持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情
基于 Spring Framework v5.2.6.RELEASE
概述
上一篇对@EnableAspectJAutoProxy注解的作用原理作了分析,它可以用来开启基于 AspectJ 注解配置的 Spring AOP 配置。除了这种方式以外,还可以在 Spring XML 配置文件中开启 AOP 特性,如果切面是通过 AspectJ 注解配置的,那么可以通过在 XML 配置中增加<aop:aspectj-autoproxy/>标签来开启,如果 AOP 切面是通过 XML 中的<aop:config>标签来配置的,那么会自动开启。
这两种开启方式的解析,都需要从 Spring 对 XML 配置文件的解析开始。
配置文件的解析
关于 Spring 对 XML 配置文件的解析,之前写过详细的源码分析文章,当时只分析了从 XML 配置文件中加载 BeanDefinition 的过程,并没有涉及其他特殊配置标签的解析,不过,大部分解析的过程都是一样的,可以参考下面的文章:
本文将不再赘述这部分过程,只分析与 Spring AOP 相关的标签的部分。我们从 DefaultBeanDefinitionDocumentReader 类的doRegisterBeanDefinitions方法开始。Spring 在上下文初始化过程中,会将 XML 文件加载并解析成对应的 Document 对象,然后针对 Document 中不同的标签做不同的处理,XML 配置文件中beans标签的信息,就会交给doRegisterBeanDefinitions来处理。
// org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
// 省略部分代码
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
这里比较重要的一行代码就是parseBeanDefinitions方法的调用,我们进入到方法中。
// org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
这里会根据当前的标签或者子标签类型执行不同的解析逻辑,对于默认标签,执行parseDefaultElement方法解析,而非默认标签执行解析代理的parseCustomElement方法来解析。默认标签指的是 IOC 容器中配置 Bean 信息的标签,比如beans、bean等,而本文要分析的 AOP 相关的标签,即<aop:config>和<aop:aspectj-autoproxy/>标签,则不属于默认标签,需要通过parseCustomElement来解析。
自定义标签的解析
进入parseCustomElement方法。
// org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element)
@Nullable
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
// org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
这里就进入了关键的部分,我们逐行代码深入分析。
通过标签找到namespaceUri
首先,要通过getNamespaceURI从要解析的元素中获取namespaceUri,这个属性是在解析 XML 文件的时候添加到元素中的。这个namespaceUri具体是什么呢?我们以下面的配置文件为例。
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
</beans>
通过标签<aop:aspectj-autoproxy/>找到的namespaceUri就是xmlns:aop="``http://www.springframework.org/schema/aop``"中的uri。
查找对应的 NamespaceHandler
接下来,判断不为空之后,就是通过下面这行代码,根据namespaceUri找到对应的 NamespaceHandler。
this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
我们根据这样代码,分析如下:
- 当前类型 BeanDefinitionParserDelegate 的
readerContext成员变量的类型是 XmlReaderContext。 - XmlReaderContext 的
getNamespaceHandlerResolver方法获取到的就是它的成员变量namespaceHandlerResolver,类型是 NamespaceHandlerResolver。 - NamespaceHandlerResolver 类型的
resolve方法的作用,就是通过一个namespaceUri来找到其对应的 NamespaceHandler,查找的方法就是从它的成员变量handlerMappings集合中以namespaceUri为 Key 来查找。
因此,我们要找到 BeanDefinitionParserDelegate 被创建的时候,它的readerContext属性是怎么来的。还记得本文最开始提到的doRegisterBeanDefinitions方法吗,BeanDefinitionParserDelegate 对象正是在这个方法中创建的。
this.delegate = createDelegate(getReaderContext(), root, parent);
它在创建时,readerContext属性是通过getReaderContext得到的,我们找到这个方法。
protected final XmlReaderContext getReaderContext() {
Assert.state(this.readerContext != null, "No XmlReaderContext available");
return this.readerContext;
}
它直接使用了当前对象的readerContext属性,我们再去找 DefaultBeanDefinitionDocumentReader 是在哪里被创建的。按照 Spring 解析 XML 配置文件的代码流程,我们可以找到 XmlBeanDefinitionReader 的registerBeanDefinitions方法。
// org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
首先,通过createBeanDefinitionDocumentReader方法创建了documentReader,方法中其实就是通过反射创建了一个 DefaultBeanDefinitionDocumentReader 类型的对象,关键在方法中的第三行代码,在调用documentReader的registerBeanDefinitions时,第二个参数看似是在创建 ReaderContext,我们先进入方法看一下。
// org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
果然,readerContext的初始化和doRegisterBeanDefinitions方法的调用都在这儿。所以,我们需要搞明白上一步中的createReaderContext方法的原理。
// org.springframework.beans.factory.xml.XmlBeanDefinitionReader#createReaderContext
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
就是简单地通过构造方法创建的,因为前面我们还用到了它的namespaceHandlerResolver成员变量,因此我们需要了解一下参数中的getNamespaceHandlerResolver方法是如何创建它的namespaceHandlerResolver的。
// org.springframework.beans.factory.xml.NamespaceHandlerResolver
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
这里可以看到,在namespaceHandlerResolver为空的情况下,会通过createDefaultNamespaceHandlerResolver方法创建一个默认的对象,我们继续深入这个方法。
// org.springframework.beans.factory.xml.NamespaceHandlerResolver
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
return new DefaultNamespaceHandlerResolver(cl);
}
这里创建了一个 DefaultNamespaceHandlerResolver 类型的对象,我们进入它的构造方法,看看它是怎么创建的。
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader, String handlerMappingsLocation) {
Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
this.handlerMappingsLocation = handlerMappingsLocation;
}
这里将常量DEFAULT_HANDLER_MAPPINGS_LOCATION赋值给了它的handlerMappingsLocation成员变量,这个常量的值如下:
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
我们可以在 Spring 框架的多个子工程中找到这个文件,在spring-aop子工程中,这个文件的内容如下:
http://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
真相大白了,我们找到了这个 NamespaceHandler 的类型,就是 AopNamespaceHandler。不过,这里只是将这个配置文件的位置告诉了 NamespaceHandlerResolver,它是怎么被获取到的呢?
我们回到获取 NamespaceHandler 的地方,也就是这样代码:
this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
现在我们只需要再搞懂resolve方法。
@Override
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}
首先从通过getHandlerMappings得到handlerMappings集合,然后通过namespaceUri得到对应的值,如果这个值就是一个 NamespaceHandler 类型的对象,那么直接返回,如果不是,那说明它是我们要找的 NamespaceHandler 的类名称,通过名称实例化一个对象,初始化(调用init方法)后再添加到handlerMappings中,覆盖之前存放的类名,下次直接获取到的就是对象。
这个过程中,我们还要分析最开始的getHandlerMappings方法,它应该就是将META-INF/spring.handlers文件内容解析成handlerMappings集合的方法。
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
if (logger.isTraceEnabled()) {
logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
}
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isTraceEnabled()) {
logger.trace("Loaded NamespaceHandler mappings: " + mappings);
}
handlerMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return handlerMappings;
}
果然,它会直接获取handlerMappings成员变量,如果是空的,则从配置文件中加载内容,最后再返回。
到这里,我们就分析完了 Spring 通过namespaceUri来获取 NamespaceHandler 的原理。我们再回到parseCustomElement方法中,看获取到 NamespaceHandler 之后,它是怎么解析 AOP 配置标签的。
自定义标签的处理
现在分析parseCustomElement方法的最后一行代码。
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
解析工作交给了上一步获取到的 AopNamespaceHandler 的parse方法,我们找到 AopNamespaceHandler 的源码。
public class AopNamespaceHandler extends NamespaceHandlerSupport {
/**
* Register the { @link BeanDefinitionParser BeanDefinitionParsers} for the
* '{ @code config}', '{ @code spring-configured}', '{ @code aspectj-autoproxy}'
* and '{ @code scoped-proxy}' tags.
*/
@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());
}
}
在之前的分析中,我们知道,AopNamespaceHandler 初次被创建出来的时候,它的init方法会被调用,这里可以看到,它通过registerBeanDefinitionParser方法,为这些 AOP 标签注册了相应的解析器。比如<aop:config>标签对应 ConfigBeanDefinitionParser 解析器,以此类推。
我们再找到它的parse方法。
// org.springframework.beans.factory.xml.NamespaceHandlerSupport#parse
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
// org.springframework.beans.factory.xml.NamespaceHandlerSupport#findParserForElement
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
很简单,其实就是根据当前要解析的标签名,从init方法注册的解析器中找到对应的解析器,并调用解析器的parse方法进行解析。
AutoProxyCreator 的注册
文章的最开始,我们提到了在 XML 中开启 Aspect 注解支持的 AOP 特性的方式,以及直接在 XML 文件中配置切面开启 AOP 特性的方式。下面我们分别看一下<aop:aspectj-autoproxy/>和<aop:config>两个标签对应的解析器的parse方法。
首先看<aop:aspectj-autoproxy/>对应的解析器 AspectJAutoProxyBeanDefinitionParser。
// org.springframework.aop.config.AspectJAutoProxyBeanDefinitionParser#parse
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
extendBeanDefinition(element, parserContext);
return null;
}
可以看到这里调用了 AopNamespaceUtils 的registerAspectJAnnotationAutoProxyCreatorIfNecessary方法,这个方法我在上一篇【Spring 源码阅读 46:@EnableAspectJAutoProxy 注解分析 】中已经介绍过了。
这也解释了为什么<aop:aspectj-autoproxy/>标签和@EnableAspectJAutoProxy注解起到的作用是类似的。
接下来在看<aop:config>对应的解析器 ConfigBeanDefinitionParser。
// org.springframework.aop.config.ConfigBeanDefinitionParser#configureAutoProxyCreator
private void configureAutoProxyCreator(ParserContext parserContext, Element element) {
AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
}
跟上面调用了相似的方法,不过有所不同,我们进去看一下。
// org.springframework.aop.config.AopNamespaceUtils#registerAspectJAutoProxyCreatorIfNecessary
public static void registerAspectJAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, parserContext);
}
再找到其中调用的registerAspectJAutoProxyCreatorIfNecessary方法。
@Nullable
public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
}
可以看到,这里注册了一个 AspectJAwareAdvisorAutoProxyCreator 后处理器。
也就是说,对于通过 Aspect 注解配置切面的情况,Spring 会通过 AnnotationAwareAspectJAutoProxyCreator 后处理器来处理,而对于通过 XML 配置切面的情况,则通过 AspectJAwareAdvisorAutoProxyCreator 来处理。从以下类关系图中,我们可以知道,它俩是父子类的关系,其中必然有很多相似的处理逻辑,只是要解析的配置方式不同罢了。
总结
本文分析了 Spring 通过 XML 配置开启 AOP 特性支持的原理,包括对注解配置切面的支持和直接在 XML 文件中配置切面的支持。
由本文和上一篇文章,我们可以知道,Spring AOP 特性,都是在后处理器中实现的,后续的文章,我将通过代码,分析处理切面的后处理器的工作原理。