相关推荐recommended
【Spring】一文带你吃透AOP面向切面编程技术(下篇)
作者:mmseoamin日期:2024-02-20

【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第1张

个人主页: 几分醉意的CSDN博客_传送门

上节我们介绍了什么是AOP、Aspectj框架的前置通知@Before传送门,这篇文章将继续详解Aspectj框架的其它注解。

文章目录

  • 💖Aspectj框架介绍
    • ✨JoinPoint通知方法的参数
    • ✨后置通知@AfterReturning
    • ✨环绕通知@Around
    • ✨异常通知@AfterTrowing
    • ✨@Pointcut定义和管理切入点注解
    • ✨总结
    • 💖投票传送门(欢迎伙伴们投票)

      💖Aspectj框架介绍

      AOP技术思想的实现:使用框架实现AOP。实现AOP的框架有很多。有名的两个

      1. Spring:Spring框架实现AOP思想中的部分功能。Spring框架实现AOP的操作比较繁琐,笨重。

      2. Aspectj:独立的框架,专门做AOp的,功能最强大的。属于Eclipse。

      而我下面主要介绍的就是Aspectj框架来实现Aop,Aspectj框架可以使用注解和xml配置文件两种方式实现AOP。

      ✨JoinPoint通知方法的参数

      切面类中的通知方法,可以有参数,但是必须是JoinPoint。

      JoinPoint: 表示正在执行的业务方法。 相当于反射中 Method

      使用要求:必须是参数列表的第一个

      作用:获取方法执行时的信息,例如方法名称, 方法的参数集合

      下面我们直接实战,注意下面用的是上一节的前置通知的业务接口和实现类。

      切面类

      @Aspect
      public class MyAspect {
      	@Before(value = "execution(* *..SomeServiceImpl.do*(..) )")
      	    public void myBefore2(JoinPoint jp){
      	        //获取方法的定义
      	        System.out.println("前置通知中,获取目标方法的定义:"+ jp.getSignature());
      	        System.out.println("前置通知中,获取方法的名称"+jp.getSignature().getName());
      	        //获取方法执行时的参数
      	        Object[] args = jp.getArgs(); //返回的是一个数组 里面存放的是所有参数
      	        for (Object arg : args) {
      	            System.out.println("前置通知,获取方法的参数是"+arg);
      	        }
      	
      	        //切面的代码。
      	        System.out.println("===前置通知,切面的功能,在目标方法之前先执行==:"+new Date());
      	        System.out.println("");
      	    }
      	}
      

      测试

      @Test
          public void test(){
              //如果没有加入代理的处理:
              // 1)目标方法执行时,没有切面功能的。
              // 2) service对象没有被改变
              //加入代理的处理:
              // 1)目标方法执行时,有切面功能的。
              // 2) service对象是改变后的 代理对象 com.sun.proxy.$Proxy8
              String s = "applicationContext.xml";
              ApplicationContext ctx = new ClassPathXmlApplicationContext(s);
              SomeService service = (SomeService)ctx.getBean("someService");
              service.doSome("ll" , 22);
          }
      
      	//JoinPoint:哪个目标对象方法执行时,它就代表哪个方法
      	//例如这里doSome执行时,它就代表doSome,然后可以获取这个方法的信息
      

      执行结果:

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第2张

      拓展:

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第3张

      ✨后置通知@AfterReturning

           * @AfterReturning:后置通知
           *    属性:value 切入点表达式
           *          returning 自定义的变量,表示目标方法的返回值的。
           *                    自定义变量名称必须和通知方法的形参名一样
           *    位置:在方法的上面
           *
           * 特点:
           *  1.在目标方法之后,执行的。
           *  2.能获取到目标方法的执行结果。
           *  3.不会影响目标方法的执行
           *
           * 方法的参数:
           *   Object res: 表示目标方法的返回值,使用res接收doOther的调用结果。
           *   Object res= doOther();
           *
           *  后置通知的执行顺序
           *  Object res = SomeServiceImpl.doOther(..);  先执行业务方法
           *  myAfterReturning(res); 在执行后置通知
           *
           *  思考:
           *    1 doOther方法返回是String ,Integer ,Long等基本类型,
           *      在后置通知中,修改返回值, 是不会影响目标方法的最后调用结果的。
           *    2 doOther返回的结果是对象类型,例如Student。
           *      在后置通知方法中,修改这个Student对象的属性值,会不会影响最后调用结果?
           *
      

      下面通过举例的方式,带大家理解后置通知。

      首先业务接口添加doOther方法,然后实现它的实现类

      public interface SomeService {
          void doSome(String name,Integer age);
          String  doOther(String name,Integer age);
      }
      
      public class SomeServiceImpl implements SomeService {
          @Override
          public void doSome(String name, Integer age) {
              System.out.println("业务方法doSome(),创建商品订单");
          }
          @Override
          public String doOther(String name, Integer age) {
              System.out.println("执行业务方法doOther,处理库存");
              return "abcd";
          }
      }
      

      创建切面类

      @Aspect
      public class MyAspect {
          //定义方法,表示切面的具体功能
          /*
             后置通知方法的定义
             1)方法是public
             2)方法是void
             3)方法名称自定义
             4)方法有参数,推荐使用Object类型
           */
          
          @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
                  returning = "res")
          public void myAfterReturning(JoinPoint jp , Object res){
              System.out.println("后置通知,在目标方法之后,执行的。能拿到执行结果:"+res);
              //修改目标方法的返回值
              if(res != null){
                  res = "HELLO Aspectj";
              }
              System.out.println("后置通知,修改res后"+res);
          }
      }
      

      测试:

      @Test
          public void test02(){
              String config="applicationContext.xml";
              ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
              SomeService service = (SomeService) ctx.getBean("someService");
              String ret = service.doOther("zhangsan", 20);
              System.out.println("test02中调用目标方法的结果:"+ret);
          }
      

      执行结果:

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第4张

      ✨环绕通知@Around

      特点介绍:

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第5张

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第6张

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第7张

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第8张

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第9张

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第10张

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第11张

           * @Around:环绕通知
           *    属性:value 切入点表达式
           *    位置:在方法定义的上面
           *
           * 返回值:Object ,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)
           * 参数:  ProceedingJoinPoint, 相当于反射中 Method。
           *        作用:执行目标方法的,等于Method.invoke()
           *
           *        public interface ProceedingJoinPoint extends JoinPoint {}
           *
           * 特点:
           *  1.在目标方法的前和后都能增强功能
           *  2.控制目标方法是否执行
           *  3.修改目标方法的执行结果。
           *
      
      @Aspect
      public class MyAspect {
          //定义方法,表示切面的具体功能
          /*
             环绕置通知方法的定义
             1)方法是public
             2)方法是必须有返回值, 推荐使用Object类型
             3)方法名称自定义
             4)方法必须有ProceedingJoinPoint参数,
           */
          @Around("execution(* *..SomeServiceImpl.doFirst(..))")
          public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
              Object methodReturn = null;
              System.out.println("执行了环绕通知,在目标方法之前,输出日志时间=="+ new Date());
              //执行目标方法  ProceedingJoinPoint,表示doFirst
              methodReturn = pjp.proceed();//method.invoke(),表示执行doFirst()方法本身
              if( methodReturn != null){
                  methodReturn ="环绕通知中,修改目标方法原来的执行结果";
              }
              System.out.println("环绕通知,在目标方法之后,增加了事务提交功能");
              //return "HelloAround,不是目标方法的执行结果";
              //返回目标方法执行结果。没有修改的。
              return methodReturn;
          }
      }
      

      测试:

      @Test
          public void test02(){
              String config="applicationContext.xml";
              ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
              SomeService service = (SomeService) ctx.getBean("someService");
              String ret = service.doFirst("zhangsan");
              System.out.println("ret调用目标方法的结果:"+ret);
          }
      

      执行结果:

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第12张

      ✨异常通知@AfterTrowing

      业务接口和实现类:

      public interface SomeService {
          void doSecond(String name);
      }
      
      @Service
      public class SomeServiceImpl implements SomeService {
          
          @Override
          public void doSecond(String name) {
              System.out.println("执行业务方法doSecond,处理库存"+(10/0));
          }
      }
      

      切面类:

      @Aspect
      public class MyAspect {
          //定义方法,表示切面的具体功能
          /*
             异常通知方法的定义
             1)方法是public
             2)方法是没有返回值。是void
             3)方法名称自定义
             4)方法有参数是Exception
           */
          /**
           * @AfterThrowing:异常通知
           *     属性: value 切入点表达式
           *           throwing 自定义变量,表示目标方法抛出的异常。
           *                    变量名必须和通知方法的形参名一样
           *     位置:在方法的上面
           * 特点:
           *  1. 在目标方法抛出异常后执行的, 没有异常不执行
           *  2. 能获取到目标方法的异常信息。
           *  3. 不是异常处理程序。可以得到发生异常的通知, 可以发送邮件,短信通知开发人员。
           *      看做是目标方法的监控程序。
           *
           *  异常通知的执行
           *  try{
           *      SomeServiceImpl.doSecond(..)
           *  }catch(Exceptoin e){
           *      myAfterThrowing(e);
           *  }
           */
          @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
          public void myAfterThrowing(Exception ex){
              System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:"+ex.getMessage());
              /*
                 异常发生可以做:
                 1.记录异常的时间,位置,等信息。
                 2.发送邮件,短信,通知开发人员
               */
          }
      }
      

      测试:

      @Test
          public void test01(){
              String config="applicationContext.xml";
              ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
              SomeService service = (SomeService) ctx.getBean("someService");
              service.doSecond("lisi");
          }
      

      执行结果:【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第13张

      ✨ 最终通知@After

      业务接口和实现类:

      public interface SomeService {
          void doThird();
      }
      
      @Service
      public class SomeServiceImpl implements SomeService {
          @Override
          public void doThird() {
              System.out.println("执行了业务方法doThird()");
          }
      }
      

      切面类:

      @Aspect
      public class MyAspect {
          //定义方法,表示切面的具体功能
          /*
             最终通知方法的定义
             1)方法是public
             2)方法是没有返回值。是void
             3)方法名称自定义
             4)方法没有参数
           */
          /**
           * @After:最终通知
           *    属性: value 切入点表达式
           *    位置: 在方法的上面
           * 特点:
           *  1. 在目标方法之后执行的。
           *  2. 总是会被执行。
           *  3. 可以用来做程序最后的收尾工作。例如清除临时数据,变量。 清理内存
           *
           *  最终通知
           *  try{
           *      SomeServiceImpl.doThird(..)
           *  }finally{
           *      myAfter()
           *  }
           */
          @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
          public void myAfter(){
              System.out.println("最终通知,总是会被执行的");
          }
      }
      

      测试:

          @Test
          public void test01(){
              String config="applicationContext.xml";
              ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
              SomeService service = (SomeService) ctx.getBean("someService");
              
              service.doThird();
          }
      

      执行结果:

      【Spring】一文带你吃透AOP面向切面编程技术(下篇),在这里插入图片描述,第14张

      ✨@Pointcut定义和管理切入点注解

      @Aspect
      public class MyAspect {
          @Before(value = "mypt()")
          public void myBefore(){
              System.out.println("前置通知,在目标方法之前先执行的");
          }
          @After(value = "mypt()")
          public void myAfter(){
              System.out.println("最终通知,总是会被执行的");
          }
          /**
           * @Pointcut: 定义和管理切入点,不是通知注解。
           *     属性: value 切入点表达式
           *     位置: 在一个自定义方法的上面, 这个方法看做是切入点表达式的别名。
           *           其他的通知注解中,可以使用方法名称,就表示使用这个切入点表达式了
           */
          @Pointcut("execution(* *..SomeServiceImpl.doThird(..))")
          private void mypt(){
              //无需代码
          }
      }
      

      ✨总结

      AOP是一种动态的技术思想,目的是实现业务功能和非业务功能的解耦合。业务功能是独立的模块,其他功能也是独立的模块。例如事务功能,日志等等。让这些事务,日志功能是可以被复用的。

      当目标方法需要一些功能时,可以在不修改,不能修改源代码的情况下,使用aop技术在程序执行期间,生成代理对象,通过代理执行业务方法,同时增加功能。

      💖投票传送门(欢迎伙伴们投票)