事情要从一次奇怪的 bug 说起, 过程大约是这样的, 在一次使用 @Transactional 注解的过程中, 为了分离真正需要事物的代码和其他业务逻辑, 我对代码进行了抽取, 抽取后的代码类似于下面这样:
但是在进行测试时发现虽然加上了事物注解, 但是事物却完全没有生效。 当时可以说是百思不得其解, 只能猜测是因为 aop 是通过 jdk 动态代理方式实现的, 因为commitSomething 方法并未声明在接口上, 自然也就不能被代理, 也就不能实现事物。 为验证这个猜测, 修改 proxyTargetClass=true 切换为 cglib 形式代理, 我们知道, cglib 是通过子类化来实现的代理, 具体生成的代码类似于下面这样:
但是修改为 cglib代理方式后事物依然没有生效。为了弄清楚这个问题, 我做了一些研究,并在这个过程中对 Spring 实现 Aop 的方式做了如下总结:
我们知道 AOP 是Aspect Oriented Programming 面向切面编程的简称,常被我们用作在事物, 日志,安全,缓存等方面。
而 Spring 也实现了 AOP 机制,主要有两种方式 :
1.Proxy base
2.AspectJ
第一种是基于代理方式实现, 比如 JDK Dynamic Proxy 和 Cglib proxy
第二种是基于 AspectJ 框架织入源代码方式实现。
下面我主要讲下 Spring Proxy base AOP 方式的实现。
代理类构成
Spring 默认有两种形式代理 jdk dynamic proxy 和 cglib proxy 在Spring 中这两种形式代理由 AopProxy 负责创建 。AopProxy有两个实现类 JdkDynamicAopProxy 和 CglibAopProxy 继承关系如下图:
选择代理类
Spring 默认使用 jdk dynamic proxy 方式实现 AOP, 当被代理类无接口时 Spring 使用 cglib 方式实现代理。创建 AopProxy 逻辑主要在类DefaultAopProxyFactory中代码如下:
可以看到判断使用哪一种代理实现逻辑是根据 config.isProxyTargetClass 来判断的, 而这个 config 是一个AdvisedSupport 的实例。 AdvisedSupport 是管理Spring AOP 配置的类, 类层次结构如下:
而 Spring 正是通过 ProxyFactory getProxy 来构造的代理。
ProxyFactory 的 isProxyTargetClass 也会在真正创建代理对象时提前计算好。
创建并调用ProxyFacotry在类 AnnotationAwareAspectJAutoProxyCreator 中代码如下:
可以看到 AnnotationAwareAspectJAutoProxyCreator 的 createProxy 主要做了两件事,
1.创建并初始化 ProxyFactory
2.创建代理 bean
代理类的调用与执行
由于 JdkDynamicAopProxy是基于 Java的动态代理,相信大家都比较熟悉, 这里就略过不讲。后文主要讲下 CglibAopProxy 调用与执行。
在前文中可以看到 Spring 是通过 AnnotationAwareAspectJAutoProxyCreator的createProxy方法来构造具有AOP能力的 bean, 这个过程就是设置好代理的拦截器以及一些其他与构造代理相关属性后将创建过程委托给 JdkDynamicAopProxy 或 CglibAopProxy 中具体一个, 再由 JdkDynamicAopProxy 或 CglibAopProxy 调用 getProxy 创建代理 bean。 所以实际我们通过 @Autowired 注解或者从 Spring 上下文中拿到的 bean 就已经是生成好的代理 bean了。 可以 debug 看下, 获取的 bean 的 class 一般是 $Proxy 或 $EnhanceByCGLIB 这样的后缀。
继续看下代理拦截逻辑也就是 AOP 的切面逻辑是如何实现的, 在 CglibAopProxy 构造代理对象时代理拦截逻辑是通过如下代码实现注册:
而 Spring 主要是通过 DynamicAdvisedInterceptor 类实现拦,截继承关系如图:
DynamicAdvisedInterceptor 的 intercept方法实现了具体的拦截逻辑:
可以看到具体实现逻辑首先是获取拦截器链, 再通过 CglibMethodInvocation 来执行拦截器链, 具体再看 CglibMethodInvocation 的 invokeJoinpoint 方法, 具体逻辑如下:
Spring 通过拦截器链对被调用方法进行动态匹配如果匹配上则进行连接器调用,否则继续往后面链路执行,当所有拦截器都被执行后调用
methodProxy.invoke(target, arguments)
执行被代理方法。 至此 Spring Proxy base 代理整个流程基本梳理完毕。
总结
理解完spring 整个 aop 实现逻辑也就可以理解前文 bug, 其实前文中不能应用上事物问题,主要原因是被代理方法的内部方法调用引起的。 因为在方法内部, 作用域就不是 proxy 对象, 而是 target source (被代理源对象), 所以理解了原因解决这个问题自然也就简单了:
主要可以通过
1.AopContext.currentProxy() 获取代理对象
2.从Spring上下文中获取代理对象
3.使用 AspectJ 方式实现 Aop
本文作者:冯剑涛(点融黑帮),主要从事Java 后端开发工作,平时喜欢研究技术,爱好是旅行和跑步。 目前主要负责 crc & ams 系统。