zoukankan      html  css  js  c++  java
  • AOP 面向切面的编程

      一、面向切面的编程需求的产生

      1. 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
      2. 代码分散: 以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。

      二、实现面向切面的编程

      1. 将需要实现AOP的类注入到Spring容器中,例如:
         1 package com.neuedu.aop;
         2 
         3 import org.springframework.stereotype.Component;
         4 
         5 @Component
         6 public class RawCaculator implements MathCaculator{
         7 
         8     @Override
         9     public int add(int i, int j) {
        10         int rs=i+j;
        11         System.out.println(i+"+"+j+"="+rs);
        12         return rs;
        13     }
        14 
        15     @Override
        16     public int sub(int i, int j) {
        17         int rs=i-j;
        18         System.out.println(i+"-"+j+"="+rs);
        19         return rs;
        20     }
        21 
        22     @Override
        23     public int mul(int i, int j) {
        24         int rs=i*j;
        25         System.out.println(i+"*"+j+"="+rs);
        26         return rs;
        27     }
        28 
        29     @Override
        30     public int div(int i, int j) {
        31         int rs=i/j;
        32         System.out.println(i+"/"+j+"="+rs);
        33         return rs;
        34     }
        35 
        36 }
        要实现AOP的计算方法类
      2. 实现切面类
        1. 使用前置通知、后置通知、返回通知、异常通知实现切面类,注入到Spring容器中
           1 package com.neuedu.aop;
           2 
           3 import static org.hamcrest.CoreMatchers.nullValue;
           4 
           5 import java.util.Arrays;
           6 import java.util.List;
           7 
           8 import org.aspectj.lang.JoinPoint;
           9 import org.aspectj.lang.ProceedingJoinPoint;
          10 import org.aspectj.lang.Signature;
          11 import org.aspectj.lang.annotation.After;
          12 import org.aspectj.lang.annotation.AfterReturning;
          13 import org.aspectj.lang.annotation.AfterThrowing;
          14 import org.aspectj.lang.annotation.Around;
          15 import org.aspectj.lang.annotation.Aspect;
          16 import org.aspectj.lang.annotation.Before;
          17 import org.springframework.core.annotation.Order;
          18 import org.springframework.stereotype.Component;
          19 
          20 @Component
          21 //@Aspect表明当前类是一个切面类
          22 @Aspect
          23 //@Order表示切面执行顺序,value值越小优先级越高
          24 @Order(value=50)
          25 public class CaculatorAspect {
          26         @Before(value = "execution(public int com.neuedu.aop.RawCaculator.*(int, int))")
          27         public void showBeginLog(JoinPoint point){
          28             //System.out.println("【日志】【前置通知】");
          29             //获得参数列表:
          30             Object[] args = point.getArgs();
          31             List<Object> asList = Arrays.asList(args);
          32             //获得方法名:
          33             Signature signature = point.getSignature();
          34             String name = signature.getName();
          35             System.out.println("【日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);
          36         }
          37         @AfterThrowing(value = "execution(public int com.neuedu.aop.RawCaculator.*(..))" ,throwing="ex")
          38         public void showThrowing(JoinPoint point,Exception ex){
          39             //System.out.println("【日志】【异常通知】");
          40             System.out.println("【日志】【异常通知】 异常信息:"+ex);
          41         }
          42         @After(value = "execution(public int com.neuedu.aop.RawCaculator.*(..))")
          43         public void showAfter(){
          44             System.out.println("【日志】【后置通知】");
          45         }
          46         @AfterReturning(value = "execution(* * .*(..))",returning="result")
          47         public void showAfterReturning(JoinPoint point,Object result){
          48             //System.out.println("【日志】【返回通知】");
          49             System.out.println("【日志】【返回通知】 目标方法的返回值为"+result);
          50         }
          51     }

               使用黄色背景标注的代码是声明的通知,其中包括通知类型切入点表达式,以下就是对通知类型的介绍,切入点表达式在最后

                @Before  前置通知:在方法执行之前执行的通知

                @After 后置通知:后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候

                @AfterReturning   返回通知:

            • 无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。
            • 在返回通知中访问连接点的返回值:
              • 在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称
              • 必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值
              • 原始的切点表达式需要出现在pointcut属性中 

                @AfterThrowing 异常通知:只在连接点抛出异常时才执行异常通知.:

          • 将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。
              • 如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行

               

              2.使用环绕通知实现切面类,注入到Spring容器中

     1   package com.neuedu.aop;
     2 
     3   import java.util.Arrays;
     4   import java.util.List;
     5 
     6   import org.aspectj.lang.ProceedingJoinPoint;
     7   import org.aspectj.lang.Signature;
     8   import org.aspectj.lang.annotation.Around;
     9   import org.aspectj.lang.annotation.Aspect;
    10   import org.springframework.core.annotation.Order;
    11   import org.springframework.stereotype.Component;
    12 
    13   @Component
    14   //@Aspect表明当前类是一个切面类
    15   @Aspect
    16   @Order(value=40)
    17     public class secondAspect {
    18       @Around(value = "execution(* * .*(..))")
    19       public Object Around(ProceedingJoinPoint point){
    20           Object result=null;
    21           Object[] args = point.getArgs();
    22           List<Object> asList = Arrays.asList(args);
    23           Signature signature = point.getSignature();
    24           String name = signature.getName();
    25           try{
    26               System.out.println("【事物日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);
    27               try {
    28                   result=point.proceed(args);
    29                 } finally{
    30                     System.out.println("【事物日志】【后置通知】");
    31                 }    
    32               System.out.println("【事物日志】【返回通知】 目标方法的返回值为"+result);    
    33           }catch (Throwable e) {
    34               System.out.println("【事物日志】【异常通知】 异常信息:"+e);
    35           }
    36           return result;
    37       }
    38   }

                @Around  环绕通知:

            • 环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
            • 对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。

            • 在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。

            • 注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。

         

           3.实现测试类

    package junit.test;
    
    import static org.junit.Assert.*;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.neuedu.aop.MathCaculator;
    import com.neuedu.aop.RawCaculator;
    
    public class TestCaculator {
    
        @Test
        public void test() {
            ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
            //MathCaculator bean = ioc.getBean(RawCaculator.class);   不可用 (代表类 类型不一样)
            //rawCaculator即使使用注解时代表类的id;又是xml配置文件里的id
            MathCaculator bean = (MathCaculator) ioc.getBean("rawCaculator");
            bean.add(10, 5);
            System.out.println();
            bean.sub(14, 5);
            System.out.println();
            bean.mul(8, 7);
            System.out.println();
            bean.div(10, 0);
        }
    
    }
    测试类

      

      三、关于切入点表达式

        1.作用:

          通过表达式的方式定位一个或多个具体的连接点。

        2.语法细节:

          1)切入点表达式的语法格式:execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

          2)举例说明:

              

    表达式

    execution(* com.atguigu.spring.ArithmeticCalculator.*(..))

    含义

    ArithmeticCalculator接口中声明的所有方法。

    第一个“*”代表任意修饰符及任意返回值。

    第二个“*”代表任意方法。

    “..”匹配任意数量、任意类型的参数。

    若目标类、接口与该切面类在同一个包中可以省略包名。

    表达式

    execution(public * ArithmeticCalculator.*(..))

    含义

    ArithmeticCalculator接口的所有公有方法

    表达式

    execution(public double ArithmeticCalculator.*(..))

    含义

    ArithmeticCalculator接口中返回double类型数值的方法

    表达式

    execution(public double ArithmeticCalculator.*(double, ..))

    含义

    第一个参数为double类型的方法。

    “..” 匹配任意数量、任意类型的参数。

    表达式

    execution(public double ArithmeticCalculator.*(double, double))

    含义

    参数类型为double,double类型的方法

        3.重用切入点:

          1)

        • 在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现

        • 在Aspect切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的。 
        • 切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。  

          2)示例代码:

     1   @Component
     2   //@Aspect表明当前类是一个切面类
     3   @Aspect
     4   //@Order表示切面执行顺序,value值越小优先级越高
     5   @Order(value=50)
     6   public class CaculatorAspect {
     7           //重用切入点
     8           @Pointcut("execution(* * .*(..))")
     9           private void LoggingOperation(){
    10               
    11           }
    12           @Before(value = "LoggingOperation()")
    13           public void showBeginLog(JoinPoint point){
    14               //获得参数列表:
    15               Object[] args = point.getArgs();
    16               List<Object> asList = Arrays.asList(args);
    17               //获得方法名:
    18               Signature signature = point.getSignature();
    19               String name = signature.getName();
    20               System.out.println("【日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);
    21           }
    22           @AfterThrowing(value = "LoggingOperation()" ,throwing="ex")
    23           public void showThrowing(JoinPoint point,Exception ex){
    24               System.out.println("【日志】【异常通知】 异常信息:"+ex);
    25           }
    26           @After(value = "LoggingOperation()")
    27           public void showAfter(){
    28               System.out.println("【日志】【后置通知】");
    29           }
    30           @AfterReturning(value = "LoggingOperation()",returning="result")
    31           public void showAfterReturning(JoinPoint point,Object result){
    32               System.out.println("【日志】【返回通知】 目标方法的返回值为"+result);
    33           }
    34       }

     



  • 相关阅读:
    全链路压测(1):认识全链路压测
    碎片式的技术笔记
    聊聊传统压测和全链路压测的区别
    生产全链路压测常态化方案
    C++选择文件打开方式的函数
    常用的一些 git 命令
    MyBatis 批量插入数据的 3 种方法!
    MyBatis Plus 批量数据插入功能,yyds!
    什么是可中断锁?有什么用?怎么实现?
    1.3w字,一文详解死锁!
  • 原文地址:https://www.cnblogs.com/alternative/p/7449961.html
Copyright © 2011-2022 走看看