Spring解析自定义XML标签过程源码解读

378 阅读5分钟

聊一聊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();
    }
  }

由于整个过程涉及到的源码层级较深,类较多,为了方法来回查看大致做了如下一个时序图(着重查看解析自定义标签的过程,后续的流程已忽略):

spring-refresh时序图.png

通过此图可以方便查看源码时,来回切换查找定位。实际上到 DefaultBeanDefinitionDocumentReader 类往后涉及到具体的从 XML标签解析成 BeanDefinition ,下面会从 parseCustomElement 往后的流程进行详细介绍。

此流程中涉及到的类图关系如下:

spring-refresh类图.jpg

解析自定义标签

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方法,一般通过继承抽象类NamespaceHandlerSupportNamespaceHandlerSupport继承自接口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 源码调用层级展开,没有具体了解源码为什么是这样进行设计,比如过程中有用到设计模式委派模式等,后续可在此基础上进行进一步的学习。