[关闭]
@adamhand 2018-11-12T07:34:07.000000Z 字数 24686 阅读 1094

Spring--AOP


概念

一个例子

首先需要说明的是,AOP是一种思想,它不是Spring特有的,Spring的AOP只是AOP的一种实现方式。

先举个例子,用通俗的语言了解一下什么是AOP。假如我们要实现一个简单计算器,它的功能包括加法、减法、乘法和除法四种。并且要求在实现核心功能代码的同时进行参数验证和日志处理。如下图所示:



可以看到,add()、sub()、mul()和div()四个方法中有很多相同的流程,而AOP的思想就是将这些相同的流程提取出来,让主流程中只包含纯净的业务逻辑代码。所以AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面(图中很形象地表示出了“横向切面”这个概念)。

AOP

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

AOP中的关键概念

  • 连接点(join point):程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理。在 Spring AOP 中, join point 总是方法的执行点, 即只有方法连接点。连接点其实就是一种标志,标志着要在哪里切入。
  • 切入点(pointcut):对连接点进行拦截的定义。
  • 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
  • 切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象。
  • 通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
  • Target(目标对象):代理的目标对象。
  • 织入(weave):将切面应用到目标对象并导致代理对象创建的过程。
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。

下面以一个简单的例子来比喻一下 AOP 中 aspect, jointpoint, pointcut 与 advice 之间的关系.

让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.

来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系. 首先我们知道, 在 Spring AOP 中 join point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 join point, 通过 point cut, 我们就可以确定哪些 join point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, join point 就相当于 爪哇的小县城里的百姓, point cut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问. 为什么可以这样类比呢?

  • join point --> 爪哇的小县城里的百姓: 因为根据定义, join point 是所有可能被织入 advice 的候选的点, 在 Spring AOP 中, 则可以认为所有方法执行点都是 join point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.
  • point cut --> 男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 advice, 但是我们并不希望在所有方法上都织入 advice, 而 pointcut 的作用就是提供一组规则来匹配 joinpoint, 给满足规则的 joinpoint 添加 advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.
  • advice --> 抓过来审问, advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 join point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.
  • aspect: aspect 是 point cut 与 advice 的组合, 因此在这里我们就可以类比: "根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问" 这一整个动作可以被认为是一个 aspect。

参考:
轻松理解AOP思想(面向切面编程)
Spring3:AOP


Spring实现AOP

在Spring中实现AOP可以有两种途径:使用Spring自带的机制实现;使用AspectJ辅助实现。

Spring本身提供了两种方式来生成代理对象: JDKProxyCglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。使用Spring自身的机制实现AOP稍微复杂,下面先演示用AspectJ来实现。

AspectJ实现Spring AOP

使用AspectJ实现AOP也可以分为两种方式:使用注解使用xml文件配置

使用注解

常见AspectJ的注解:

  • @Before – 方法执行前运行
  • @After – 运行在方法返回结果后
  • @AfterReturning – 运行在方法返回一个结果后,在拦截器返回结果。
  • @AfterThrowing – 运行方法在抛出异常后,
  • @Around – 围绕方法执行运行,结合以上这三个通知。

下面的例子使用IDEA建立maven + Java Project项目,比导包方便。

1. 目录结构



2. Spring Beans
CustomerBo和CustomerBoImp中的代码:

  1. package com.aop.CustomerBo;
  2. public interface CustomerBo {
  3. void addCustomer();
  4. String addCustomerReturnValue();
  5. void addCustomerThrowException() throws Exception;
  6. void addCustomerAround(String name);
  7. }
  1. //CustomerBoImp
  2. package com.aop.CustomerBoImp;
  3. import com.aop.CustomerBo.CustomerBo;
  4. public class CustomerBoImp implements CustomerBo {
  5. public void addCustomer(){
  6. System.out.println("addCustomer() is running ");
  7. }
  8. public String addCustomerReturnValue(){
  9. System.out.println("addCustomerReturnValue() is running ");
  10. return "abc";
  11. }
  12. public void addCustomerThrowException() throws Exception {
  13. System.out.println("addCustomerThrowException() is running ");
  14. throw new Exception("Generic Error");
  15. }
  16. public void addCustomerAround(String name){
  17. System.out.println("addCustomerAround() is running, args : " + name);
  18. }
  19. }

3. 启用AspectJ
在 Spring 配置文件,把“<aop:aspectj-autoproxy />”,并定义Aspect(拦截)和普通的bean。

  1. <!--applicationContext.xml-->
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <beans xmlns="http://www.springframework.org/schema/beans"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  6. <aop:aspectj-autoproxy/>
  7. <bean id="customerBo" class="com.aop.CustomerBoImp.CustomerBoImp"/>
  8. <!--Aspect-->
  9. <bean id = "logAspect" class="com.aop.Aspect.LoggingAspect"/>
  10. </beans>

4. Aspect类

  1. //LoggingAspect
  2. package com.aop.Aspect;
  3. import org.aspectj.lang.JoinPoint;
  4. import org.aspectj.lang.ProceedingJoinPoint;
  5. import org.aspectj.lang.annotation.*;
  6. import java.util.Arrays;
  7. @Aspect
  8. public class LoggingAspect {
  9. /**
  10. * 在方法执行前的通知。
  11. * @param joinPoint
  12. */
  13. @Before("execution(* com.aop.CustomerBo.CustomerBo.addCustomer(..))")
  14. public void logBefore(JoinPoint joinPoint) {
  15. System.out.println("logBefore() is running!");
  16. System.out.println("hijacked : " + joinPoint.getSignature().getName());
  17. System.out.println("******");
  18. }
  19. /**
  20. * 在方法执行后的通知,无论方法有没有发生异常。
  21. * @param joinPoint
  22. */
  23. @After("execution(* com.aop.CustomerBo.CustomerBo.addCustomer(..))")
  24. public void logAfter(JoinPoint joinPoint) {
  25. System.out.println("logAfter() is running!");
  26. System.out.println("hijacked : " + joinPoint.getSignature().getName());
  27. System.out.println("******");
  28. }
  29. /**
  30. * 在方法正常执行后的通知。能够访问到方法的返回值。
  31. * @param joinPoint
  32. * @param result
  33. */
  34. @AfterReturning(
  35. pointcut = "execution(* com.aop.CustomerBo.CustomerBo.addCustomerReturnValue(..))",
  36. returning= "result")
  37. public void logAfterReturning(JoinPoint joinPoint, Object result) {
  38. System.out.println("logAfterReturning() is running!");
  39. System.out.println("hijacked : " + joinPoint.getSignature().getName());
  40. System.out.println("Method returned value is : " + result);
  41. System.out.println("******");
  42. }
  43. /**
  44. * 在目标方法出现异常时会执行的代码。
  45. * @param joinPoint
  46. * @param error
  47. */
  48. @AfterThrowing(
  49. pointcut = "execution(* com.aop.CustomerBo.CustomerBo.addCustomerThrowException(..))",
  50. throwing= "error")
  51. public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
  52. System.out.println("logAfterThrowing() is running!");
  53. System.out.println("hijacked : " + joinPoint.getSignature().getName());
  54. System.out.println("Exception : " + error);
  55. System.out.println("******");
  56. }
  57. /**
  58. * 环绕通知需要携带ProceedingJoinPoint类型的参数,
  59. * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。
  60. * @param joinPoint
  61. * @throws Throwable
  62. */
  63. @Around("execution(* com.aop.CustomerBo.CustomerBo.addCustomerAround(..))")
  64. public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
  65. System.out.println("logAround() is running!");
  66. System.out.println("hijacked method : " + joinPoint.getSignature().getName());
  67. System.out.println("hijacked arguments : " + Arrays.toString(joinPoint.getArgs()));
  68. System.out.println("Around before is running!");
  69. joinPoint.proceed(); //continue on the intercepted method
  70. System.out.println("Around after is running!");
  71. System.out.println("******");
  72. }
  73. }

5. App代码

  1. package com.aop;
  2. import com.aop.CustomerBo.CustomerBo;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class App {
  6. public static void main( String[] args ) throws Exception {
  7. ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  8. CustomerBo customerBo = (CustomerBo) applicationContext.getBean("customerBo");
  9. //customerBo.addCustomer();
  10. //customerBo.addCustomerReturnValue();
  11. //customerBo.addCustomerThrowException();
  12. customerBo.addCustomerAround("adamhand");
  13. }
  14. }

6. 执行结果

  1. //语句:前置通知和后置通知
  2. customerBo.addCustomer();
  3. //结果
  4. logBefore() is running!
  5. hijacked : addCustomer
  6. ******
  7. addCustomer() is running
  8. logAfter() is running!
  9. hijacked : addCustomer
  10. ******
  1. //语句:方法返回后的通知
  2. customerBo.addCustomerReturnValue();
  3. //结果
  4. addCustomerReturnValue() is running
  5. logAfterReturning() is running!
  6. hijacked : addCustomerReturnValue
  7. Method returned value is : abc
  8. ******
  1. //语句:异常通知
  2. customerBo.addCustomerThrowException();
  3. //结果
  4. addCustomerThrowException() is running
  5. at com.aop.CustomerBoImp.CustomerBoImp.addCustomerThrowException(CustomerBoImp.java:18)
  6. logAfterThrowing() is running!
  7. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  8. hijacked : addCustomerThrowException
  9. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  10. Exception : java.lang.Exception: Generic Error
  11. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  12. ******
  13. at java.lang.reflect.Method.invoke(Method.java:498)
  14. ...
  15. ******
  1. //语句:环绕通知
  2. customerBo.addCustomerAround("adamhand")
  3. //结果
  4. logAround() is running!
  5. hijacked method : addCustomerAround
  6. hijacked arguments : [adamhand]
  7. Around before is running!
  8. addCustomerAround() is running, args : adamhand
  9. Around after is running!
  10. ******

使用xml文件

AspectJ中注解方式和xml文件方式的对应关系如下:

  • <aop:before> = @Before
  • <aop:after> = @After
  • <aop:after-returning> = @AfterReturning
  • <aop:after-throwing> = @AfterReturning
  • <aop:after-around> = @Around

还是上面的例子,转成xml文件配置方式之后,只需要将LoggerAspect文件中的注解去掉,然后applicationContext.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" xmlns:aop="http://www.springframework.org/schema/aop"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  5. <aop:aspectj-autoproxy/>
  6. <bean id="customerBo" class="com.aop.xml.CustomerBoImp.CustomerBoImp"/>
  7. <!--Aspect-->
  8. <bean id = "logAspect" class="com.aop.xml.Aspect.LoggingAspect"/>
  9. <aop:config>
  10. <aop:aspect id="aspectLoggging" ref="logAspect">
  11. <!-- @Before -->
  12. <aop:pointcut id="pointCutBefore"
  13. expression="execution(* com.aop.xml.CustomerBo.CustomerBo.addCustomer(..))" />
  14. <aop:before method="logBefore" pointcut-ref="pointCutBefore" />
  15. <!-- @After -->
  16. <aop:pointcut id="pointCutAfter"
  17. expression="execution(* com.aop.xml.CustomerBo.CustomerBo.addCustomer(..))" />
  18. <aop:after method="logAfter" pointcut-ref="pointCutAfter" />
  19. <!-- @AfterReturning -->
  20. <aop:pointcut id="pointCutAfterReturning"
  21. expression="execution(* com.aop.xml.CustomerBo.CustomerBo.addCustomerReturnValue(..))" />
  22. <aop:after-returning method="logAfterReturning"
  23. returning="result" pointcut-ref="pointCutAfterReturning" />
  24. <!-- @AfterThrowing -->
  25. <aop:pointcut id="pointCutAfterThrowing"
  26. expression="execution(* com.aop.xml.CustomerBo.CustomerBo.addCustomerThrowException(..))" />
  27. <aop:after-throwing method="logAfterThrowing"
  28. throwing="error" pointcut-ref="pointCutAfterThrowing" />
  29. <!-- @Around -->
  30. <aop:pointcut id="pointCutAround"
  31. expression="execution(* com.aop.xml.CustomerBo.CustomerBo.addCustomerAround(..))" />
  32. <aop:around method="logAround" pointcut-ref="pointCutAround" />
  33. </aop:aspect>
  34. </aop:config>
  35. </beans>

使用Spring自带的工具实现AOP

在Spring AOP中,有 4 种类型通知(advices)的支持:

  • 通知(Advice)之前 - 该方法执行前运行
  • 通知(Advice)返回之后 – 运行后,该方法返回一个结果
  • 通知(Advice)抛出之后 – 运行方法抛出异常后
  • 环绕通知 – 环绕方法执行运行,结合以上这三个通知

使用Spring实现AOP的方式也有两种:经典的基于代理的AOP实现(即不使用<aop:fonfig>等标签);另一种是用纯粹的POJO切面(就是纯粹通过<aop:fonfig>标签配置,也是一种比较简单的方式)。

经典方式

例子在eclipse下面会执行成功,但是在idea下面会报Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy2 cannot be cast to com.aop.self.CustomerService.CustomerService错误,不知道是什么原因。以下程序是使用eclipse编写的

1. 创建被代理类法

  1. package com.yiibai.customer.services;
  2. public class CustomerService
  3. {
  4. private String name;
  5. private String url;
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. public void setUrl(String url) {
  10. this.url = url;
  11. }
  12. public void printName(){
  13. System.out.println("Customer name : " + this.name);
  14. }
  15. public void printURL(){
  16. System.out.println("Customer website : " + this.url);
  17. }
  18. public void printThrowException(){
  19. throw new IllegalArgumentException();
  20. }
  21. }

2. 前置通知

  • 创建一个实现 MethodBeforeAdvice 接口的类。
  • 在 bean 配置文件(applicationContext.xml),创建一个 bean 的 HijackBeforeMethod 类,并命名为“customerServiceProxy” 作为一个新的代理对象。
    • ‘target’ – 定义你想拦截的bean。
    • ‘interceptorNames’ – 定义要应用这个代理/目标对象的类(通知)。
  1. package com.yiibai.aop;
  2. import java.lang.reflect.Method;
  3. import org.springframework.aop.MethodBeforeAdvice;
  4. //before a method is invoke
  5. public class HijackBeforeMethod implements MethodBeforeAdvice
  6. {
  7. @Override
  8. public void before(Method method, Object[] args, Object target)
  9. throws Throwable {
  10. System.out.println("HijackBeforeMethod : Before method hijacked!");
  11. }
  12. }
  1. <bean id="customerService" class="com.yiibai.customer.services.CustomerService" >
  2. <property name="name" value="Yiibai" />
  3. <property name="url" value="http://www.yiibai.com" />
  4. </bean>
  5. <bean id="HijackBeforeMethodBeanAdvice" class="com.yiibai.aop.HijackBeforeMethod" />
  6. <bean id="customerServiceProxy"
  7. class="org.springframework.aop.framework.ProxyFactoryBean">
  8. <property name="target" ref="customerService" />
  9. <property name="interceptorNames">
  10. <list>
  11. <value>customerAdvisor</value>
  12. </list>
  13. </property>
  14. </bean>
  15. <bean id="customerAdvisor"
  16. class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
  17. <property name="mappedName" value="printName" />
  18. <property name="advice" ref="HijackBeforeMethodBeanAdvice" />
  19. </bean>

3. 后置通知
创建一个实现AfterReturningAdvice接口的类。配置文件的写法和上面差不多,只是将HijackBeforeMethod 改为HijackAfterMethod 。

  1. package com.yiibai.aop;
  2. import java.lang.reflect.Method;
  3. import org.springframework.aop.AfterReturningAdvice;
  4. //after a method is invoke
  5. public class HijackAfterMethod implements AfterReturningAdvice
  6. {
  7. @Override
  8. public void afterReturning(Object returnValue, Method method,
  9. Object[] args, Object target) throws Throwable {
  10. System.out.println("HijackAfterMethod : After method hijacked!");
  11. }
  12. }

4. 抛出后通知
它将在执行方法抛出一个异常后。创建一个实现ThrowsAdvice接口的类,并创建一个afterThrowing方法拦截抛出:IllegalArgumentException异常。

  1. package com.yiibai.aop;
  2. import org.springframework.aop.ThrowsAdvice;
  3. public class HijackThrowException implements ThrowsAdvice
  4. {
  5. public void afterThrowing(IllegalArgumentException e) throws Throwable {
  6. System.out.println("HijackThrowException : Throw exception hijacked!");
  7. }
  8. }

5. 环绕通知
它结合了上面的三个通知,在方法执行过程中执行。创建一个实现了MethodInterceptor接口的类。必须调用“methodInvocation.proceed();” 继续在原来的方法执行,否则原来的方法将不会执行。

  1. package com.yiibai.aop;
  2. import java.util.Arrays;
  3. import org.aopalliance.intercept.MethodInterceptor;
  4. import org.aopalliance.intercept.MethodInvocation;
  5. public class HijackAroundMethod implements MethodInterceptor
  6. {
  7. @Override
  8. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  9. System.out.println("Method name : "
  10. + methodInvocation.getMethod().getName());
  11. System.out.println("Method arguments : "
  12. + Arrays.toString(methodInvocation.getArguments()));
  13. //same with MethodBeforeAdvice
  14. System.out.println("HijackAroundMethod : Before method hijacked!");
  15. try{
  16. //proceed to original method call
  17. Object result = methodInvocation.proceed();
  18. //same with AfterReturningAdvice
  19. System.out.println("HijackAroundMethod : Before after hijacked!");
  20. return result;
  21. }catch(IllegalArgumentException e){
  22. //same with ThrowsAdvice
  23. System.out.println("HijackAroundMethod : Throw exception hijacked!");
  24. throw e;
  25. }
  26. }
  27. }

配置文件为:

  1. <bean id="customerService" class="com.yiibai.customer.services.CustomerService" >
  2. <property name="name" value="Yiibai" />
  3. <property name="url" value="http://www.yiibai.com" />
  4. </bean>
  5. <bean id="hijackAroundMethodBeanAdvice" class="com.yiibai.aop.HijackAroundMethod" />
  6. <bean id="customerServiceProxy"
  7. class="org.springframework.aop.framework.ProxyFactoryBean">
  8. <property name="target" ref="customerService" />
  9. <property name="interceptorNames">
  10. <list>
  11. <value>customerAdvisor</value>
  12. </list>
  13. </property>
  14. </bean>
  15. <!--两种写法-->
  16. <!-- <bean id="customerAdvisor"
  17. class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
  18. <property name="mappedName" value="printName" />
  19. <property name="advice" ref="hijackAroundMethodBeanAdvice" />
  20. </bean> -->
  21. <bean id="customerAdvisor"
  22. class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
  23. <property name="patterns">
  24. <list>
  25. <value>.*URL.*</value>
  26. </list>
  27. </property>
  28. <property name="advice" ref="hijackAroundMethodBeanAdvice" />
  29. </bean>

使用<aop:fonfig>标签

1. 定义接口和借口实现类

  1. package com.aop.pojo.Sleepable;
  2. public interface Sleepable {
  3. public void sleep();
  4. }
  1. package com.aop.pojo.SleepImp;
  2. import com.aop.pojo.Sleepable.Sleepable;
  3. public class SleepImp implements Sleepable {
  4. @Override
  5. public void sleep() {
  6. System.out.println("睡觉");
  7. }
  8. }

2. 定义切面类

  1. package com.aop.pojo.SleepHelper;
  2. public class SleepHelper {
  3. public void beforeSleep(){
  4. System.out.println("睡觉前要脱衣服");
  5. }
  6. public void afterSleep(){
  7. System.out.println("起床后要穿衣服");
  8. }
  9. }

3. 配置文件

  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" xmlns:aop="http://www.springframework.org/schema/aop"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  5. <!--定义被代理类-->
  6. <bean id="sleepImp" class="com.aop.pojo.SleepImp.SleepImp"/>
  7. <!--定义通知内容,也就是切入点执行前后需要做的事情-->
  8. <bean id="sleepHelper" class="com.aop.pojo.SleepHelper.SleepHelper"/>
  9. <!--另一种写法-->
  10. <!-- <aop:config>
  11. <aop:aspect ref="sleepHelper">
  12. <aop:before method="beforeSleep" pointcut="execution(* *.sleep(..))"/>
  13. <aop:after method="afterSleep" pointcut="execution(* *.sleep(..))"/>
  14. </aop:aspect>
  15. </aop:config>-->
  16. <aop:config>
  17. <aop:aspect ref="sleepHelper">
  18. <aop:pointcut id="sleepHelpers" expression="execution(* *.sleep(..))" />
  19. <aop:before pointcut-ref="sleepHelpers" method="beforeSleep" />
  20. <aop:after pointcut-ref="sleepHelpers" method="afterSleep" />
  21. </aop:aspect>
  22. </aop:config>
  23. </beans>

4. 主类

  1. public class App
  2. {
  3. public static void main( String[] args )
  4. {
  5. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  6. Sleepable sleepImp = (Sleepable) context.getBean("sleepImp");
  7. sleepImp.sleep();
  8. }
  9. }

注意:
Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。如果被代理的目标实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则使用CGLIB代理。

如果用JDK动态代理,就必须为被代理的目标实现一个接口(要注意的地方是:需要将ctx.getBean()方法的返回值用接口类型接收);如果使用CGLIB强制代理,就必选事先将CGLIB包导入项目,并在配置文件中设置proxy-target-class="true",这个属性决定是基于接口的还是基于类的代理被创建:

  1. <aop:config proxy-target-class="true">
  2. <aop:aspect ref="sleepHelper">
  3. <aop:pointcut id="sleepHelpers" expression="execution(* *.sleep(..))" />
  4. <aop:before pointcut-ref="sleepHelpers" method="beforeSleep" />
  5. <aop:after pointcut-ref="sleepHelpers" method="afterSleep" />
  6. </aop:aspect>
  7. </aop:config>

总结:Spring AOP的实现方式

使用Spring实现AOP方式分为两种:

  • 使用Spring原生的方式:JDKProxy或者CGLib
  • 使用AspectJ:使用注解或者xml

Spring AOP实现原理

下面来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,直接上相关代码:

  1. /**
  2. * <ol>
  3. * <li>获取代理类要实现的接口,除了Advised对象中配置的,还会加上SpringProxy, Advised(opaque=false)
  4. * <li>检查上面得到的接口中有没有定义 equals或者hashcode的接口
  5. * <li>调用Proxy.newProxyInstance创建代理对象
  6. * </ol>
  7. */
  8. public Object getProxy(ClassLoader classLoader) {
  9. if (logger.isDebugEnabled()) {
  10. logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource());
  11. }
  12. Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised);
  13. findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
  14. return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
  15. }

下面的问题是,代理对象生成了,那切面是如何织入的?

我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而通过JdkDynamicAopProxy的签名我们可以看到这个类其实也实现了InvocationHandler,下面我们就通过分析这个类中实现的invoke()方法来具体看下Spring AOP是如何织入切面的。

  1. publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable {
  2. MethodInvocation invocation = null;
  3. Object oldProxy = null;
  4. boolean setProxyContext = false;
  5. TargetSource targetSource = this.advised.targetSource;
  6. Class targetClass = null;
  7. Object target = null;
  8. try {
  9. //eqauls()方法,具目标对象未实现此方法
  10. if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){
  11. return (equals(args[0])? Boolean.TRUE : Boolean.FALSE);
  12. }
  13. //hashCode()方法,具目标对象未实现此方法
  14. if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){
  15. return newInteger(hashCode());
  16. }
  17. //Advised接口或者其父接口中定义的方法,直接反射调用,不应用通知
  18. if (!this.advised.opaque &&method.getDeclaringClass().isInterface()
  19. &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {
  20. // Service invocations onProxyConfig with the proxy config...
  21. return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args);
  22. }
  23. Object retVal = null;
  24. if (this.advised.exposeProxy) {
  25. // Make invocation available ifnecessary.
  26. oldProxy = AopContext.setCurrentProxy(proxy);
  27. setProxyContext = true;
  28. }
  29. //获得目标对象的类
  30. target = targetSource.getTarget();
  31. if (target != null) {
  32. targetClass = target.getClass();
  33. }
  34. //获取可以应用到此方法上的Interceptor列表
  35. List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);
  36. //如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)
  37. if (chain.isEmpty()) {
  38. retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
  39. } else {
  40. //创建MethodInvocation
  41. invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
  42. retVal = invocation.proceed();
  43. }
  44. // Massage return value if necessary.
  45. if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy)
  46. &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
  47. // Special case: it returned"this" and the return type of the method
  48. // is type-compatible. Notethat we can't help if the target sets
  49. // a reference to itself inanother returned object.
  50. retVal = proxy;
  51. }
  52. return retVal;
  53. } finally {
  54. if (target != null && !targetSource.isStatic()) {
  55. // Must have come fromTargetSource.
  56. targetSource.releaseTarget(target);
  57. }
  58. if (setProxyContext) {
  59. // Restore old proxy.
  60. AopContext.setCurrentProxy(oldProxy);
  61. }
  62. }
  63. }

主流程可以简述为:获取可以应用到此方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行joinpoint; 如果没有,则直接反射执行joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。

首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:

  1. public List<Object>getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
  2. MethodCacheKeycacheKey = new MethodCacheKey(method);
  3. List<Object>cached = this.methodCache.get(cacheKey);
  4. if(cached == null) {
  5. cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
  6. this,method, targetClass);
  7. this.methodCache.put(cacheKey,cached);
  8. }
  9. returncached;
  10. }

可以看到实际的获取工作其实是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。

下面来分析下这个方法的实现:

  1. /**
  2. * 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.如果是IntroductionAdvisor,
  3. * 则判断此Advisor能否应用到目标类targetClass上.如果是PointcutAdvisor,则判断
  4. * 此Advisor能否应用到目标方法method上.将满足条件的Advisor通过AdvisorAdaptor转化成Interceptor列表返回.
  5. */
  6. publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) {
  7. // This is somewhat tricky... we have to process introductions first,
  8. // but we need to preserve order in the ultimate list.
  9. List interceptorList = new ArrayList(config.getAdvisors().length);
  10. //查看是否包含IntroductionAdvisor
  11. boolean hasIntroductions = hasMatchingIntroductions(config,targetClass);
  12. //这里实际上注册一系列AdvisorAdapter,用于将Advisor转化成MethodInterceptor
  13. AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
  14. Advisor[] advisors = config.getAdvisors();
  15. for (int i = 0; i <advisors.length; i++) {
  16. Advisor advisor = advisors[i];
  17. if (advisor instanceof PointcutAdvisor) {
  18. // Add it conditionally.
  19. PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor;
  20. if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
  21. //TODO: 这个地方这两个方法的位置可以互换下
  22. //将Advisor转化成Interceptor
  23. MethodInterceptor[]interceptors = registry.getInterceptors(advisor);
  24. //检查当前advisor的pointcut是否可以匹配当前方法
  25. MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher();
  26. if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) {
  27. if(mm.isRuntime()) {
  28. // Creating a newobject instance in the getInterceptors() method
  29. // isn't a problemas we normally cache created chains.
  30. for (intj = 0; j < interceptors.length; j++) {
  31. interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm));
  32. }
  33. } else {
  34. interceptorList.addAll(Arrays.asList(interceptors));
  35. }
  36. }
  37. }
  38. } else if (advisor instanceof IntroductionAdvisor){
  39. IntroductionAdvisor ia =(IntroductionAdvisor) advisor;
  40. if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
  41. Interceptor[] interceptors= registry.getInterceptors(advisor);
  42. interceptorList.addAll(Arrays.asList(interceptors));
  43. }
  44. } else {
  45. Interceptor[] interceptors =registry.getInterceptors(advisor);
  46. interceptorList.addAll(Arrays.asList(interceptors));
  47. }
  48. }
  49. return interceptorList;
  50. }

这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor。

接下来我们再看下得到的拦截器链是怎么起作用的。

  1. if (chain.isEmpty()) {
  2. retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
  3. } else {
  4. //创建MethodInvocation
  5. invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
  6. retVal = invocation.proceed();
  7. }

从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行,来看下具体代码

  1. public Object proceed() throws Throwable {
  2. // We start with an index of -1and increment early.
  3. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) {
  4. //如果Interceptor执行完了,则执行joinPoint
  5. return invokeJoinpoint();
  6. }
  7. Object interceptorOrInterceptionAdvice =
  8. this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
  9. //如果要动态匹配joinPoint
  10. if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){
  11. // Evaluate dynamic method matcher here: static part will already have
  12. // been evaluated and found to match.
  13. InterceptorAndDynamicMethodMatcher dm =
  14. (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
  15. //动态匹配:运行时参数是否满足匹配条件
  16. if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) {
  17. //执行当前Intercetpor
  18. returndm.interceptor.invoke(this);
  19. }
  20. else {
  21. //动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor
  22. return proceed();
  23. }
  24. }
  25. else {
  26. // It's an interceptor, so we just invoke it: The pointcutwill have
  27. // been evaluated statically before this object was constructed.
  28. //执行当前Intercetpor
  29. return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
  30. }
  31. }

参考

Spring AOP四种实现方式Demo详解与相关知识探究
Spring AOP 的实现机制
Spring3:AOP
Spring AOP 实现原理
java.lang.ClassCastException: com.sun.proxy.$Proxy27 cannot be cast to com.bbk.n002.service.QuestionService
Spring—AOP两种代理机制对比(JDK和CGLib动态代理)
Spring AOP通知实例 – Advice

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注