11、Spring之基于注解的AOP

涛声依旧在  金牌会员 | 2023-8-28 12:08:02 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 873|帖子 873|积分 2619

11.1、环境搭建

创建名为spring_aop_annotation的新module,过程参考9.1节
11.1.1、配置打包方式和依赖


注意:AOP需要在IOC的基础上实现,因此需要导入IOC的依赖
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <groupId>org.rain</groupId>
  7.     <artifactId>spring_aop_annotation</artifactId>
  8.     <version>1.0-SNAPSHOT</version>
  9.     <packaging>jar</packaging>
  10.     <dependencies>
  11.         
  12.         <dependency>
  13.             <groupId>org.springframework</groupId>
  14.             <artifactId>spring-context</artifactId>
  15.             <version>5.3.1</version>
  16.         </dependency>
  17.         
  18.         <dependency>
  19.             <groupId>org.springframework</groupId>
  20.             <artifactId>spring-aspects</artifactId>
  21.             <version>5.3.1</version>
  22.         </dependency>
  23.         
  24.         <dependency>
  25.             <groupId>junit</groupId>
  26.             <artifactId>junit</artifactId>
  27.             <version>4.12</version>
  28.             <scope>test</scope>
  29.         </dependency>
  30.     </dependencies>
  31. </project>
复制代码
11.1.2、创建Calculator接口及实现类

  1. package org.rain.spring.aop.annotation;
  2. /**
  3. * @author liaojy
  4. * @date 2023/8/12 - 17:43
  5. */
  6. public interface Calculator {
  7.     int add(int i, int j);
  8.     int sub(int i, int j);
  9.     int mul(int i, int j);
  10.     int div(int i, int j);
  11. }
复制代码
  1. package org.rain.spring.aop.annotation;
  2. import org.springframework.stereotype.Component;
  3. /**
  4. * @author liaojy
  5. * @date 2023/8/12 - 17:45
  6. */
  7. // @Component注解保证这个目标类能够放入IOC容器
  8. @Component
  9. public class CalculatorImpl implements Calculator {
  10.     public int add(int i, int j) {
  11.         int result = i + j;
  12.         System.out.println("方法内部 result = " + result);
  13.         return result;
  14.     }
  15.     public int sub(int i, int j) {
  16.         int result = i - j;
  17.         System.out.println("方法内部 result = " + result);
  18.         return result;
  19.     }
  20.     public int mul(int i, int j) {
  21.         int result = i * j;
  22.         System.out.println("方法内部 result = " + result);
  23.         return result;
  24.     }
  25.     public int div(int i, int j) {
  26.         int result = i / j;
  27.         System.out.println("方法内部 result = " + result);
  28.         return result;
  29.     }
  30. }
复制代码
11.1.3、创建切面类LoggerAspect

  1. package org.rain.spring.aop.annotation;
  2. import org.aspectj.lang.annotation.Aspect;
  3. import org.springframework.stereotype.Component;
  4. /**
  5. * @author liaojy
  6. * @date 2023/8/12 - 17:56
  7. */
  8. // @Aspect表示这个类是一个切面类
  9. @Aspect
  10. // @Component注解保证这个切面类能够放入IOC容器
  11. @Component
  12. public class LoggerAspect {
  13. }
复制代码
11.1.4、创建spring配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.        xmlns:context="http://www.springframework.org/schema/context"
  5.        xmlns:aop="http://www.springframework.org/schema/aop"
  6.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
  7.    
  8.     <context:component-scan base-package="org.rain.spring.aop.annotation"></context:component-scan>
  9.    
  10.     <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  11. </beans>
复制代码
11.2、前置通知的使用

11.2.1、基本示例

11.2.1.1、配置前置方法

  1.     /*
  2.     * @Before注解:用于将方法标识为前置通知(方法)
  3.     * @Before注解的value属性值为切入点表达式,其作用是将该前置通知(方法)安插到对应目标方法的连接点上
  4.     * */
  5.     @Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")
  6.     public void beforeMethod(){
  7.         System.out.println("LoggerAspect,前置通知");
  8.     }
复制代码
11.2.1.2、测试使用效果


由控制台日志可知,切面类的前置通知(方法),通过切入点表达式,作用到了目标方法的连接点上
  1.     @Test
  2.     public void testAOPByAnnotation(){
  3.         ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
  4.         // 注意:这里不能直接获取目标对象来使用;因为使用了AOP之后,IOC容器中就只有对应目标对象的代理对象;
  5.         // 如果强行获取目标对象,则报错:NoSuchBeanDefinitionException
  6.         //Calculator calculator = ioc.getBean(CalculatorImpl.class);
  7.         // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
  8.         Calculator calculator = ioc.getBean(Calculator.class);
  9.         // 只能通过代理对象来访问目标对象中的方法
  10.         calculator.add(1,2);
  11.     }
复制代码
11.2.2、高级示例

11.2.2.1、改进前置方法


该示例中(前置)通知方法引入了连接点参数,通过连接点参数,可以动态获取(切入点表达式)对应的目标方法的名称和参数列表
  1.     /*
  2.     * @Before注解:用于将方法标识为前置通知(方法)
  3.     * @Before注解的value属性值为切入点表达式,其作用是将该前置通知(方法)安插到对应目标方法的连接点上
  4.     * */
  5.     @Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")
  6.     // joinPoint参数:可以获取(通过切入点表达式定位出的)连接点的相关信息
  7.     public void beforeMethod(JoinPoint joinPoint){
  8.         // 获取连接点所对应目标方法的名称
  9.         String methodName = joinPoint.getSignature().getName();
  10.         // 获取连接点所对应目标方法的参数列表
  11.         Object[] args = joinPoint.getArgs();
  12.         System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
  13.     }
复制代码
11.2.2.2、测试使用效果


11.3、切入点表达式的进阶用法

11.3.1、高频用法示例

  1.     // @Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")
  2.     /**
  3.      * 第一个*表示任意访问修饰符和返回值类型,
  4.      * 第二个*表示该类的任意方法名称,
  5.      * (..)表示方法的任意参数列表
  6.      * 在类的位置也可以使用*,表示当前包下所有的类,
  7.      * 在包的位置也可以使用*,表示当前包下所有的子包,
  8.      */
  9.     @Before("execution(* org.rain.spring.aop.annotation.CalculatorImpl.*(..))")
  10.     public void beforeMethod(JoinPoint joinPoint){
  11.         String methodName = joinPoint.getSignature().getName();
  12.         Object[] args = joinPoint.getArgs();
  13.         System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
  14.     }
复制代码
11.3.2、详细语法图解


11.3.3、复用切入点表达式

11.3.3.1、声明公共的切入点表达式

  1.     @Pointcut("execution(* org.rain.spring.aop.annotation.CalculatorImpl.*(..))")
  2.     public void pointCutOne(){}
复制代码
11.3.3.2、在同一个切面类中复用

  1.     // @Before注解的value属性值,可以设置为使用了@Pointcut注解标识的方法名,从而复用该@Pointcut注解定义的切入点表达式
  2.     @Before("pointCutOne()")
  3.     public void beforeMethod(JoinPoint joinPoint){
  4.         String methodName = joinPoint.getSignature().getName();
  5.         Object[] args = joinPoint.getArgs();
  6.         System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
  7.     }
复制代码
11.3.3.3、在不同一个切面类中复用
  1.     // 复用其他切面类中@Pointcut注解定义的切入点表达式,
  2.         // @Before注解的value属性值,需要设置为使用了@Pointcut注解标识的(全限定类名+)方法名
  3.     @Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
  4.     public void beforeMethod(JoinPoint joinPoint){
  5.         String methodName = joinPoint.getSignature().getName();
  6.         Object[] args = joinPoint.getArgs();
  7.         System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
  8.     }
复制代码
11.4、其他通知的使用

11.4.1、后置通知

11.4.1.1、配置后置方法

  1.     // @After注解:用于将方法标识为后置通知(方法)
  2.     @After("pointCutOne()")
  3.     public void afterMethod(JoinPoint joinPoint){
  4.         String methodName = joinPoint.getSignature().getName();
  5.         Object[] args = joinPoint.getArgs();
  6.         System.out.println("LoggerAspect-->后置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
  7.     }
复制代码
11.4.1.2、测试使用效果


由控制台日志可知,后置通知在目标对象方法的finally子句中执行(一般用于释放资源)
  1.     @Test
  2.     public void testAOPByAnnotation(){
  3.         ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
  4.         // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
  5.         Calculator calculator = ioc.getBean(Calculator.class);
  6.         // 只能通过代理对象来访问目标对象中的方法
  7.         calculator.div(1,0);
  8.     }
复制代码
11.4.2、返回通知

11.4.2.1、配置返回通知

  1.     /**
  2.      * @AfterReturning注解:用于将方法标识为返回通知(方法)
  3.      *  returning属性:指定(返回)通知方法中的某个参数(名),用于接收目标对象方法的返回值
  4.      */
  5.     @AfterReturning(value = "pointCutOne()",returning = "result")
  6.     public void afterReturningMethod(JoinPoint joinPoint,Object result){
  7.         String methodName = joinPoint.getSignature().getName();
  8.         Object[] args = joinPoint.getArgs();
  9.         System.out.println("LoggerAspect-->返回通知,方法名:"+methodName+",结果:"+ result);
  10.     }
复制代码
11.4.2.2、测试使用效果


由控制台日志可知,返回通知在目标对象方法的返回值之后执行
  1.     @Test
  2.     public void testAOPByAnnotation(){
  3.         ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
  4.         // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
  5.         Calculator calculator = ioc.getBean(Calculator.class);
  6.         // 只能通过代理对象来访问目标对象中的方法
  7.         calculator.div(1,1);
  8.     }
复制代码
11.4.3、异常通知

11.4.3.1、配置异常通知

  1.     /**
  2.      * @AfterThrowing注解:用于将方法标识为异常通知(方法)
  3.      *  throwing属性:指定(异常)通知方法中的某个参数(名),用于接收目标对象方法出现的异常
  4.      */
  5.     @AfterThrowing(value = "pointCutOne()",throwing = "ex")
  6.     public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
  7.         String methodName = joinPoint.getSignature().getName();
  8.         Object[] args = joinPoint.getArgs();
  9.         System.out.println("LoggerAspect-->异常通知,方法名:"+methodName+",异常:"+ ex);
  10.     }
复制代码
11.4.3.2、测试使用效果


由控制台日志可知,异常通知在目标对象方法的catch子句中执行
  1.     @Test
  2.     public void testAOPByAnnotation(){
  3.         ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
  4.         // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
  5.         Calculator calculator = ioc.getBean(Calculator.class);
  6.         // 只能通过代理对象来访问目标对象中的方法
  7.         calculator.div(1,0);
  8.     }
复制代码
11.4.4、通知的执行顺序

11.4.4.1、Spring版本5.3.x以前


  • 前置通知
  • 目标操作
  • 后置通知
  • 返回通知或异常通知
11.4.4.2、Spring版本5.3.x以后

本示例


  • 前置通知
  • 目标操作
  • 返回通知或异常通知
  • 后置通知
11.5、环绕通知

11.5.1、配置环绕通知


环绕通知和动态代理的形式,非常相似
  1.     /**
  2.      * @Around注解:用于将方法标识为环绕通知(方法)
  3.      *  环绕通知(方法)使用的参数是ProceedingJoinPoint类型
  4.      *  环绕通知(方法)的返回值,必须和目标对象方法的返回值一致
  5.      */
  6.     @Around("pointCutOne()")
  7.     public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
  8.         String methodName = proceedingJoinPoint.getSignature().getName();
  9.         Object[] args = proceedingJoinPoint.getArgs();
  10.         Object result = null;
  11.         try {
  12.             System.out.println("LoggerAspect-->环绕前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
  13.             // 表示目标对象方法的执行
  14.             result = proceedingJoinPoint.proceed();
  15.             System.out.println("LoggerAspect-->环绕返回通知,方法名:"+methodName+",结果:"+ result);
  16.         } catch (Throwable throwable) {
  17.             throwable.printStackTrace();
  18.             System.out.println("LoggerAspect-->环绕异常通知,方法名:"+methodName+",异常:"+ throwable);
  19.         }finally {
  20.             System.out.println("LoggerAspect-->环绕后置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
  21.         }
  22.         return result;
  23.     }
复制代码
11.5.2、测试使用效果


注意:因为环绕通知包括了其他四种通知,所以一般要么配置其他四种通知,要么只配置环绕通知;本示例为了展示效果才同时配置
  1.     @Test
  2.     public void testAOPByAnnotation(){
  3.         ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
  4.         // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
  5.         Calculator calculator = ioc.getBean(Calculator.class);
  6.         // 只能通过代理对象来访问目标对象中的方法
  7.         calculator.div(1,1);
  8.     }
复制代码
11.6、切面的优先级

11.6.1、创建其他切面类ValidateAspect

  1. package org.rain.spring.aop.annotation;
  2. import org.aspectj.lang.annotation.Aspect;
  3. import org.springframework.stereotype.Component;
  4. /**
  5. * @author liaojy
  6. * @date 2023/8/15 - 7:49
  7. */
  8. @Aspect
  9. @Component
  10. public class ValidateAspect {
  11. }
复制代码
11.6.2、配置前置通知方法

  1.     @Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
  2.     public void beforeMethod(){
  3.         System.out.println("ValidateAspect-->前置通知");
  4.     }
复制代码
11.6.3、测试使用效果


由控制台日志可知,ValidateAspect切面的前置通知方法生效了,但执行顺序在LoggerAspect切面的前置通知方法的后面
  1.     @Test
  2.     public void testAOPByAnnotation(){
  3.         ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
  4.         // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
  5.         Calculator calculator = ioc.getBean(Calculator.class);
  6.         // 只能通过代理对象来访问目标对象中的方法
  7.         calculator.div(1,1);
  8.     }
复制代码
11.6.4、调整切面的优先级

  1. package org.rain.spring.aop.annotation;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;/** * @author liaojy * @date 2023/8/15 - 7:49 */@Aspect@Component// @Order注解:用于设置切面的优先级,value属性值越小,优先级越高,默认值为Integer的最大值@Order(2023)public class ValidateAspect {    @Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
  2.     public void beforeMethod(){
  3.         System.out.println("ValidateAspect-->前置通知");
  4.     }}
复制代码
11.6.5、测试调整后的效果


由控制台日志可知,ValidateAspect切面的前置通知方法的执行顺序,在LoggerAspect切面的前置通知方法的前面
这是因为ValidateAspect切面的@Order注解的value属性值已设为2023,要小于LoggerAspect切面所使用的默认值(Integer的最大值2147483647)
11.7、扩展知识



  • AspectJ本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,但最终效果是动态的。
  • weaver就是织入器,Spring只是借用了AspectJ中的注解。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

涛声依旧在

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表