Spring – AOP(10)

  • 2020 年 3 月 17 日
  • 筆記

编写一个简单的需求:要求在程序执行期间追踪正在发生的活动

// 接口  public interface AtithmeticCalculator {      int add(int i,int j);      int sub(int i,int j);      int mul(int i,int j);      int div(int i,int j);  }
// 实现  public class AtithmeticCalculatorImpl implements AtithmeticCalculator {      @Override      public int add(int i, int j) {          System.out.println("The method add begins with [" + i + "," + j + "]");          int result = i + j;          System.out.println("The method add ends with " + result);          return result;      }      @Override      public int sub(int i, int j) {          System.out.println("The method add begins with [" + i + "," + j + "]");          int result = i - j;          System.out.println("The method add ends with " + result);          return result;      }      @Override      public int mul(int i, int j) {          System.out.println("The method add begins with [" + i + "," + j + "]");          int result = i * j;          System.out.println("The method add ends with " + result);          return result;      }      @Override      public int div(int i, int j) {          System.out.println("The method add begins with [" + i + "," + j + "]");          int result = i / j;          System.out.println("The method add ends with " + result);          return result;      }  }
// Main  public class Main {      public static void main(String[] args) {          AtithmeticCalculator atithmeticCalculator = new AtithmeticCalculatorImpl();          int result = atithmeticCalculator.add(1,2);          System.out.println(result);          result = atithmeticCalculator.div(4,2);          System.out.println(result);      }  }  // Output  The method add begins with [1,2]  The method add ends with 3  3  The method add begins with [4,2]  The method add ends with 2  2

上面例子中的实现代码存在的问题:

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

使用动态代理解决上述问题 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及合适将方法调用转到原始对象上

// 接口  public interface ArithmeticCalculator {      int add(int i, int j);      int sub(int i, int j);      int mul(int i, int j);      int div(int i, int j);  }
// 实现类  public class ArithmeticCalculatorImpl implements ArithmeticCalculator {      @Override      public int add(int i, int j) {          int result = i + j;          return result;      }      @Override      public int sub(int i, int j) {          int result = i - j;          return result;      }      @Override      public int mul(int i, int j) {          int result = i * j;          return result;      }      @Override      public int div(int i, int j) {          int result = i / j;          return result;      }  }
// 代理  public class ArithmeticCalculatorLoggingProxy {      // 要代理的对象      private ArithmeticCalculator target;        public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target){          this.target = target;      }      public ArithmeticCalculator getLoggingProxy(){          ArithmeticCalculator proxy = null;          // 代理对象由哪一个类加载器负责加载          ClassLoader loader = target.getClass().getClassLoader();          // 代理对象的类型,即其中有哪些方法          Class [] interfaces = new Class[]{ArithmeticCalculator.class};          // 当调用代理对象其中的方法时,该执行的代码          InvocationHandler h = new InvocationHandler() {              /**               *               * @param proxy 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象               * @param method 正在被调用的方法               * @param args 调用方法时,传入的参数               * @return               * @throws Throwable               */              @Override              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                  String methodName = method.getName();                  // 日志                  System.out.println("The method" + methodName + "begins with " + Arrays.asList(args));                  // 执行方法                  Object result = method.invoke(target,args);                  // 日志                  System.out.println("The method" + methodName + "ends with " + result);                  return result;              }          };          proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);          return proxy;      }  }
// Main  public class Main {      public static void main(String[] args) {          ArithmeticCalculator target = new ArithmeticCalculatorImpl();          ArithmeticCalculator proty = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();          int result = proty.add(1,2);          System.out.println(result);          result = proty.div(8,2);          System.out.println(result);      }  }

使用AOP

AOP面向切面编程,是一种新的方法论,并对传统OOP(面向对象编程)的补充

在应用AOP编程时,仍然需求定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样依赖横切关注点就被模块化到特殊的对象(切面)里

AOP术语

切面(Aspect):横切关注点 (跨越应用程序多个模块的功能)被模块化的特殊对象 通知(Advice):切面必须要完成的工作 目标(Target):被通知的对象 代理(Proxy):向目标对象应用通知之后创建的对象 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前,调用后,方法抛出异常后等。 切点(pointcut):每个类都拥有多个连接点。即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点

Spring中启用AspectJ注解支持

AspectJ:Java社区里最完整最流行的AOP框架

通知是标注有注解的简单的Java方法: @Before 前置通知,在方法执行之前执行 @After 后置通知,在方法执行之后执行 @AfterRunning 返回通知,在方法返回结果之后执行 @AfterThrowing 异常通知,在方法抛出异常之后 @Around环绕通知,环绕着方法执行

// maven注入依赖  <dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-aop</artifactId>    <version>5.1.12.RELEASE</version>  </dependency>    <dependency>    <groupId>org.aspectj</groupId>    <artifactId>aspectjweaver</artifactId>    <version>1.9.4</version>  </dependency>
// ArithmeticCalculator  public interface ArithmeticCalculator {      int add(int i, int j);      int sub(int i, int j);      int mul(int i, int j);      int div(int i, int j);  }
// ArithmeticCalculatorImpl  @Component  public class ArithmeticCalculatorImpl implements ArithmeticCalculator {      @Override      public int add(int i, int j) {          int result = i + j;          return result;      }      @Override      public int sub(int i, int j) {          int result = i - j;          return result;      }      @Override      public int mul(int i, int j) {          int result = i * j;          return result;      }      @Override      public int div(int i, int j) {          int result = i / j;          return result;      }  }
// ArithmeticCalculatorLoggingProxy  public class ArithmeticCalculatorLoggingProxy {      // 要代理的对象      private ArithmeticCalculator target;        public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target){          this.target = target;      }      public ArithmeticCalculator getLoggingProxy(){          ArithmeticCalculator proxy = null;          // 代理对象由哪一个类加载器负责加载          ClassLoader loader = target.getClass().getClassLoader();          // 代理对象的类型,即其中有哪些方法          Class [] interfaces = new Class[]{ArithmeticCalculator.class};          // 当调用代理对象其中的方法时,该执行的代码          InvocationHandler h = new InvocationHandler() {              /**               * @param proxy 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象               * @param method 正在被调用的方法               * @param args 调用方法时,传入的参数               * @return               * @throws Throwable               */              @Override              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                  String methodName = method.getName();                  // 日志                  System.out.println("The method" + methodName + "begins with " + Arrays.asList(args));                  // 执行方法                  Object result = method.invoke(target,args);                  // 日志                  System.out.println("The method" + methodName + "ends with " + result);                  return result;              }          };          proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);          return proxy;      }  }
// LoggingAspect  @Component  @Aspect  public class LoggingAspect {        // 声明该方法是一个前置通知:在目标方法开始之前执行      // @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.add(int, int))") // 只针对add方法      @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.*(int, int))") // 所有方法:add、mul、div、sub      public void beforeMethod(JoinPoint joinPoint){          String methodName = joinPoint.getSignature().getName();          List<Object> args = Arrays.asList(joinPoint.getArgs());          System.out.println("The method " + methodName + " begins with" + args);      }  }
// Main  public class Main {      public static void main(String[] args) {          ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");          ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);          int result = arithmeticCalculator.add(3,6);          System.out.println("result: " + result);          result = arithmeticCalculator.sub(2,1);          System.out.println("result: " + result);          result = arithmeticCalculator.mul(2,1);          System.out.println("result: " + result);          result = arithmeticCalculator.div(6,3);          System.out.println("result: " + result);      }  }

前置通知

在方法执行之前执行的通知,使用@Before注解,并将切入点表达式的值作为注解值

编写AspectJ切入点表达式

通过方法的签名来匹配各种方法: execution * com.sangyu.test10.ArithmeticCalculator.*(...) 匹配ArithmeticCalculator中声明的所有方法,第一个代表任意修饰符及任意返回值,第二个 代表任意方法,…表示匹配任意数量的参数(若目标类于接口与该切面在同一个包中,可以省略包名)

execution public * com.sangyu.test10.ArithmeticCalculator.*(...) 匹配ArithmeticCalculator接口的所有共有方法

execution public double com.sangyu.test10.ArithmeticCalculator.*(...) 匹配ArithmeticCalculator中返回double类型数值的方法

execution public double com.sangyu.test10.ArithmeticCalculator.*(double,...) 匹配第一个参数为double类型的方法 …匹配任意数量任意类型的参数

execution public double com.sangyu.test10.ArithmeticCalculator.*(double,double) 匹配参数类型为double,double类型的方法

execution * *,*(...) 执行任意类的任意方法

JoinPoint

通知方法中beforeMethod()中声明一个类型为JoinPoint的参数,可以访问链接细节,如方法名称和参数值

后置通知

在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止,一个切面可以包括一个或者多个通知

@Component  @Aspect  public class LoggingAspect {      // 声明该方法是一个前置通知:在目标方法开始之前执行       @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.add(int, int))") // 只针对add方法  //    @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.*(int, int))") // 所有方法:add、mul、div、sub      public void beforeMethod(JoinPoint joinPoint){          String methodName = joinPoint.getSignature().getName();          List<Object> args = Arrays.asList(joinPoint.getArgs());          System.out.println("The method " + methodName + " begins with" + args);      }      // 后置通知:在目标方法执行后(无论是否发生异常),执行的通知      @Before("execution(public int com.sangyu.test10.ArithmeticCalculatorImpl.*(int, int))") // 所有方法:add、mul、div、sub      public void afterMethod(JoinPoint joinPoint){           String methodName = joinPoint.getSignature().getName();          System.out.println("The method " + methodName + " end");      }  }