我记得之前写过Spring AOP相关的文章,但是最近在观看Cat源代码的时候发现 @Aspect 这个注解与AspectJ这个项目,查阅了不少博客,感觉还是云里雾里,这篇博客就是基于博客以及自己实际实验与测试搞懂 Spring AOP和 AspectJ之间的关系
AOP是什么 #
网上的资料很多,我就不写那些烂大街的总结了,其实很简单,我们先了解一下Python的装饰器
我们定义一个装饰器
def cost(f):
def _real(*arg, **kward):
start = time.time()
reslt = f(*arg, **kward)
end = time.time()
print(‘cost’, end - start, ‘ seconds’)
Return _real
然后我们可以将这个装饰器放在任何方法上
@cost
def run():
print(“run")
我们运行任何代码都可以使用这个装饰器,其实AOP的核心要求也就是这个,只要我定义了一个方法,我能让我想让的函数都能执行这个,这就叫解耦,Python怎么实现这个呢,其实很简单,一个语法糖,编译器会把这个cost函数重新定义为一个新函数,具体原理我就不介绍了,我就把拆解后的代码写出来吧
def _run():
print(“run”)
def run():
return cost(_run)()
然后在Java中虽然也有@
这个用法,但是那个是注解,它的作用就是一个文档,我们可以反编译有注解的类,只要这个注解的RetentionPolicy是RetentionPolicy.RUNTIME
它就会在保留在字节码上面
Spring AOP怎么实现 #
那Spring AOP如何实现在字节码上面读取到注解,然后实现AOP功能呢,答案就是反射(这也是为啥Spring启动很慢的原因,反射太慢了)
通过Class.getAnnotation
这个方法,我们可以获取到类上面所有的注解,Spring 作为Bean工厂,对应所有的Bean它都会去读取它的注解,根据注解来灵活组合功能
拿到注解后,如何实现把你想切入的代码插入到这个被注解到类上面呢。Java是一个面对对象的语言,它要想改变类的方法,只能通过继承
继承有两张实现方式,假如类实现了接口,则使用JDK动态代理方式,即该类的newProxyInstance
方法,第二种就是使用CGLib动态代理,自动生成类的字节码
为什么能实现呢,因为所有的bean都是Spring来控制生成的。因为获取bean的时候,我们也能知道这个bean的类名,所以Spring 也提供了通过类名来控制切面,这就是自己来实现的好处,Python的装饰器只有被装饰的类才有用,而Spring由于自己实现了这套逻辑,所以它其实也支持通过类名来进行AOP
Spring AOP的局限性 #
前面说到Spring AOP的原理,我们提到了一个关键就是被代理的都只能是bean
,这就带来一个局限性,对应非bean的类,我们无法代理,不信你可以试试在Spring Boot里面切入一个非bean的三方如org.apache.ibatis.executor.BaseExecutor
那对应这种非Spring Bean的没法做到了吗,这里就要提到一个框架 AspectJ
这个就是AOP的鼻祖,Spring其实切面名字也叫Aspect
,虽然实现不是用的AspectJ,但是用了它的注解类,这个框架强在哪呢,首先它提供了两种AOP方式
编译时候,修改编译的字节码,或者编译后增强
运行的时候,通过agent修改字节码
编译时候修改字节码,我们比较熟悉的lombok就是这样的,是通过jdk1.6 javax.annotation.processing.Processor
引入的一种agent插入方式,通过SPI技术,你只要实现了这个类即可修改字节码
编译后修改字节码是通过 JDK1.5 提供的 java.lang.instrument.Instrumentation
来实现的,原理其实都很简单,使用SPI进行替换增强,感兴趣可以根据资料自己实操一下
AspectJ和Spring的关系 #
AspectJ 对应Spring 来说只是提供了注解类,Spring用代理类或者CGLIB实现了自己的逻辑
总结 #
之所以写这篇博文,主要是在学习JVM的时候发现对于JAVA的注解,Spring如何实现的自己不是很清楚,所以顺藤摸瓜,把这条链路给整明白,当然还有很多细节还没有深挖,比如AspectJ是怎么将切面代码进行字节码转编译,它是如何做到不出现循环依赖的
要想深刻理解AspectJ和Spring实现原理,非常推荐使用Arthas使用jad将运行的类给反编译下来,通过这种,你就不会停留在网上各种花里胡哨的文字上面,Spring的确是一个很牛逼的框架,封装了很多东西,定义了很多规范,要想理解这些,必须深入到源代码层次
AspectJ原理
https://blog.csdn.net/qq_40378034/article/details/115281684
Spring Boot AOP 原理
https://juejin.cn/post/6844903721101426701
https://blog.mythsman.com/post/5d301cf2976abc05b34546be/
Lombok原理