ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AspectJ 全注解开发详解

AspectJ 全注解开发详解 AspectJ 全注解开发详解AspectJ 全注解开发是指完全用注解定义切面、切点和通知不写 XML 配置文件。Spring AOP 支持 AspectJ 的注解语法配合EnableAspectJAutoProxy即可开启自动代理。这是目前 Spring 项目中最主流的 AOP 开发方式。一、全注解开发的核心步骤步骤操作注解1引入依赖spring-boot-starter-aop2开启自动代理EnableAspectJAutoProxy3定义切面AspectComponent4定义切点Pointcut5定义通知Before、After、Around等二、引入依赖dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency这个依赖包含了spring-aop、aspectjweaver等核心库。Spring Boot 项目引入后AOP 自动配置默认开启不需要额外加EnableAspectJAutoProxy。如果是纯 Spring 项目需要手动添加ConfigurationEnableAspectJAutoProxypublicclassAopConfig{}三、定义切面切面用Aspect和Component标注。Aspect告诉 Spring 这是一个切面类Component让 Spring 扫描并注册为 Bean。AspectComponentpublicclassLogAspect{privatestaticfinalLoggerlogLoggerFactory.getLogger(LogAspect.class);// 切点和通知在这里定义}Aspect来自org.aspectj.lang.annotation包Component来自 Spring。四、定义切点切点用Pointcut定义方法返回void方法体为空。Pointcut(execution(* com.example.service.*.*(..)))publicvoidserviceLayer(){}Pointcut(annotation(com.example.annotation.Log))publicvoidlogAnnotation(){}Pointcut(execution(* com.example.service.*.*(..)) annotation(com.example.annotation.Log))publicvoidloggedServiceMethod(){}切点方法名就是切点的名称其他通知通过方法名引用它。切点方法本身不会被调用只是一个标识。五、五种通知1. Before 前置通知在目标方法执行前执行。Before(serviceLayer())publicvoidbefore(JoinPointjoinPoint){StringmethodNamejoinPoint.getSignature().getName();Object[]argsjoinPoint.getArgs();log.info(开始执行{}参数{},methodName,Arrays.toString(args));}2. After 后置通知在目标方法执行后执行无论是否抛出异常。After(serviceLayer())publicvoidafter(JoinPointjoinPoint){log.info(执行结束{},joinPoint.getSignature().getName());}3. AfterReturning 返回通知在目标方法正常返回后执行可以获取返回值。AfterReturning(pointcutserviceLayer(),returningresult)publicvoidafterReturning(JoinPointjoinPoint,Objectresult){log.info(返回结果{},result);}returning属性指定方法参数名Spring 会把目标方法的返回值赋给它。如果返回值是 voidresult为 null。4. AfterThrowing 异常通知在目标方法抛出异常后执行可以获取异常对象。AfterThrowing(pointcutserviceLayer(),throwingex)publicvoidafterThrowing(JoinPointjoinPoint,Exceptionex){log.error(执行异常{}异常信息{},joinPoint.getSignature().getName(),ex.getMessage());}throwing属性指定方法参数名Spring 会把目标方法抛出的异常赋给它。可以指定具体的异常类型来过滤。5. Around 环绕通知功能最全的通知可以控制目标方法是否执行、修改返回值、处理异常。Around(serviceLayer())publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{StringmethodNamepjp.getSignature().getName();longstartSystem.currentTimeMillis();Objectresult;try{resultpjp.proceed();// 执行目标方法longcostSystem.currentTimeMillis()-start;log.info({} 执行成功耗时{}ms,methodName,cost);returnresult;}catch(Throwablee){longcostSystem.currentTimeMillis()-start;log.error({} 执行异常耗时{}ms,methodName,cost,e);throwe;}}ProceedingJoinPoint比JoinPoint多了一个proceed()方法用于执行目标方法。Around的返回值会作为目标方法的返回值返回所以必须返回Object且要返回proceed()的结果。六、获取连接点信息JoinPoint提供了获取目标方法信息的方法。Before(serviceLayer())publicvoidbefore(JoinPointjoinPoint){// 方法签名SignaturesignaturejoinPoint.getSignature();StringmethodNamesignature.getName();StringclassNamesignature.getDeclaringTypeName();// 方法参数Object[]argsjoinPoint.getArgs();// 目标对象ObjecttargetjoinPoint.getTarget();// 代理对象ObjectproxyjoinPoint.getThis();// 获取方法上的注解MethodSignaturemethodSignature(MethodSignature)signature;MethodmethodmethodSignature.getMethod();LoglogAnnotationmethod.getAnnotation(Log.class);}方法返回值说明getSignature()Signature方法签名getArgs()Object[]方法参数getTarget()Object目标对象getThis()Object代理对象getKind()String连接点类型七、切点表达式的组合多个切点可以用逻辑运算符组合。Pointcut(execution(* com.example.service.*.*(..)))publicvoidserviceLayer(){}Pointcut(annotation(com.example.annotation.Log))publicvoidlogAnnotation(){}// 与service 包下标注了 Log 的方法Pointcut(serviceLayer() logAnnotation())publicvoidloggedServiceMethod(){}// 或service 包或 dao 包下的方法Pointcut(execution(* com.example.service.*.*(..)) || execution(* com.example.dao.*.*(..)))publicvoidserviceOrDao(){}// 非service 包下没有标注 NoLog 的方法Pointcut(serviceLayer() !annotation(com.example.annotation.NoLog))publicvoidserviceWithoutNoLog(){}八、通知的执行顺序同一个切面中不同通知的执行顺序Around 前半部分 → Before → 目标方法 → AfterReturning / AfterThrowing → After Around 后半部分完整顺序Around中proceed()之前的代码Before目标方法执行AfterReturning正常返回或AfterThrowing抛出异常AfterAround中proceed()之后的代码九、多个切面的执行顺序多个切面作用于同一个方法时用Order控制顺序。数值越小优先级越高。AspectComponentOrder(1)publicclassLogAspect{// 先执行}AspectComponentOrder(2)publicclassTransactionAspect{// 后执行}对于BeforeOrder值小的先执行。对于AfterOrder值大的先执行。Around则按嵌套顺序执行。十、完整示例1. 自定义注解Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)publicinterfaceLog{Stringvalue()default;}2. 切面AspectComponentSlf4jpublicclassLogAspect{Pointcut(annotation(com.example.annotation.Log))publicvoidlogPointcut(){}Around(logPointcut() annotation(logAnnotation))publicObjectaround(ProceedingJoinPointpjp,LoglogAnnotation)throwsThrowable{StringmethodNamepjp.getSignature().getName();StringoperationlogAnnotation.value();log.info(开始执行{}操作{},methodName,operation);longstartSystem.currentTimeMillis();try{Objectresultpjp.proceed();longcostSystem.currentTimeMillis()-start;log.info(执行成功{}耗时{}ms,methodName,cost);returnresult;}catch(Throwablee){longcostSystem.currentTimeMillis()-start;log.error(执行失败{}耗时{}ms异常{},methodName,cost,e.getMessage());throwe;}}}3. 业务方法ServicepublicclassUserService{Log(注册用户)publicUserregister(Stringname){System.out.println(注册用户name);returnnewUser(1L,name);}Log(查询用户)publicUserfindById(Longid){returnnewUser(id,张三);}}4. 测试SpringBootTestclassUserServiceTest{AutowiredprivateUserServiceuserService;TestvoidtestRegister(){userService.register(张三);}}输出开始执行register操作注册用户 注册用户张三 执行成功register耗时0ms十一、通知中获取注解参数Around通知可以直接获取方法上的注解对象这是通过annotation切点表达式实现的。Around(annotation(logAnnotation))publicObjectaround(ProceedingJoinPointpjp,LoglogAnnotation)throwsThrowable{// logAnnotation 就是方法上的 Log 注解实例StringvaluelogAnnotation.value();// ...returnpjp.proceed();}方法参数名logAnnotation必须与切点表达式annotation(logAnnotation)中的名称一致。Spring 会自动注入注解实例。十二、注意事项1. 切面本身不能被代理切面类不应该被其他切面增强否则会导致无限递归。Spring 会跳过切面类的代理。2. 切点表达式要准确execution(* com.example.service.*.*(..))只匹配service包下的类。包名或方法签名写错会导致增强不生效。3. 内部调用失效同一个类中方法 A 调用方法 BB 的增强不会生效。因为内部调用不经过代理对象。需要通过注入自身代理或AopContext.currentProxy()解决。4. Around 必须返回 proceed() 的结果// ❌ 错误返回值丢失Around(serviceLayer())publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{pjp.proceed();returnnull;// 目标方法的返回值被丢弃}// ✅ 正确Around(serviceLayer())publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{Objectresultpjp.proceed();returnresult;}5. AfterReturning 和 AfterThrowing 不能同时获取返回值两者互斥正常返回时执行AfterReturning抛出异常时执行AfterThrowing。6. 代理方式Spring Boot 默认使用 CGLIB 代理。如果需要 JDK 动态代理设置EnableAspectJAutoProxy(proxyTargetClass false)。十三、总结维度核心要点依赖spring-boot-starter-aop开启Spring Boot 自动开启纯 Spring 用EnableAspectJAutoProxy切面AspectComponent切点Pointcut方法返回 void方法体为空通知Before、After、AfterReturning、AfterThrowing、Around连接点JoinPoint普通通知、ProceedingJoinPoint环绕通知顺序Order控制切面优先级注意事项内部调用失效、Around 必须返回 proceed() 的结果默认代理Spring Boot 默认 CGLIBAspectJ 全注解开发是 Spring AOP 的主流方式。它把切面、切点、通知都写在 Java 类里配置简单、类型安全、可读性高。掌握Aspect、Pointcut和五种通知的用法配合切点表达式就能覆盖绝大多数横切关注点的处理需求。
返回列表