写点什么

涨薪神作!美团大佬带你揭秘 Spring AOP 的底层原理及实现,限时领

发布于: 2021 年 05 月 29 日
涨薪神作!美团大佬带你揭秘Spring AOP的底层原理及实现,限时领

今日分享开始啦,请大家多多指教~


spring aop 使得我们的 aop 开发工作变得简单,这是众所周知的。今天我们一起揭秘 spring aop 底层原理及实现吧!


AOP 面向切面编程:主要是通过切面类来提高代码的复用,降低业务代码的耦合性,从而提高开发效率。主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。

  • AOP 实现原理:aop 是通过 cglib 的动态代理实现的。

  • jdk 动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。

  • cglib 动态代理:将代理对象类的 class 文件加载进来,通过 ASM 字节码技术修改其字节码生成子类来处理。


区别:JDK 动态代理只能对实现了接口的类生成代理,而不能针对类 。CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。因为是继承,所以该类或方法最好不要声明成 final ,final 可以阻止继承和多态。

使用

要分析 spring aop 的底层原理,首先要会使用,先创建一个普通 maven webapp 项目,引入 spring-context 依赖,版本为 5.1.1RELEASE。

然后我使用 aspectj 作为 aop 的语法实现,和 spring 整合起来。

接下来我全称用注解的形式来模拟 spring aop 的使用,先创建一个配置类去扫描包,开启 aspectJ 的自动代理支持。

然后新建一个接口和接口的实现类:

创建切面:

创建测试方法:

执行方法,可以看到在打印 query…之前打印了 before----------

这个时候我们很想知道为什么这句 before 会打印在 query 之前呢,稍微对 spring aop 有所了解的人应该知道,spring 是通过动态代理和字节码技术来实现 aop 操作的,也就是经常说的 jdk 动态代理和 cglib 动态代理两种模式,那么,spring 究竟是怎么创建的代理对象,又是什么时候产生的代理对象呢,下面我们来一起探究一下源码,来揭开这个谜底。

源码分析

首先我们透过现象看本质,我先把断点打在测试方法的最后一行,我们来看这个时候的 dao 对象。

那么接下来我们就要去找到什么时候这个 dao 对象变成了动态代理对象的,既然在最后一行的时候对象已经变成了代理对象,那么我门自然就猜想是在上一句代码的位置 spring 执行了创建代理对象的操作,我们把断点移到上一行,debug 进去。

这行代码我看方法名觉得应该是有用的代码,方法意思应该是 spring 处理好的 bean,跟进去看看。



执行完 getBeanNamesForType(requiredType)后,我们看 idea 的变量显示,果然有一个 bean,name 是 IndexDao。

那么接下来自然会进到 length==1 的那个代码块,这时候我再 debug 进入,这里还是一个 getBean 方法。


在 spring 容器中还有一些没有 name 的其他的 bean 需要被创建,所以这里我用上了条件断点,当 beanName 等于 indexDao 的时候,才会进入断点,但是当我 F8 跑完这行代码的时候,出乎意料的事情发生了。

惊不惊喜,意不意外,getSingleton 这行代码执行结束之后,代理对象就已经被创建了,所以需要 debug 进入这行代码去看。

但是我在这里只看到了 get 方法,那么这些 bean 是什么时候放到 singletonObjects 里的呢,我来找找。

在 DefaultSingletonBeanRegistry 注册器中,我找到了 singletonObjects.put 方法,代表 bean 是这个时候被放到这个 map 里去的,接下来我在这行代码上进行条件断点,然后我们来看它的调用链,找出是什么时候执行的 addSingleton 方法,其实从这个时候我已经知道,断点打在测试方法的倒数第二行是不对的,在 getBean 之前其实代理对象已经产生了。


在 createBean 方法上,我也加上条件断点,然后 debug 进入。


接下来我 debug 进入 doCreateBean 方法





debug 跟进 initializeBean 方法,条件断点在两个初始化处理器上,我隐约觉得代理对象就是从这两个方法中产生的,我们拭目以待。

执行完 applyBeanPostProcessorsBeforeInitialization 方法,这个时候我们看到 warppedBean 还是 indexDao,并没有产生代理对象。

我猜想在下一个后置处理器中,代理对象将被创建,我 debug 进去。


看到这个处理器,我豁然开朗,应该就是经过这个处理器的处理产生的代理对象了,跑完这段代码来验证一下我的猜想。

可以看到我的猜想被证明是正确的,运行完这个后置处理器,代理对象就被创建出来了。 到了这里我们知道了代理对象是从哪里来的了,但是还是没搞清楚代理对象是怎么创建出来的,这时候我们就需要 debug 进入到这个处理器内部去瞧瞧了。

于是乎我又进到了 wrapIfNecessary 这个方法内部



我们看到这里有一个 if 语句,当 config 中的 isOptimize 和 isProxyTargetClass 还有 hasNoUserSuppliedProxyInterfaces 三个判断条件只要有一个满足的话,spring 就会选择 cglib 的方式进行动态代理,而 config 中的两个 boolean 变量的默认值都是 false,而我们的被代理对象又是实现接口的,所以 spring 会选择 jdk 动态代理的实现形式来完成动态代理。

当然,我们也可以在这种情况下手动的配置 config 值来让 spring 选择 cglib 作为动态代理的实现方式,稍后会演示。


来演示一下怎么修改 config 让 spring 在有接口的情况下选择 cglib 作为动态代理的实现方式,其实很简单,在配置类的这个注解后加上 proxyTargetClass=true 就可以了。

@EnableAspectJAutoProxy(proxyTargetClass = true)

总结

我以 spring aop 实现的调用链图来结束这次的总结

今日份分享已结束,请大家多多包涵和指点!

用户头像

还未添加个人签名 2021.04.20 加入

Java工具与相关资料获取等WX: pfx950924(备注来源)

评论

发布
暂无评论
涨薪神作!美团大佬带你揭秘Spring AOP的底层原理及实现,限时领