乐于分享
好东西不私藏

Spring AOP源码深度解析:原来动态代理是这样玩的,颠覆三观!

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}

你看 除了接口这个因素 还有optimizeproxyTargetClass两个开关在影响选择呢。

当年我就在这个地方踩过坑。

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来获取目标对象。

这样做的好处是什么呢?

可以实现各种高级特性 比如对象池、懒加载、热替换等等。SingletonTargetSourcePrototypeTargetSourceHotSwappableTargetSource 每种都有不同的用途。

当年Spring团队设计这套架构的时候 真的是把扩展性考虑得相当周全了。

从一个简单的动态代理 到复杂的切面编程框架 中间涉及的设计模式和思考真的是值得我们深入学习的。

说白了 技术的魅力不就在于这些精妙的设计和巧思吗?

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Spring AOP源码深度解析:原来动态代理是这样玩的,颠覆三观!

评论 抢沙发

8 + 2 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮