Spring AOP源码分析(二)动态A0P自定义标签
摘要: 本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。
之前讲过Spring中的自定义注解,如果声明了自定义的注解,那么就一定会在程序中的某个地方注册了对应的解析器。我们搜索整个代码,尝试找到注册的地方,全局搜索后我们发现了在AopNamespaceHandler中对应着这样一段函数:
@Override
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); // Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
此处不再对Spring中的自定义注解方式进行讨论。有兴趣的读者可以回顾之前的内容。
我们可以得知,在解析配置文件的时候,一旦遇到aspectj-autoproxy注解时就是使用解析器AspectJAutoProxyBeanDefinitionParser进行解析,那么我们来看一看AspectJAutoProxyBeanDefinitionParser的内部实现。
一、注册AspectJAutoProxyBeanDefinitionParser
所有解析器,因为是对BeanDefinitionParser接口的统一实现,入口都是从parse函数开始的,AspectJAutoProxyBeanDefinitionParser的parse函数如下:
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 注册AnnotationAwareAspectJAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
// 对于注解中子类的处理
extendBeanDefinition(element, parserContext);
return null;
}
其中registerAspectJAnnotationAutoProxyCreatorIfNecessary函数是我们比较关心的,也是关键逻辑的实现。
/**
* 注册AnnotationAwareAspectJAutoProxyCreator
* @param parserContext
* @param sourceElement
*/
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
// 注册或升级AutoProxyCreator定义beanName为org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
// 对于proxy-target-class以及expose-proxy属性的处理
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
// 注册组件并通知,便于监听器做进一步处理
registerComponentIfNecessary(beanDefinition, parserContext);
}
在registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中主要完成了3件事情,基本上每行代码就是一个完整的逻辑。
1. 注册或者升级AnnotationAwareAspectJAutoProxyCreator
对于AOP的实现,基本上都是靠AnnotationAwareAspectJAutoProxyCreator去完成,它可以根据@Point注解定义的切点来自动代理相匹配的bean。但是为了配置简便,Spring使用了自定义配置来帮助我们自动注册AnnotationAwareAspectJAutoProxyCreator,其注册过程就是在这里实现的。
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
@Nullable Object source) { return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
} @Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,
@Nullable Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); // 如果已经存在了自动代理创建器且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底需要使用哪一种
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
// 改变bean最重要的就是改变bean所对应的className属性
apcDefinition.setBeanClassName(cls.getName());
}
}
// 如果已经存在自动代理创建器并且与将要创建的一致,那么无需再次创建
return null;
} RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
以上代码实现了自动注册AnnotationAwareAspectJAutoProxyCreator类的功能,同时这里还涉及了—个优先级的问题,如果巳经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底需要使用哪个。
2. 处理proxy-target-class以及expose-proxy属性
useClassProxyinglfNecessary 实现了 proxy-target-class 厲件以及 expose-proxy M性的处理。
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
if (sourceElement != null) {
// 对于proxy-target-class属性的处理
boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
// 对于expose-proxy属性的处理
boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
// 强制使用的过程其实也是一个属性设置的过程
public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
}
}
- proxy-target-class:Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议尽量使用JDK的动态代理),如果被代理的目标对象实现了至少一个接口, 則会使用JDK动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。如果你希望强制使用CGLIB代理,(例如希望代理目标对象的所有方法,而不只是实现自接口的方法)那也可以。但是需要考虑以下两个问题。
- 无法通知(advise) Final方法,因为它们不能被覆写。
- 你需要将CGLIB二进制发行包放在classpath下面。
与之相较,JDK本身就提供了动态代理,强制使用CGLIB代理需要将<aop:config>的 proxy-target-class 厲性设为 true:
<aop:config proxy-target-class = "true">...</aop:config>
当需要使用CGLIB代理和@AspectJ自动代理支持,可以按照以下方式设罝<aop:aspectj- autoproxy>的 proxy-target-class 属性:
<aop:aspectj-autoproxy proxy-target-class = "true"/>
而实际使用的过程中才会发现细节问题的差別,The devil is in the detail.
- JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
- CGIJB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM (开源的Java字节码编辑类库)操作字节码实现的,性能比JDK强。
- expose-proxy:有时候目标对象内部的自我调用将无法实施切面中的增强,如下示例:
public interface AService {
public void a();
public void b();
}
@Service()
public class AServicelmpll implements AService {
@Transactional(propagation = Propagation.REQUIRED)
public void a() {
this.b{);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
}
}
此处的this指向目标对象,因此调用this.b()将不会执行b事务切面,即不会执行事务增强, 因此 b 方法的事务定义“@Transactional(propagation = Propagation.REQUIRES_NEW)” 将不会实施,为了解决这个问题,我们可以这样做:
<aop:aspectj-autoproxy expose-proxy = "true"/>
然后将以上代码中的 “this.b();” 修改为 “((AService) AopContext.currentProxy()).b();” 即可。 通过以上的修改便可以完成对a和b方法的同时增强。
最后注册组件并通知,便于监听器做进一步处理,这里就不再一一赘述了。
Spring AOP源码分析(二)动态A0P自定义标签的更多相关文章
- 5.2 Spring5源码--Spring AOP源码分析二
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- 5.2 spring5源码--spring AOP源码分析二--切面的配置方式
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- Spring AOP源码分析(三):基于JDK动态代理和CGLIB创建代理对象的实现原理
AOP代理对象的创建 AOP相关的代理对象的创建主要在applyBeanPostProcessorsBeforeInstantiation方法实现: protected Object applyBea ...
- spring AOP源码分析(三)
在上一篇文章 spring AOP源码分析(二)中,我们已经知道如何生成一个代理对象了,那么当代理对象调用代理方法时,增强行为也就是拦截器是如何发挥作用的呢?接下来我们将介绍JDK动态代理和cglib ...
- Spring AOP 源码分析 - 拦截器链的执行过程
1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...
- Spring AOP 源码分析 - 创建代理对象
1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...
- Spring AOP 源码分析 - 筛选合适的通知器
1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...
- Spring AOP 源码分析系列文章导读
1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ...
- spring aop 源码分析(二) 代理方法的执行过程分析
在上一篇aop源码分析时,我们已经分析了一个bean被代理的详细过程,参考:https://www.cnblogs.com/yangxiaohui227/p/13266014.html 本次主要是分析 ...
- spring AOP源码分析(一)
对于springAOP的源码分析,我打算分三部分来讲解:1.配置文件的解析,解析为BeanDefination和其他信息然后注册到BeanFactory中:2.为目标对象配置增强行为以及代理对象的生成 ...
随机推荐
- ArcGIS Server集群布署
ArcGIS Server集群布署 准备如下的4台机器: 计算机名 IP 布署软件 说明 VMWIN2008ENSS1 192.168.1.111 ArcGIS for Server VMWIN2 ...
- c#实现内存映射文件共享内存
原文:http://blog.csdn.net/wangtiewei/article/details/51112668 内存映射文件是利用虚拟内存把文件映射到进程的地址空间中去,在此之后进程操作文件, ...
- textarea显示默认值
点击不显示默认值,鼠标离开如果没有内容就显示默认值,如果有内容就显示内容. <textarea class="area" onfocus="if(value=='请 ...
- Android应用程序启动过程(二)分析
本文依据Android6.0源码,从点击Launcher图标,直至解析到MainActivity#OnCreate()被调用. Launcher简析 Launcher也是个应用程序,不过是个特殊的应用 ...
- Android ListView几个重要属性
1.transciptMode属性,需要用ListView或者其它显示大量Items的控件实时跟踪或者查看信息,并且希望最新的条目可以自动滚动到可视范围内.通过设置的控件transcriptMode属 ...
- LeetCode题解之Add Strings
1.题目描述 2.问题分析 直接按照加法运算规则运算即可,注意进位问题. 3.代码 string addStrings(string num1, string num2) { if( num1.emp ...
- 3.Servlet实例
一.基础实例 1.参照如下例子创建maven web工程: https://www.cnblogs.com/lukelook/p/9187313.html 2.创建一个简单的Servlet 类 pac ...
- 2.Servlet基础总结
一.简介 1.什么是Servlet Servlet(Server Applet),全称Java Servlet,未有中文译文.是用Java编写的服务器端程序.其主要功能在于交互式地浏览和修改数据,生成 ...
- Invalid argument during startup: unknown conf file parameter : requirepass
redis 设置来密码,出现来这个错误, 把配置文件那一行的 空格 去掉 # requirepass foobared 改成 requirepass 123456 因为# 和 requirepass ...
- 用字典给Model赋值并支持map键值替换
用字典给Model赋值并支持map键值替换 这个是昨天教程的升级版本,支持键值的map替换. 源码如下: NSObject+Properties.h 与 NSObject+Properties.m / ...