本文简单的分析了Spring AOP,最初是本人内部培训时使用。虽然用词不严谨,但相信对刚刚接触AOP的人会有一定帮助。
一. AOP的本质——源代码再加工
在很多业务场景中都会涉及到事务,我们希望涉及事务的代码能抽取出来,而不是散落在代码的各个角落。
我们通过以下方式来解决这个问题:
1. 在每个需要事务的位置做一个标记,比如 @Transactional
2. 在打包应用之前,先将源代码喂给 “源代码再加工器”
3. “源代码再加工器” 将输入的源代码作为普通的文本处理,扫描发现@Transactional标记后,直接将该标记替换为 beginTransaction commit等事务相关代码
4. 将“源代码再加工器”输出的文本文件当作源代码进行编译打包
Spring AOP的思路就可以简单理解为以上4步,其本质就是“源代码再加工”, 虽然具体方式和上面有所不同,但思路是完全一样的。
二. 使用AOP
上文已经说明AOP的本质是“源代码再加工”, 那么使用AOP就只要关心2个问题: 在什么位置进行再加工? 进行什么样的再加工?
1. 指定再加工的位置
指定再加工的位置分为2层,第一层叫做Joinpoint, 表示AOP能够支持的位置,比如方法调用处,静态代码初始化块处等。
第二层叫做Pointcut 用于在Joinpoint限定的范围之内,选出用户感兴趣的点。Pointcut用类似正则表达式的方式来表示。
2. 指定进行什么样的再加工
AOP支持的再加工方式有2种,第一种叫做Advice,作用再方法调用层面。比如可以实现在方法调用之前执行某些操作。
第二种叫做Introduction,作用在类层面,可以让已经存在的类实现一个新的接口。比如:已经存在类叫做OldClass,新接口叫做NewInterface,新接口的实现类叫做DefaultImplementation。
读者可以简单的将Introduction理解为: 将DefaultImplementation中的接口实现代码复制到OldClass中,并在OldClass的类声明中加上implements NewInterface.
最后还有一个概念叫做Aspect,用于把上面提到的概念放到一起。Aspect本身没有意义,仅仅是作为一个容器存在。
总结: 使用AOP时,只要用Pointcut指定再加工位置,用Advice或Introduction指定加工内容,最后把他们放到容器Aspect中。
三. 实现AOP
AOP的实现有两种方式:
1. 运行时代理: 在程序运行时,通过生成代理实现
2. load-time weaving: 需要使用专门的编译器,在编译期完成源代码的再加工
这里以Spring的事务为例,分析第一种实现
1. spring 内部会把一个Pointcut和一个Advice放在一起,组成Advisor
2. <tx:annotation-driven> 标签会注册 InfrastructureAdvisorAutoProxyCreator这个BeanPostProcessor
3. InfrastructureAdvisorAutoProxyCreator会获取到所有的Advisor,然后再postProcessBeforeInitialization方法中判断bean是否满足Pointcut指定的条件。
如果满足,则对当前bean应用Advice, 生成代理并替代原始的bean;不满足则跳过
4. 这样就实现了通过代理植入一些额外的代码,也就实现了源代码再加工