在聊聊 Spring 的 XML Schema 扩展机制的使用方式中介绍了如何通过 xml 自定义标签进行 spring 扩展,在各类组件(Redis,Mybatis,Dubbo)需要和 Spring 进行整合使用时都会用到。
本文将从 Spring 源码中寻找 xml 自定义标签生效的原因,实际上,在源码中可以发现 Spring 将 xml 标签分成了两类,一类是 spring 自定义的标签<beans>;另一类则是扩展自定义标签,如 spring 事务标签<tx:xxx>等,以及前面文章中介绍的用户自定义的标签都属于扩展自定义标签。
阅读本文前建议可先查看聊聊 Spring 的 XML Schema 扩展机制的使用方式
本文中 spring 源码版本为 4.3.13-RELEASE
源码解析
从容器启动作为入口,如下所示启动代码为聊聊 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 context = new ClassPathXmlApplicationContext("spring-service.xml")
即容器初始化,包括解析 xml、加载 bean 等,ClassPathXmlApplicationContext
是 BeanFactory 的实现类。
ClassPathXmlApplicationContext
以 ClassPathXmlApplicationContext 的构造方法为入口,如下:
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 往后的流程进行详细介绍。
为方便了解上图中给类与类之间的关系,可查看如下 UML 类图:
DefaultBeanDefinitionDocumentReader
如上类图中所示DefaultBeanDefinitionDocumentReader
是接口BeanDefinitionDocumentReader
的实现类,用于从 XML 中读取 bean definition。
如下源码所示,在doRegisterBeanDefinitions
方法中要注意两个地方,一个是创建了一个委派类 BeanDefinitionParserDelegate;一个是 parseBeanDefinitions(root, this.delegate),为具体解析 BeanDefinition 的方法。
// 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.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.java
@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
复制代码
在此处会将http://www.springframework.org/schema/aop作为 map 的 key,org.springframework.aop.config.AopNamespaceHandler 为对应的 value。
因此,回到 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));
}
复制代码
一般通过继承抽象类NamespaceHandlerSupport
(NamespaceHandlerSupport
继承自接口NamespaceHandler
)来通过 registerBeanDefinitionParser 注册一个自定义的 BeanDefinitionParser。然后上述
handler.parse 会先从注册过的中找到对应 uri 对应的 BeanDefinitionParser,最后进行解析成 BeanDefinition,
以下以 AopNamespaceHandler 为例进行说明:
// 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);
}
复制代码
完成了 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;
}
复制代码
至此,对 spring 容器是如何加载并解析自定义标签的过程基本分析完成。
总结
通过对 spring 容器是如何加载并解析自定义标签的过程的源码过程进行阅读分析,了解自定义标签生效的原因,相应的 spring 是如何对 xml 的配置进行解析为 bean definition 的过程也大致可以了解。上述整个过程完成是跟着 spring 源码调用层级展开,没有具体了解源码为什么是这样进行设计,比如过程中有用到设计模式委派模式等,后续可在此基础上进行进一步的学习。
评论