Spring(三)-AOP

打印 上一主题 下一主题

主题 938|帖子 938|积分 2814

1、名词理解


  • 切面(Aspect):

    • 含有前置通知,后置通知,返回通知,异常抛出通知,环绕通知等方法的

  • 通知(Advice):

    • 对原方法进行添加处理(如日志等)的方法

  • 切入点(PointCute):

    • 通知需要在哪些方法上执行的表达式;(可以唯一匹配或模糊匹配);

  • 连接点(JoinPoint):

    • 与切入点匹配的具体执行的方法

  • 目标(Target):

    • 原业务类(主要 是核心代码);

  • 代理(Proxy):

    • 生成的代理类(包含原业务类的 核心代码 和 通知里面的代码);

2、前置通知

2.1 jar
  1. <properties>
  2.         <spring.version>4.3.18.RELEASE</spring.version>
  3. </properties>
  4. <dependencies>
  5.    
  6.     <dependency>
  7.         <groupId>org.springframework</groupId>
  8.         <artifactId>spring-beans</artifactId>
  9.         <version>${spring.version}</version>
  10.     </dependency>
  11.    
  12.    
  13.     <dependency>
  14.         <groupId>org.springframework</groupId>
  15.         <artifactId>spring-core</artifactId>
  16.         <version>${spring.version}</version>
  17.     </dependency>
  18.    
  19.    
  20.     <dependency>
  21.         <groupId>org.springframework</groupId>
  22.         <artifactId>spring-context</artifactId>
  23.         <version>${spring.version}</version>
  24.     </dependency>
  25.    
  26.    
  27.     <dependency>
  28.         <groupId>org.springframework</groupId>
  29.         <artifactId>spring-expression</artifactId>
  30.         <version>${spring.version}</version>
  31.     </dependency>
  32.    
  33.    
  34.    
  35.     <dependency>
  36.         <groupId>org.springframework</groupId>
  37.         <artifactId>spring-aspects</artifactId>
  38.         <version>${spring.version}</version>
  39.     </dependency>
  40.    
  41. </dependencies>
复制代码
2.2 切入点

通知需要在哪些方法上执行的表达式;(可以唯一匹配或模糊匹配);
2.2.1 唯一匹配
  1. execution(public int com.kgc.spring.aspectj.ArithmeticCalculator.add(int ,int ))
复制代码
execution(修饰符  返回值类型   方法全类名)
2.2.2 模糊匹配
  1. execution(* com.kgc.spring.aspectj.*.*(..)
复制代码
通用切入点表达式含义:

  • 第一个*:代表任意的修饰符,任意的返回值类型;
  • 第二个*:代表任意的类;
  • 第三个*:代表任意的方法;
  • . .           :代表任意的类型和个数的形参;
2.2.3 可重用切入点表达式

其他地方直接应用此方法即可;
  1. //重用切入点表达式
  2. @Pointcut( "execution(* com.kgc.spring.aspectj.*.*(..))")
  3. public void joinPointcut(){}
  4. //同一个类中引用
  5. @Before("joinPointcut()")
  6. @After("joinPointcut()")
  7. //其他类中引用(方法全类名)
  8. @Before("com.kgc.spring.aspectj.LogAspect.joinPointcut()")
复制代码
2.3  JoinPoint 和 ProceedingJoinPoint

2.3.1 JoinPoint 对象

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。
常用api:
方法名功能Signature getSignature();获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息Object[] getArgs();获取传入目标方法的参数对象Object getTarget();获取被代理的对象Object getThis();获取代理对象2.3.2 ProceedingJoinPoint对象

ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中 添加了 两个方法.
方法名功能Object proceed() throws Throwable执行目标方法Object proceed(Object[] var1) throws Throwable传入的新的参数去执行目标方法2.4  @Before

2.4.1 接口

ArithmeticCalculator
  1. public interface ArithmeticCalculator {
  2.     //加
  3.     int add(int m,int n);
  4.     //减
  5.     int sub(int m,int n);
  6.     //乘
  7.     int nul(int m,int n);
  8.     //除
  9.     int div(int m,int n);
  10. }
复制代码
2.4.2 实现类

ArithmeticCalculatorImpl
  1. @Service("arithmeticCalculator")  
  2. //起别名,方便单元测试,根据别名,从容器中获取
  3. public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
  4.     @Override
  5.     public int add(int m, int n) {
  6.         return m + n;
  7.     }
  8.     @Override
  9.     public int sub(int m, int n) {
  10.         return m - n;
  11.     }
  12.     @Override
  13.     public int nul(int m, int n) {
  14.         return m*n;
  15.     }
  16.     @Override
  17.     public int div(int m, int n) {
  18.         System.out.println("====== 执行 div 方法 ======");
  19.         return m/n;
  20.     }
  21. }
复制代码
2.4.3 @Before 前置通知

在目标方法执行前,自动执行此方法(通过代理实现);
  1. @Component //声明为一个普通的组件,放入spring的容器中,才可以生效
  2. @Aspect  //声明当前类是 一个切面
  3. public class LogAspect {
  4.     //重用切入点表达式
  5.     @Pointcut( "execution(* com.kgc.spring.aspectj.*.*(..))")
  6.     public void joinPointcut(){}
  7.     //前置通知 @Before
  8.     @Before("joinPointcut()")
  9.     public void  logBeforeMethod(JoinPoint joinPoint){
  10.         //获取通知作用的目标方法名
  11.         String methodName = joinPoint.getSignature().getName();
  12.         //获取通知作用的目标方法入参,返回的是参数值数组
  13.         Object[] methodParams = joinPoint.getArgs();
  14.         System.out.println("------ LogAspect "+methodName+" 方法,入参:"+ Arrays.toString(methodParams) +" ------");
  15.         }
  16.    
  17. }
复制代码
2.5 配置文件

spring-aop.xml
  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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  7.        
  8.         <context:component-scan base-package="com.kgc.spring.aspectj"></context:component-scan>
  9.    
  10.    
  11.         <aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
  12.    
  13. </beans>
复制代码
2.6测试
  1. public  void  testSpringAopAspectj(){
  2.     //从容器中获取计算器的实例对象
  3.     ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
  4.     System.out.println(arithmeticCalculator.getClass());
  5.     //调用切面作用的目标方法,执行操作,
  6.     int result = arithmeticCalculator.div(20, 10);
  7.     System.out.println("****** 通过单元测试,计算结果:"+result +" ******");
  8. }
复制代码
测试结果
  1. class com.sun.proxy.$Proxy15
  2.    
  3. ------ LogAspect div 方法,入参:[20, 10] ------
  4.    
  5. ====== 执行 div 方法 ======
  6.    
  7. ****** 通过单元测试,计算结果:2 ******   
复制代码
3、后置通知

3.1 @After

目标方法发执行之后,自动执行;
特点:

  • 后置通知无法获取目标方法的返回值;
  • 它的执行跟目标方法是否抛出异常无关,不影响此方法的执行;
  1. @After("joinPointcut()")
  2. public void  logAfterMethod(JoinPoint joinPoint){
  3.     //获取通知作用的目标方法名
  4.     String methodName = joinPoint.getSignature().getName();
  5.     //获取通知作用的目标方法入参,返回的是参数值数组
  6.     Object[] methodParams = joinPoint.getArgs();
  7.     System.out.println("------ LogAspect "+methodName+" 方法执行结束 ------");
  8. }
复制代码
3.2  测试

3.2.1 无异常
  1. @Test
  2. public  void  testSpringAopAspectj(){
  3.     //从容器中获取计算器的实例对象
  4.     ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
  5.     //调用切面作用的目标方法,执行操作,
  6.     int result = arithmeticCalculator.div(20, 10);
  7.     System.out.println("****** 通过单元测试,计算结果:"+result +" ******");
  8. }
复制代码
测试结果
  1. ====== 执行 div 方法 ======
  2.    
  3. ------ LogAspect div 方法执行结束 ------
  4.    
  5. ****** 通过单元测试,计算结果:2 ******
复制代码
3.2.2 有异常
  1. @Test
  2. public  void  testSpringAopAspectj(){
  3.     //从容器中获取计算器的实例对象
  4.     ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
  5.     //调用切面作用的目标方法,执行操作,
  6.     int result = arithmeticCalculator.div(20, 0);
  7.     System.out.println("****** 通过单元测试,计算结果:"+result +" ******");
  8. }
复制代码
测试结果
  1. ====== 执行 div 方法 ======
  2.    
  3. ------ LogAspect div 方法执行结束 ------ //有异常也会执行后置通知
  4. java.lang.ArithmeticException: / by zero
复制代码
4、返回通知

4.1 @AfterReturning


  • 目标方法返回结果后自动执行,可以获取目标方法的返回值;
  • 但是要求@AfterReturning必须增加属性returning,指定一个参数名;
  • 且此参数名必须跟通知方法的一个形参名一致用于接收返回值;
  1. @AfterReturning(value = "joinPointcut()",returning = "result")
  2. public void  afterReturningMethod(JoinPoint joinPoint,Object result){
  3.     //获取通知作用的目标方法名
  4.     String methodName = joinPoint.getSignature().getName();
  5.     System.out.println("------ LogAspect "+methodName+" 方法,执行结果:"+ result +" ------");
  6. }
复制代码
4.2 测试

测试结果
  1. ====== 执行 div 方法 ======
  2.    
  3. ------ LogAspect div 方法,返回结果:2 ------
  4.    
  5. ****** 通过单元测试,计算结果:2 ******
复制代码
5、异常抛出通知

5.1 @AfterThrowing


  • 异常抛出通知 @AfterThrowing ,在目标方法抛出异常后,可以获取目标方法发生异常后抛出的异常信息;
  • 但是要求 @AfterThrowing 必须增加属性 throwing,指定一个参数名;
  • 且此参数名必须跟通知方法的一个形参名一致,用于接收异常;
  1. @AfterThrowing(value = "joinPointcut()",throwing = "ex")
  2. public void logAfterThrowingMethod(JoinPoint joinPoint,Exception ex){
  3.     //获取通知作用的目标方法名
  4.     String methodName = joinPoint.getSignature().getName();
  5.     System.out.println("------ LogAspect "+methodName+" 方法,执行异常信息:"+ ex.getMessage() +" ------");
  6. }
复制代码
5.2 测试
  1. @Test
  2. public  void  testSpringAopAspectj(){
  3.     //从容器中获取计算器的实例对象
  4.     ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
  5.     System.out.println(arithmeticCalculator.getClass());
  6.     //调用切面作用的目标方法,执行操作,
  7.     int result = arithmeticCalculator.div(20, 0);
  8.     System.out.println("****** 通过单元测试,计算结果:"+result +" ******");
  9. }
复制代码
测试结果
  1. ====== 执行 div 方法 ======
  2. ------ LogAspect div 方法,执行异常信息:/ by zero ------
  3. java.lang.ArithmeticException: / by zero
复制代码
6、环绕通知

6.1 @Around


  • 环绕通知 @Around,可以看作是上面四种通知的结合体,一般不建议跟单个的通知共用(防止冲突失效);
  • 作用:可以让开发人员在环绕通知的处理方法中根据不同也业务逻辑,决定是否发起对目标方法的调用;
  1. @Around(value = "joinPointcut()")
  2. public Object logAroundMethod(ProceedingJoinPoint joinPoint){
  3.     //获取通知作用的目标方法名
  4.     String methodName = joinPoint.getSignature().getName();
  5.     //定义获取目标方法的返回值变量
  6.     Object result = null;
  7.     try{
  8.         //实现前置通知功能
  9.         System.out.println("------ LogAspect "+methodName+" 方法 Around通知,入参:"+ Arrays.toString(joinPoint.getArgs()) +" ------");
  10.         //手动调用原目标方法(业务中决定,是否对核心方法方法发起调用)
  11.         result  = joinPoint.proceed();
  12.     }catch (Throwable tx){
  13.         //实现异常抛出通知功能
  14.         System.out.println("------ LogAspect "+methodName+" 方法 Around通知,执行异常信息:"+ tx.getMessage() +" ------");
  15.     }finally {
  16.         //实现后置通知功能
  17.         System.out.println("------ LogAspect "+methodName+" 方法 Around通知,执行结束 ------");
  18.     }
  19.     //实现返回通知功能
  20.     System.out.println("------ LogAspect "+methodName+" 方法 Around通知,执行结果:"+ result +" ------");
  21.     return result;
  22. }
复制代码
6.2 测试

6.2.1 测试结果,无异常
  1. //调用切面作用的目标方法,执行操作,
  2. int result = arithmeticCalculator.div(20, 10);
复制代码
  1. class com.sun.proxy.$Proxy13
  2.    
  3. ------ LogAspect div 方法 Around通知,入参:[20, 10] ------
  4.    
  5. ====== 执行 div 方法 ======
  6.    
  7. ------ LogAspect div 方法 Around通知,执行结束 ------
  8.    
  9. ------ LogAspect div 方法 Around通知,返回结果:2 ------
  10.    
  11. ****** 通过单元测试,计算结果:2 ******
复制代码
6.2.2 测试结果,有异常
  1. //调用切面作用的目标方法,执行操作,
  2. int result = arithmeticCalculator.div(20, 0);
复制代码
  1. class com.sun.proxy.$Proxy13
  2.    
  3. ------ LogAspect div 方法 Around通知,入参:[20, 0] ------
  4.    
  5. ====== 执行 div 方法 ======
  6.    
  7. ------ LogAspect div 方法 Around通知,执行异常信息:/ by zero ------
  8.    
  9. ------ LogAspect div 方法 Around通知,执行结束 ------
  10.    
  11. ------ LogAspect div 方法 Around通知,返回结果:null ------
复制代码
6.2.3 测试结果 不调用 原方法
  1. //调用切面作用的目标方法,执行操作,
  2. int result = arithmeticCalculator.div(20, 0);//(业务中决定,是否对核心方法发起调用)//不调用核心方法//result  = joinPoint.proceed();
复制代码
  1. ------ LogAspect div 方法 Around通知,入参:[20, 10] ------
  2.    
  3. ------ LogAspect div 方法 Around通知,执行结束 ------
  4.    
  5. ------ LogAspect div 方法 Around通知,返回结果:null ------
复制代码
7、切入点优先级

当有多个前置通知时,我们想自定义前置通知顺序:使用@Order(int)
指定切面优先级,一般都是int型整数,值越小优先级越高**(默认值 2^31 - 1 最低优先级);
7.1 多个前置通知

logBeforeMethod
  1. @Before("joinPointcut()")
  2. public void  logBeforeMethod(JoinPoint joinPoint){
  3.     //获取通知作用的目标方法名
  4.     String methodName = joinPoint.getSignature().getName();
  5.     //获取通知作用的目标方法入参,返回的是参数值数组
  6.     Object[] methodParams = joinPoint.getArgs();
  7.     System.out.println("------ LogAspectBeforeMethod "+methodName+" 方法,入参:"+ Arrays.toString(methodParams) +" ------");
  8. }
复制代码
verifyBeforeMethod
  1. @Component
  2. @Aspect
  3. public class VerifyParamAspect {
  4.     @Before("com.kgc.spring.aspectj.LogAspect.joinPointcut()")
  5.     public void  verifyBeforeMethod( JoinPoint joinPoint){
  6.         //获取通知作用的目标方法名
  7.         String methodName = joinPoint.getSignature().getName();
  8.         //获取通知作用的目标方法入参,返回的是参数值数组
  9.         Object[] methodParams = joinPoint.getArgs();
  10.         System.out.println("------ verifyBeforeMethod "+methodName+" 方法,入参:"+ Arrays.toString(methodParams) +" ------");
  11.     }
  12. }
复制代码
7.2 测试(默认)
  1. @Test
  2. public   void testVerifyParamAspect(){
  3.     //从容器中获取计算器的实例对象
  4.     ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
  5.     System.out.println(arithmeticCalculator.getClass());
  6.     //调用切面作用的目标方法,执行操作,
  7.     int result = arithmeticCalculator.div(20, 10);
  8.     System.out.println("****** 通过单元测试,计算结果:"+result +" ******");
  9. }
复制代码
测试结果
  1. ------ LogAspectBeforeMethod div 方法,入参:[20, 10] ------  //LogAspectBeforeMethod 先执行
  2.    
  3. ------ verifyBeforeMethod div 方法,入参:[20, 10] ------
  4.    
  5. ====== 执行 div 方法 ======
  6.    
  7. ****** 通过单元测试,计算结果:2 ******
复制代码
7.3 测试(自定义优先级)
  1. @Component
  2. @Aspect
  3. @Order(1) //指定切面优先级,一般都是int型整数,值越小,优先级越高(默认值 2^31 - 1)
  4. public class VerifyParamAspect {
  5.     @Before("com.kgc.spring.aspectj.LogAspect.joinPointcut()")
  6.     public void  verifyBeforeMethod( JoinPoint joinPoint){
  7.         //获取通知作用的目标方法名
  8.         String methodName = joinPoint.getSignature().getName();
  9.         //获取通知作用的目标方法入参,返回的是参数值数组
  10.         Object[] methodParams = joinPoint.getArgs();
  11.         System.out.println("------ verifyBeforeMethod "+methodName+" 方法,入参:"+ Arrays.toString(methodParams) +" ------");
  12.     }
  13. }
复制代码
测试结果
  1. ------ verifyBeforeMethod div 方法,入参:[20, 10] ------ //优先级高的切面中的verifyBeforeMethod,先执行
  2.    
  3. ------ LogAspectBeforeMethod div 方法,入参:[20, 10] ------
  4.    
  5. ====== 执行 div 方法 ======
  6.    
  7. ****** 通过单元测试,计算结果:2 ******
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

西河刘卡车医

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

标签云

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