聊一聊Spring的XML Schema扩展机制一文中介绍了通过自定义XML标签(除了spring-beans-x.xsd中定义的标签)来完成扩展功能,其广泛应用与各组件与Spring整合,例如Mybatis,Redis,MQ组件等;也应用于Spring自带的功能,例如事务<tx:xxx />、AOP等。
本文将从 Spring 源码中寻找 XML自定义标签生效的原因,实际上,在源码中可以发现 Spring 将 XML标签分成了两类,一类是 spring 自定义的标签;另一类则是扩展自定义标签,如 spring 事务标签tx:xxx等,以及前面文章中介绍的用户自定义的标签都属于扩展自定义标签。
源码解读
以聊一聊Spring的XML Schema扩展机制中的启动类为入口来对源码进行解读:
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-service.xml");
DistributedIdComponent bean = context.getBean(DistributedIdComponent.class);
String id = bean.generateId();
System.out.println("id:" + id);
}
}
ClassPathXmlApplicationContext是 BeanFactory 的实现类,作为容器上下文,其初始化的过程包括解析 xml、加载 bean 等,也就是对应上面的ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-service.xml");。
加载时序
入口ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-service.xml");,ClassPathXmlApplicationContext的两个构造方法如下,其中refresh()方法为主要入口:
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
// 主要入口
refresh();
}
}
由于整个过程涉及到的源码层级较深,类较多,为了方法来回查看大致做了如下一个时序图(着重查看解析自定义标签的过程,后续的流程已忽略):
通过此图可以方便查看源码时,来回切换查找定位。实际上到 DefaultBeanDefinitionDocumentReader 类往后涉及到具体的从 XML标签解析成 BeanDefinition ,下面会从 parseCustomElement 往后的流程进行详细介绍。
此流程中涉及到的类图关系如下:
解析自定义标签
DefaultBeanDefinitionDocumentReader
如上类图中所示DefaultBeanDefinitionDocumentReader是接口BeanDefinitionDocumentReader的实现类,用于从 XML 中解析配置为 bean definition。
如下源码所示,在doRegisterBeanDefinitions方法中要注意两个地方,一个是创建了一个委派类 BeanDefinitionParserDelegate,是实际解析XML标签配置为bean definition的类;一个是 parseBeanDefinitions(root, this.delegate),为具体解析 BeanDefinition 的方法,最后调用BeanDefinitionParserDelegate进行处理。(此处用到了委派模式)
// DefaultBeanDefinitionDocumentReader.java
/**
* Register each bean definition within the given root {@code <beans/>} element.
*/
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
// 解析xml中的BeanDefinition
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
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);
}
}
从 parseBeanDefinitions 方法中的逻辑可以看出,最终会有两个分支,一个是解析默认标签;一个是解析自定义(扩展)标签,这里直接看 parseCustomElement,进而进入委派类BeanDefinitionParserDelegate:
// BeanDefinitionParserDelegate.java
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
// 关键点
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));
}
关键的一行代码为NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri),获取对应的 NamespaceHandler 类。
其中this.readerContext是在 XmlBeanDefinitionReader 中 registerBeanDefinitions 的方法中实例化 XmlReaderContext 然后传递下来的(可查看第一张时序图来定位到此方法在整个流程中的位置),如下:
// XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// createReaderContext见下面的方法
documentReader.registerBeanDefinitions(doc, createReaderContext见下面的方法(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
// 实例化XmlReaderContext
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
// 创建DefaultNamespaceHandlerResolver
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}
从而可以得出this.readerContext.getNamespaceHandlerResolver()返回的就是DefaultNamespaceHandlerResolver实例,进而查看DefaultNamespaceHandlerResolver的 resolve(namespaceUri)方法,如下查看DefaultNamespaceHandlerResolver类代码:
// DefaultNamespaceHandlerResolver.java
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
/**
* The location to look for the mapping files. Can be present in multiple JAR files.
*/
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
/** 保存namespace URI与NamespaceHandler类名的映射关系 */
@Nullable
private volatile Map<String, Object> handlerMappings;
@Override
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("NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "]: problem with handler class file or dependent class", err);
}
}
}
/**
* Load the specified NamespaceHandler mappings lazily.
*/
private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(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 this.handlerMappings;
}
}
handlerMappingsLocation 默认取META-INF/spring.handlers,通过上述的 getHandlerMappings()方法将META-INF/spring.handlers里配置的信息以 map 的结构保存在属性handlerMappings中,例如 spring-aop 的 spring-handlers 文件中配置如下:
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
在此处会将www.springframework.org/schema/aop作… map 的 key,org.springframework.aop.config.AopNamespaceHandler 为对应的 value,存入handlerMappings中。
因此,回到 BeanDefinitionParserDelegate 的 parseCustomElement 方法中,
// BeanDefinitionParserDelegate.java
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
// this.readerContext.getNamespaceHandlerResolver()对应为DefaultNamespaceHandlerResolver
// 通过DefaultNamespaceHandlerResolver的resolve方法,会从META-INF/spring.handlers中的配置获取到对应的NamespaceHandler实现类
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 使用自定义NamespaceHandler实现类进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);可以根据namespace uri来获取到对应的NamespaceHandler类,最后调用NamespaceHandler的parse方法对XML进行解析为bean definition。
接下来查看NamespaceHandler#parse方法,一般通过继承抽象类NamespaceHandlerSupport (NamespaceHandlerSupport继承自接口NamespaceHandler)来通过 registerBeanDefinitionParser 注册一个自定义的 BeanDefinitionParser,
// abstract class NamespaceHandlerSupport
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 获取到BeanDefinitionParser,并调用其parse方法
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 获取名称如aspectj-autoproxy
String localName = parserContext.getDelegate().getLocalName(element);
// 获取到对应的BeanDefinitionParser 如AspectJAutoProxyBeanDefinitionParser
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
查看findParserForElement方法可知,首先获取到localName,通过localName获取BeanDefinitionParser,那么localName与BeanDefinitionParser关系是在哪里进行关联的?是在NamespaceHandler的实现类中重写init()方法来完成,如下为Spring AOP的NamespaceHandler:
// 1.注册BeanDefinitionParser
public class AopNamespaceHandler extends NamespaceHandlerSupport {
public AopNamespaceHandler() {
}
public void init() {
// 调用NamespaceHandlerSupport的registerBeanDefinitionParser进行注册BeanDefinitionParser
this.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
this.registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
this.registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
// abstract class NamespaceHandlerSupport
// parsers属性保存映射关系
private final Map<String, BeanDefinitionParser> parsers =
new HashMap<String, BeanDefinitionParser>();
// 上面init方法调用时写入属性parsers中
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
从上可知,在init()方法中调用this.registerBeanDefinitionParser方法在NamespaceHandlerSupport中实现,最终保存在Map类型的属性parsers中。
最终,通过相应的localName可以找到对应的BeanDefinitionParser,最终解析XML里的配置为Bean Definition的过程在BeanDefinitionParser中,如聊一聊Spring的XML Schema扩展机制中所示的DistributedIdParser ,以及上面的AOP中对应的AspectJAutoProxyBeanDefinitionParser等。
至此,对Spring 容器是如何加载并解析自定义标签的过程基本分析完成。
总结
通过对 Spring 容器是如何加载并解析自定义标签的过程的源码过程进行阅读分析,了解配置自定义标签生效的原因,相应的Spring 是如何对 XML的配置进行解析为 bean definition 的过程也大致可以了解。上述整个过程完成是跟着 spring 源码调用层级展开,没有具体了解源码为什么是这样进行设计,比如过程中有用到设计模式委派模式等,后续可在此基础上进行进一步的学习。