Spring AOP源码深度解析:原来动态代理是这样玩的,颠覆三观!
那天晚上十一点 监控突然狂报警。
CPU飙到98% 内存也快爆了。排查半天发现竟然是AOP切面里的一个小小逻辑 导致了代理对象无限递归调用。当时我就想 这玩意儿到底是怎么工作的呢?
说起Spring AOP 大家都知道是基于动态代理实现的。可你真的了解它背后的门道吗?
01
先说个扎心的事实吧。
很多人以为Spring AOP就是简单的JDK动态代理加CGLIB 其实这想法too naive了。Spring在这里面玩了很多花样 设计得相当精妙。
我们先看看最核心的ProxyFactory这个类。它就像一个代理工厂的总指挥 负责决定到底用哪种代理方式。
1publicclassProxyFactoryextendsProxyCreatorSupport{
2
3public Object getProxy(){
4return createAopProxy().getProxy();
5 }
6
7protectedfinalsynchronized AopProxy createAopProxy(){
8if (!this.active) {
9 activate();
10 }
11return getAopProxyFactory().createAopProxy(this);
12 }
13}
看起来很简单对吧?
关键在于createAopProxy()这个方法。它会根据配置决定是用JdkDynamicAopProxy还是CglibAopProxy。
02
这里有个很多人不知道的细节。
Spring判断用哪种代理的逻辑并不是我们想象的那么简单。不是说”有接口就用JDK 没接口就用CGLIB”这么粗暴。
实际的判断逻辑在DefaultAopProxyFactory里:
1public AopProxy createAopProxy(AdvisedSupport config){
2if (config.isOptimize() || config.isProxyTargetClass()
3 || hasNoUserSuppliedProxyInterfaces(config)) {
4// 使用CGLIB
5returnnew CglibAopProxy(config);
6 } else {
7// 使用JDK动态代理
8returnnew JdkDynamicAopProxy(config);
9 }
10}
你看 除了接口这个因素 还有optimize和proxyTargetClass两个开关在影响选择呢。
当年我就在这个地方踩过坑。
03
现在来看最有意思的部分了。
JDK动态代理的实现原理大家都知道 但Spring对它的封装相当巧妙。核心逻辑在JdkDynamicAopProxy.invoke()方法里:
1public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
2 MethodInvocation invocation;
3 Object oldProxy = null;
4boolean setProxyContext = false;
5
6 TargetSource targetSource = this.advised.targetSource;
7 Object target = targetSource.getTarget();
8
9try {
10// 获取拦截器链
11 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
12
13if (chain.isEmpty()) {
14// 没有切面直接调用原方法
15 Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
16 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
17 } else {
18// 创建方法调用对象
19 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
20 retVal = invocation.proceed();
21 }
22 }
23}
这段代码的精髓在于拦截器链的设计。
Spring把所有的切面通知都转换成了MethodInterceptor 然后用责任链模式串起来。每个拦截器都可以决定是否继续执行下一个拦截器 或者直接返回结果。
04
说到责任链 不得不提ReflectiveMethodInvocation.proceed()这个方法了。
这玩意儿的实现简直是教科书级别的:
1public Object proceed()throws Throwable {
2// 拦截器链执行完了 调用目标方法
3if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
4return invokeJoinpoint();
5 }
6
7// 获取下一个拦截器
8 Object interceptorOrInterceptionAdvice =
9this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
10
11if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
12// 动态匹配
13 InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
14if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
15return dm.interceptor.invoke(this);
16 } else {
17return proceed(); // 递归调用
18 }
19 } else {
20// 直接调用拦截器
21return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
22 }
23}
看懂了吗?
这就是一个递归调用的过程。每个拦截器都会调用invocation.proceed() 而这个方法又会触发下一个拦截器的执行。
开头我提到的那个无限递归问题 就是因为某个切面的逻辑有误 导致拦截器链陷入了死循环。
05
最后说个很少人注意到的点。
Spring AOP还有个TargetSource的概念 这个设计相当巧妙。它不是直接持有目标对象的引用 而是通过TargetSource来获取目标对象。
这样做的好处是什么呢?
可以实现各种高级特性 比如对象池、懒加载、热替换等等。SingletonTargetSource、PrototypeTargetSource、HotSwappableTargetSource 每种都有不同的用途。
当年Spring团队设计这套架构的时候 真的是把扩展性考虑得相当周全了。
从一个简单的动态代理 到复杂的切面编程框架 中间涉及的设计模式和思考真的是值得我们深入学习的。
说白了 技术的魅力不就在于这些精妙的设计和巧思吗?
夜雨聆风
