Springboot中Aspect切面介绍与使用实例

    xiaoxiao2025-04-14  11

    本文主要介绍切面,实例介绍了日志处理,与自定义注解捕获等,文章末附源码地址

    简介

    AOP为Aspect Oriented Programming的缩写, 意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 在日常开发当中经常用来记录日志,方法跟踪、拦截、事务,权限等

    切面方法说明:

    注解作用@Aspect把当前类标识为一个切面供容器读取@Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式@Before标识一个前置增强方法,其不能阻止业务模块的执行,除非抛出异常;@AfterReturning后置增强,相当于AfterReturningAdvice,方法退出时执行@AfterThrowing异常抛出增强,相当于ThrowsAdvice@After方法之后执行,不管是抛出异常或者正常退出都会执行,类似于finally的作用;@Around环绕增强,方法执行之前,与执行之后均会执行

    引入AOP依赖

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>

    创建测试controller

    注:参数就不贴在这里了,底部附源码地址

    /** * 人员管理接口 * * @author 码农猿 */ @RestController @RequestMapping("/user") public class UserInfoController { /** * 添加人员 * * @author 码农猿 */ @PostMapping(value = "/add-user") public Response addUser1(@RequestBody UserAddParam addParam) { return Response.success(); } }

    定义测试切面

    package com.example.config.aspect; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * 切面测试 * * @author 码农猿 */ @Aspect @Component @Slf4j public class AspectExample { /** * [@Pointcut]:统一切点,对com.example.controller及其子包中所有的类的所有方法切面 */ @Pointcut("execution(public * com.example.controller..*.*(..))") public void pointcut() { log.info("[切面处理] >> 使用注解 @Pointcut 定义切点位置"); } /** * [@Before]:前置通知 */ @Before("pointcut()") public void beforeMethod(JoinPoint joinPoint) { log.info("[切面处理] >> 使用注解 @Before 调用了方法前置通知 "); } /** * [@After]:后置通知 */ @After("pointcut()") public void afterMethod(JoinPoint joinPoint) { log.info("[切面处理] >> 使用注解 @After 调用了方法后置通知 "); } /** * [@AfterRunning]:@AfterRunning: 返回通知 rsult为返回内容 */ @AfterReturning(value = "pointcut()", returning = "result") public void afterReturningMethod(JoinPoint joinPoint, Object result) { log.info("[切面处理] >> 使用注解 @AfterReturning 调用了方法返回后通知 "); } /** * [@AfterThrowing]:异常通知 */ @AfterThrowing(value = "pointcut()", throwing = "e") public void afterThrowingMethod(JoinPoint joinPoint, Exception e) { log.info("[切面处理] >> 使用注解 @AfterThrowing 调用了方法异常通知 "); } /** * [@Around]:环绕通知 */ @Around("pointcut()") public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable { //获取方法名称 String methodName = pjp.getSignature().getName(); log.info("[切面处理] >> 使用注解 @Around 方法 :{} 执行之前 ", methodName); Object result = pjp.proceed(); log.info("[切面处理] >> 使用注解 @Around 方法 :{} 执行之后 ,返回值:{}", methodName, JSON.toJSONString(result)); return result; } }

    测试结果 控制台输出结果

    执行顺序分析

    使用实例

    1.打印controller方法参数日志

    package com.example.config.aspect; import com.example.util.SystemClock; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Random; /** * controller 日志打印 * 服务请求拦截 * 1.请求处理 * 2.map转请求对象 * * @author 码农猿 */ @Aspect @Component @Slf4j public class ControllerAspect { private static Random random = new Random(); /** * 统一切点,对com.example.controller及其子包中所有的类的所有方法切面 */ @Pointcut("execution(public * com.example.controller..*.*(..))") private void allMethod() { } @Around("allMethod()") public Object doAround(ProceedingJoinPoint call) throws Throwable { MethodSignature signature = (MethodSignature) call.getSignature(); Method method = signature.getMethod(); String[] classNameArray = method.getDeclaringClass().getName().split("\\."); String methodName = classNameArray[classNameArray.length - 1] + " >>> " + method.getName(); String params = buildParamsDefault(call); long start = SystemClock.millisClock().now(); //随机请求标识,便于日志追踪 String reqId = getRandom(10); Object result = null; try { log.info("日志标识:{} >> [接口请求开始] 方法名:{} , 请求参数:{}", reqId, methodName, params); result = call.proceed(); return result; } finally { long runTimes = SystemClock.millisClock().now() - start; log.info("日志标识:{} >> [接口请求结束] 方法名:{} , 请求耗时:{} ms", reqId, methodName, runTimes); } } private String buildParamsDefault(ProceedingJoinPoint call) { String params = " ["; for (int i = 0; i < call.getArgs().length; i++) { Object obj = call.getArgs()[i]; if (null != obj) { if (obj instanceof HttpServletRequest) { continue; } String str = obj.toString(); if (obj.getClass() != String.class) { str = ToStringBuilder.reflectionToString(obj, ToStringStyle.SHORT_PREFIX_STYLE); } if (i != call.getArgs().length - 1) { params += str + ","; } else { params += str + " ]"; } } if (params.length() == 1) { params += "]"; } } return params; } private static String getRandom(int length) { StringBuilder ret = new StringBuilder(); for (int i = 0; i < length; ++i) { boolean isChar = random.nextInt(2) % 2 == 0; if (isChar) { int choice = random.nextInt(2) % 2 == 0 ? 65 : 97; ret.append((char) (choice + random.nextInt(26))); } else { ret.append(Integer.toString(random.nextInt(10))); } } return ret.toString(); } }

    效果示例

    2.自定义注解切面处理

    背景:在某些业务场景下需要某方法做一些特殊的处理,可以通过该方式 以自定义注解捕获异常为例 注:当然springBoot 统一异常处理应该使用@ControllerAdvice,以下示例,只是举了个例子,用来说明切面的作用

    自定义注解

    /** * 接口统一异常处理 * 注:所有需要捕获异常的接口均需要在实现处添加该注解 * * @author 码农猿 */ @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiAnnotation { /** * 方法描述 */ String description() default "业务接口"; }

    切面代码

    package com.example.aspect; import com.example.annotation.ApiAnnotation; import com.example.bean.result.Response; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import java.text.MessageFormat; /** * Facade 接口统一异常处理切面 * * @author 码农猿 */ @Slf4j @Aspect @Component public class ApiExceptionAdviceAspect { /** * 匹配所有使用以下注解的方法 * * @see ApiAnnotation */ @Pointcut("@annotation(com.example.annotation.ApiAnnotation)") public void pointCut() { } @Around("pointCut()&&@annotation(adviceLog)") public Object logAround(ProceedingJoinPoint joinPoint, ApiAnnotation adviceLog) { //方法返回结果 Object result = null; try { log.info("【Api-切面处理】>> {} >> 处理开始 ", adviceLog.description()); //执行方法 result = joinPoint.proceed(); log.info("【Api-切面处理】>> {} >> 处理结束 ", adviceLog.description()); return result; } catch (Throwable e) { Response apiResult = new Response(); //其它未知异常捕获 log.error("【Api-切面处理】>> {} >> 发生异常 , stack = {}", adviceLog.description(), ExceptionUtils.getStackTrace(e)); String errorMsg = MessageFormat.format("{0} >> 发生异常了", adviceLog.description()); apiResult.setSuccess(false); apiResult.setErrorMsg(errorMsg); return apiResult; } } }

    源码地址 关注程序员小强公众号更多编程趣事,知识心得与您分享

    最新回复(0)