简易的AOP
- 不依赖IoC容器,可独立使用
- 支持Spring AOP和AspectJ
学习方式
- 先把掌握AOP的核心原理实现
- 然后pull dummy-aop到本地,把测试案例都跑一遍,沿着测试案例debug几遍,把流程搞清楚
- 最后,可以尝试把dummy-ioc和dummy-aop两个项目整合起来,得到完整的dummy-spring(通过BeanPostProcessor)
AOP的核心原理简介
这里就不扯什么是AOP以及AspectJ的历史了,在一般大众的认知中,AOP就是在方法的前后做一些事情(Spring默认也就是方法级别的AOP)。
接下来,让我们一步步实现AOP。
这是一个很普通的方法UserService#sayHello。
如何在sayHello前后做一些事情呢?最粗暴的做法是:
但这并不符合开闭原则,也不通用。如果需要在OrderService#order方法前后也做一些事情,就要把代码拷贝一份。
所以,我们必须将sayHello和sayHello前后要做的事情进行分离,而分离的目的是为了下次更好的相聚(组合)。
Spring AOP的做法是,抽象出以下概念:
Pointcut
Advice
Advisor
Pointcut
俗称切点,可以简单理解为“怎么切/切哪里”,通常是一个匹配规则。 比如AspectJ的AspectJExpressionPointcut
,允许我们配置切点表达式:
execution(* com.bravo.test.service.UserService.sayHello(..))
Advice
则是具体的增强逻辑,即前面说的“方法前后要做的事情”。
而Advisor
= Pointcut
+ Advice
。 对于符合Pointcut规则的目标方法应用Advice,以求在目标方法前后做一些事情。
所以,上面拆开的sayHello和sayHello前后要做的事现在要产生联系了:
Spring把这个匹配过程封装在了AdvisorChainFactory中,代码大致如下:
Advisor持有Pointcut,方法匹配成功,则返回advice用于增强当前方法。
尽管在我看来这些Advisor
、Advice
、Pointcut
这些概念已经很清晰,但对于初学者来说可能仍然有点绕。
所以,下面的讲解中,我再做一步简化,只留下Advice的概念,丢弃Pointcut和Advisor(dummy-aop会保留这些概念)。
没有Pointcut怎么知道哪些方法需要增强呢?交给调用者手动组装。比如:
讲到这,对于如何实现AOP应该有一个模糊的概念了。
接下来我们讨论最难的两个问题:
- 如何把advice嵌入目标方法前后
- 如何链式执行advice
所谓AOP,就是在调用目标方法前后额外执行一些逻辑。整个过程大致如下:
Method#invoke负责执行目标方法,额外的一些事情交给Advice去做。
在这里,Spring又抽象出了一个新的概念:MethodInvocation
。它把上面整个流程进行了封装。
即把“Method执行前后需要额外做一些操作”这个过程抽象成MethodInvocation(method和method之前的操作打包在一块)。
Method是Java对方法的抽象,它包含一个方法的所有信息,如名称、参数和返回类型。 而MethodInvocation则是Spring对方法调用的抽象,更偏向动态的概念,调用proceed时会额外执行Advice。
MethodInvocation最重要的实现类是ReflectiveMethodInvocation,其主要成员变量包括:
- Method
- MethodInterceptorChain
想要使用MethodInvocation调用方法,则必然要先经过一串Interceptor。
MethodInterceptor是AOP联盟的,Advice是Spring自己定义的。Spring底层把Advice适配成MethodInterceptor。 MethodInterceptor比Advice更加见名知意,也和MethodInvocation更搭。
接着,我们使用JDK动态代理为userService生成代理对象。代理对象会将一次方法调用委托到目标对象,但在此之前会执行advice:
那么,如何产生链式调用呢?
MethodInvocation就是Filter模式中的FilterChain,持有Interceptor并且负责推进下一个Interceptor。
建议先看简化版的AOP代码:设计模式那些事儿
搞懂以后再看dummy-aop 的代码。