基于XML的AOP实现——标签解析基于注解的AOP实现——标签解析总结参考
在初识Spring AOP一文中我们初步了解了 Spring AOP 的概念和实现方式,那么我们接下来深入了解底层实现的原理,这就需要对源码进行解读。在之前的 Sping IoC 系列,从测试代码里的定义可以知道该先从哪里入手,比如说 ApplicationContext 和 getBean()
等。但是 AOP 没有在测试代码中有直观的提现,只有 XML 配置文件中有所定义,因此我们决定从标签解析入手,开始我们的源码解读之旅。
基于XML的AOP实现——标签解析
在Spring IoC之存储对象BeanDefinition一文中我们对标签解析有了初步的认识,其中包括四大基本标签, 分别是 import、alias、bean、beans。 其中 bean 标签的解析是最核心的功能,对于 alias、import、beans 标签的解析都是基于 bean 标签解析的。 除此之外我们还学习自定义标签的解析,详细解读可以参看Spring IoC自定义标签解析。那么我们就开始定位到标签解析部分,对 XML 配置文件进行分析。
首先我们将对基于 XML 实现的 AOP 功能进行学习,配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="host" class="com.msdn.bean.Host" />
<bean id="proxy" class="com.msdn.aop.ProxyXML" />
<!--Spring基于Xml的切面-->
<aop:config>
<!--定义切点函数-->
<aop:pointcut id="rentPointCut" expression="execution(* com.msdn.bean.Host.rent())"/>
<!-- 定义切面 order 定义优先级,值越小优先级越大-->
<aop:aspect ref="proxy" order="0">
<!--前置通知-->
<aop:before method="seeHouse" pointcut-ref="rentPointCut" />
<!--环绕通知-->
<aop:around method="getMoney" pointcut-ref="rentPointCut" />
<!--后置通知-->
<aop:after method="fare" pointcut-ref="rentPointCut" />
</aop:aspect>
</aop:config>
</beans>
重点关注<aop:config>
标签,那我们直接跳转到标签解析的地方,在 DefaultBeanDefinitionDocumentReader 类的 parseBeanDefinitions()
方法中,经过调试发现<aop:config>
标签属于自定义标签,因此会执行 parseCustomElement()
方法。这里就不做过多讲解了,在 Spring IoC自定义标签中都有介绍。那我们直接再跳转到标签处理器处,在此之前需要知道在哪发现的标签处理器。我们来到 NamespaceHandlerSupport 类中的 findParserForElement()
方法,调试结果如下:
由图可知,对应的标签处理器为 ConfigBeanDefinitionParser,查看其核心方法。
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
//注册AspectJAutoProxyCreator
this.configureAutoProxyCreator(parserContext, element);
List<Element> childElts = DomUtils.getChildElements(element);
Iterator var5 = childElts.iterator();
//遍历<aop:config>标签下的子标签,注册BeanDefinition
while(var5.hasNext()) {
Element elt = (Element)var5.next();
String localName = parserContext.getDelegate().getLocalName(elt);
if ("pointcut".equals(localName)) {
this.parsePointcut(elt, parserContext);
} else if ("advisor".equals(localName)) {
this.parseAdvisor(elt, parserContext);
} else if ("aspect".equals(localName)) {
this.parseAspect(elt, parserContext);
}
}
parserContext.popAndRegisterContainingComponent();
return null;
}
其中 configureAutoProxyCreator()
方法我们比较关心的,下面对于标签的处理,自己进行调试查看之后是可以直观理解的,就像我们之前讲解 BeanDefinitionParser 接口的实现方式时一样,只不过这里内容更多一些而已。关于自定义标签的解析方法,如果有兴趣的朋友可以去看一下Spring自定义标签的实现。
那我们接着研究 configureAutoProxyCreator()
方法,具体实现在 AopNamespaceUtils 类中。
public static void registerAspectJAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {
//注册或升级AutoProxyCreator定义beanName为org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));
//对于proxy-target-class以及expose-proxy属性的处理
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
//注册组件并通知,便于监听器做进一步处理
//其中beanDefinition的className为AnnotationAutoProxyCreator
registerComponentIfNecessary(beanDefinition, parserContext);
}
在 registerAspectJAutoProxyCreatorIfNecessary()
方法中主要完成3件事情。
1、注册或者升级 AspectJAwareAdvisorAutoProxyCreator
对于基于 XML 文件的 AOP 实现,基本上是靠 AspectJAwareAdvisorAutoProxyCreator 来完成,它可以根据<aop:pointcut>
标签来自动代理相匹配的 bean。为了配置简便,Spring 使用了自定义配置来帮助我们自动注册 AspectJAwareAdvisorAutoProxyCreator,其注册过程还是在 AopNamespaceUtils 类中。
public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
}
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
//如果已经存在了自动代理创建器且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底需要使用哪个
if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
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;
} else {
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", -2147483648);
beanDefinition.setRole(2);
registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);
return beanDefinition;
}
}
以上代码中实现了自动注册 AspectJAwareAdvisorAutoProxyCreator 类的功能,同时这里还涉及了一个优先级的问题,如果已经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底需要使用哪个。
2、处理 proxy-target-class 以及 expose-proxy 属性
useClassProxyingIfNecessary()
方法实现了 proxy-target-class 以及 expose-proxy 属性的处理。其代码定义如下:
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
if (sourceElement != null) {
boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute("proxy-target-class"));
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute("expose-proxy"));
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
不过呢,由于我们目前分析的基于 XML 文件的 AOP 实现,所以这两个属性我们并没有使用到。但是需要注意的是,在之前的文章中有提到 proxy-target-class 属性,相信到了这一步,大家也都发现了该方法是针对 <aop:aspectj-autoproxy proxy-target-class="true" />
的解析,准确点说就是 Spring 对注解实现的解析。这点后续我们再分析。
3、注册组件并通知
registerComponentIfNecessary()
方法用来注册组件并通知,便于监听器做进一步处理。
private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
if (beanDefinition != null) {
parserContext.registerComponent(new BeanComponentDefinition(beanDefinition, "org.springframework.aop.config.internalAutoProxyCreator"));
}
}
在 parse()
方法中解析其他标签的时候,除了注册相关的 BeanDefinition,也注册了组件,不过在 parse()
方法要结束的时候调用 popAndRegisterContainingComponent()
方法将 parserContext 中存在的组件给去除掉。
基于注解的AOP实现——标签解析
在上面的解析过程,我们在源码中发现还有一套类似的代码,因为我们实现 AOP 采用两种方式,所以猜测那部分代码是为了基于注解的标签处理。废话不多说,我们直接定位到 NamespaceHandlerSupport 类中的 parse()
方法。调试结果如下:
当遇到<aop:aspectj-autoproxy proxy-target-class="true" />
标签时,会使用 AspectJAutoProxyBeanDefinitionParser 解析器进行解析,那么我们就进入该类一探究竟。
还是先从 parse 方法入手。
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 注册AnnotationAwareAspectJAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
// 对于注解中子类的处理
this.extendBeanDefinition(element, parserContext);
return null;
}
对比上面关于 <aop:config>
标签的解析,可以发现此处更为简洁,并且通过查看代码之后会发现很相似。
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));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, parserContext);
}
看到这个方法是不是觉得特别眼熟,没错,这和上面我们解读过的很相似,这里也要做3件事,但是具体细节还是有所不同,不同点在于此处注册的是 AnnotationAwareAspectJAutoProxyCreator。
1、注册或升级AnnotationAwareAspectJAutoProxyCreator
对于AOP的实现,基本上都是靠AnnotationAwareAspectJAutoProxyCreator去完成,它可以根据@Point注解定义的切点来自动代理相匹配的bean。但是为了配置简便,Spring使用了自定义配置来帮助我们自动注册AnnotationAwareAspectJAutoProxyCreator,其注册过程就是在这里实现的。我们继续跟进到方法内部:
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
} else {
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", -2147483648);
beanDefinition.setRole(2);
registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);
return beanDefinition;
}
}
该方法就不多做介绍了,和上面调用的是同一个方法。
2、处理proxy-target-class以及expose-proxy属性
useClassProxyingIfNecessary实现了proxy-target-class属性以及expose-proxy属性的处理,进入到方法内部:
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
if (sourceElement != null) {
boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute("proxy-target-class"));
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute("expose-proxy"));
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
基于 XML 分析时因为没有使用相关属性,所以我们没有对这两个属性进行解析,但是此时我们使用了 proxy-target-class 属性,那不妨看看具体怎么调用的。
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
BeanDefinition definition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
}
public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
BeanDefinition definition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
}
}
<aop:aspectj-autoproxy proxy-target-class="true"/>
proxy-target-class 属性值决定是基于接口的还是基于类的代理被创建,默认为 false。如果 proxy-target-class 属性值被设置为 true,那么基于类的代理将有效(这时需要 Cglib 库)。反之是基于接口的代理(JDK的动态代理),建议尽量使用 JDK 动态代理。
如果被代理的目标对象实现了至少一个接口,则会使用 JDK 动态代理,所有该目标类型实现的接口都将被代理。若该目标对象没有任何接口实现,则创建一个 Cglib 代理,如果你希望强制使用 Cglib 代理,(例如希望代理目标对象的所有方法,而不只是实现子接口的方法)那也可以。但是需要考虑以下两个问题:
无法通知(Advice)Final 方法,因为它们不能被覆写。
你需要将 Cglib 二进制发行包放在 classpath 下面。
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方法的同时增强。
3、注册组件并通知
registerComponentIfNecessary()
方法用来注册组件并通知,便于监听器做进一步处理。
extendBeanDefinition()
方法用来解析 <aop:aspectj-autoproxy>
下的子标签,但是暂未使用到。
总结
本文篇幅倒是不长,我是借鉴学习 Spring IoC 标签解析的经验,追根溯源,尽量把 AOP 标签解析这个事情从头到尾讲清楚。参考的书籍上只讲解了基于注解的 AOP 标签解析,为了解答自己心中的疑惑,本文基于两种方式都进行了讲解,虽然差异很小,但是心里总归是踏实一些。大家在阅读的过程中,如果发现文章中出现错误或不妥之处,这里还请指明,也请多多指教。大家共同学习,一起进步。