Spring 标签解析流程及自定义标签

 2023-02-02
原文作者:梦未醒Java 原文地址:https://juejin.cn/post/7032813151452135438

写在开头

刚开始写文章有不足之处欢迎各位朋友指正;

大家应该都知道 AbstractApplicationContext#refresh 方法涵盖了Spring容器启动时的所有操作;

今天我们研究的是 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(),该方法内部就是 标签的解析 ,我们将通过源码来总结出如何实现自定义标签,最后自己实现一个自定义标签;

Spring源码 -- obtainFreshBeanFactory();

  • 这里略过obtainFreshBeanFactory()方法的实现, 直接开始refreshBeanFactory();
    @Override
    protected final void refreshBeanFactory() throws BeansException {
       // 是否存在beanFactory, 若存在销毁
       if (hasBeanFactory()) {
          destroyBeans();
          closeBeanFactory();
       }
       try {
          // 创建 DefaultListableBeanFactory
          DefaultListableBeanFactory beanFactory = createBeanFactory();
          beanFactory.setSerializationId(getId());
          customizeBeanFactory(beanFactory);
          // 核心方法,解析xml文件成BeanDefinition对象
          loadBeanDefinitions(beanFactory);
          this.beanFactory = beanFactory;
       }
       catch (IOException ex) {
          throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
       }
    }

  • 此处需要注意, loadBeanDefinitions 为名称的方法有很多个重载;
  • 接下来方法上段代码中提到的loadBeanDefinitions(beanFactory);
  • 此时入参为DefaultListableBeanFactory类型的对象;
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
       // 用于解析xml文件 成document对象
       // 内部包含 PathMatchingResourcePatternResolver 用于加载和匹配资源文件;
       // 包含 StandardEnvironment 用于解析EL表达式
       // 包含 DefaultListableBeanFactory
       // 后续会创建 BeanDefinitionDocumentReader 用于解析document对象中的 默认标签
       XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
    
       beanDefinitionReader.setEnvironment(this.getEnvironment());
       beanDefinitionReader.setResourceLoader(this);
       // 重点,重点,重点 
       // 此处在创建 ResourceEntityResolver对象时会 创建PluggableSchemaResolver对象,此对象用于加载META-INF/spring.schemas文件里的内容, 此文件的作用是让程序知道配置文件中的标签定义配置文件中使用的标签的定义
       beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); 
    
       // Allow a subclass to provide custom initialization of the reader,
       // then proceed with actually loading the bean definitions.
       initBeanDefinitionReader(beanDefinitionReader);
       // 重要,重要,重要, 加载并解析文件
       loadBeanDefinitions(beanDefinitionReader); 
    }

  • 注意:接下来将会调用多个名为loadBeanDefinitions的方法,但他们的入参都不相同,并且这些方法都是XmlBeanDefinitionReader类中的方法;

  • 这里就不在一一列出来了,方法的入参顺序依次是:

    • String[] -> String -> Resource[] -> Resource -> EncodedResource
    // 这里把最后一个方法列出来
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
       Assert.notNull(encodedResource, "EncodedResource must not be null");
       if (logger.isTraceEnabled()) {
          logger.trace("Loading XML bean definitions from " + encodedResource);
       }
    
       Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
       // 通过往集合中添加元素,判断当前配置文件是否解析过
       if (!currentResources.add(encodedResource)) {
          throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
       }
    
       try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
          InputSource inputSource = new InputSource(inputStream);
          if (encodedResource.getEncoding() != null) {
             inputSource.setEncoding(encodedResource.getEncoding());
          }
          // 开始解析xml文件
          return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
       }
       catch (IOException ex) {
          throw new BeanDefinitionStoreException(
                "IOException parsing XML document from " + encodedResource.getResource(), ex);
       }
       finally {
          currentResources.remove(encodedResource);
          if (currentResources.isEmpty()) {
             this.resourcesCurrentlyBeingLoaded.remove();
          }
       }
    }
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
          throws BeanDefinitionStoreException {
    
       try {
          // 解析xml文件 成 document
          Document doc = doLoadDocument(inputSource, resource);
          // 解析Document对象
          int count = registerBeanDefinitions(doc, resource);
          if (logger.isDebugEnabled()) {
             logger.debug("Loaded " + count + " bean definitions from " + resource);
          }
          return count;
       } // 这里把多余的catch块删了
       catch (Throwable ex) {
          throw new BeanDefinitionStoreException(resource.getDescription(),
                "Unexpected exception parsing XML document from " + resource, ex);
       }
    }

  • xml文件如何解析document对象的代码这里就不在列出,感兴趣的朋友可以自己阅读
  • 下面说registerBeanDefinitions(Document doc, Resource resource)
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
       // 从document对象中解析 默认标签(如:import、bean、beans、alias)
       BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
       int countBefore = getRegistry().getBeanDefinitionCount();
       // 创建 XmlReaderContext,包含 NamespaceHandlerResolver,此对象加载META-INF/spring.handlers文件,用来获取非默认标签的处理类
       // 开始解析 document文档
       documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
       return getRegistry().getBeanDefinitionCount() - countBefore;
    }

  • 继续往下读;
    @Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
       this.readerContext = readerContext;
       // 开始解析
       doRegisterBeanDefinitions(doc.getDocumentElement());
    }
    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 此对象判断标签是不是默认标签
       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);
             // We cannot use Profiles.of(...) since profile expressions are not supported
             // in XML config. See SPR-12458 for details.
             if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isDebugEnabled()) {
                   logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                         "] not matching: " + getReaderContext().getResource());
                }
                return;
             }
          }
       }
       
       preProcessXml(root);
       // 真正解析开始
       parseBeanDefinitions(root, this.delegate);
       postProcessXml(root);
    
       this.delegate = parent;
    }

  • 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);
       }
    }
  • 由于是要总结出自定义标签如何实现;
  • 我们跟踪delegate.parseCustomElement(ele);
  • parseCustomElement(ele)方法会调用如下方法:
    @Nullable
    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
       // 获取对应命名空间
       String namespaceUri = getNamespaceURI(ele);
       if (namespaceUri == null) {
          return null;
       }
       // 根据命名空间找到对应的NamespaceHandlerspring
       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));
    }
  • handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))
    @Override
    @Nullable
    public BeanDefinition parse(Element element, ParserContext parserContext) {
       // 根据element 获取对应的解析类
       BeanDefinitionParser parser = findParserForElement(element, parserContext);
       // 调用解析类的 parser方法, 如果该方法没有被重写调用父类的
       return (parser != null ? parser.parse(element, parserContext) : null);
    }
  • parser.parse(element, parserContext)(下面的方法是父类的)
    public final BeanDefinition parse(Element element, ParserContext parserContext) {
       AbstractBeanDefinition definition = parseInternal(element, parserContext);
       if (definition != null && !parserContext.isNested()) {
          try {
             String id = resolveId(element, definition, parserContext);
             if (!StringUtils.hasText(id)) {
                parserContext.getReaderContext().error(
                      "Id is required for element '" + parserContext.getDelegate().getLocalName(element)
                            + "' when used as a top-level tag", element);
             }
             String[] aliases = null;
             if (shouldParseNameAsAliases()) {
                String name = element.getAttribute(NAME_ATTRIBUTE);
                if (StringUtils.hasLength(name)) {
                   aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                }
             }
             
             // 将 AbstractBeanDefinition 转换成 BeanDefinitionHolder
             BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
             registerBeanDefinition(holder, parserContext.getRegistry());
             if (shouldFireEvents()) {
                BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
                postProcessComponentDefinition(componentDefinition);
                parserContext.registerComponent(componentDefinition);
             }
          }
          catch (BeanDefinitionStoreException ex) {
             String msg = ex.getMessage();
             parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
             return null;
          }
       }
       return definition;
    }
  • parseInternal(element, parserContext)
    protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
       // 创建 GenericBeanDefinition
       BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
       // 获取我们要创建类的父类(即:自定义标签要解析成那个类的父类),
       String parentName = getParentName(element);
       if (parentName != null) {
          builder.getRawBeanDefinition().setParentName(parentName);
       }
       // 获取 自定义标签要解析成什么类型, 也就是我们 自定义BeanDefinitionParser 中getBeanClass返回的值;
       Class<?> beanClass = getBeanClass(element);
       if (beanClass != null) {
          builder.getRawBeanDefinition().setBeanClass(beanClass);
       }
       else {
          String beanClassName = getBeanClassName(element);
          if (beanClassName != null) {
             builder.getRawBeanDefinition().setBeanClassName(beanClassName);
          }
       }
       builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
       BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
       if (containingBd != null) {
          // Inner bean definition must receive same scope as containing bean.
          builder.setScope(containingBd.getScope());
       }
       if (parserContext.isDefaultLazyInit()) {
          // Default-lazy-init applies to custom bean definitions as well.
          builder.setLazyInit(true);
       }
       // 开始执行我们的自定义BeanDefinitionParser中的doParse, 此方法是一定要重写的
       doParse(element, parserContext, builder);
       return builder.getBeanDefinition();
    }

  • 参考一下源码中NamespaceHandler是如何实现的

  • 我们参考如下类,发现

    • 1、自定义NamespaceHandler,需实现NamespaceHandlerSupport
    • 2、覆写init();
    • 3、方法中注入自定义的解析类,registerBeanDefinitionParser(标签名,解析类)
    public class ContextNamespaceHandler extends NamespaceHandlerSupport {
    
       @Override
       public void init() {
          registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
          registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
          registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
          registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
          registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
          registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
          registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
          registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
       }
    
    }
  • 参考PropertyPlaceholderBeanDefinitionParser
    class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser {
    
       private static final String SYSTEM_PROPERTIES_MODE_ATTRIBUTE = "system-properties-mode";
    
       private static final String SYSTEM_PROPERTIES_MODE_DEFAULT = "ENVIRONMENT";
    
    
       
        // 返回属性值所对应的对象
       @Override
       @SuppressWarnings("deprecation")
       protected Class<?> getBeanClass(Element element) {
          if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
             return PropertySourcesPlaceholderConfigurer.class;
          }
          return org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.class;
       }
    
       @Override
       protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
          super.doParse(element, parserContext, builder);
    
          builder.addPropertyValue("ignoreUnresolvablePlaceholders",
                Boolean.valueOf(element.getAttribute("ignore-unresolvable")));
    
          String systemPropertiesModeName = element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE);
          if (StringUtils.hasLength(systemPropertiesModeName) &&
                !systemPropertiesModeName.equals(SYSTEM_PROPERTIES_MODE_DEFAULT)) {
             builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_" + systemPropertiesModeName);
          }
    
          if (element.hasAttribute("value-separator")) {
             builder.addPropertyValue("valueSeparator", element.getAttribute("value-separator"));
          }
          if (element.hasAttribute("trim-values")) {
             builder.addPropertyValue("trimValues", element.getAttribute("trim-values"));
          }
          if (element.hasAttribute("null-value")) {
             builder.addPropertyValue("nullValue", element.getAttribute("null-value"));
          }
       }
    
    }

总结

通过以上流程我们能够总结出自定义标签要做一下几件事:

  1. 创建xsd文件定义我们的标签元素和属性;
  2. 创建META-INF/spring.schemas添加schemaLocation和xsd文件做映射(xml文件中使用标签时需要导入命名空间);
  3. 继承AbstractSingleBeanDefinitionParser创建自定义BeanDefinitionParser,并实现需要的方法(getBeanClass、doParse这两个是一定要实现的),实现解析标签的逻辑
  4. 实现NamespaceHandlerSupport创建自定义NamespaceHandler,实现init()注册自定义BeanDefinitionParser;
  5. 创建META-INF/spring.handlers文件添加命名空间和我们自定义NamespaceHandler的映射关系;

自定义标签实现

  • 实现类似bean标签功能的user标签;
  1. user.xsd
    <?xml version="1.0" encoding="UTF-8"?>
    <schema xmlns="http://www.w3.org/2001/XMLSchema"
          targetNamespace="http://www.zfb.com/schema/user"
          elementFormDefault="qualified">
       <element name="user">
          <complexType>
             <attribute name="id" type="string"/>
             <attribute name="userName" type="string"/>
             <attribute name="email" type="string"/>
             <attribute name="password" type="string"/>
          </complexType>
       </element>
    </schema>
  1. META-INF/spring.schemas
    http://www.zfb.com/schema/user.xsd=META-INF/user.xsd
  1. 自定义BeanDefinitionParser
    public class MyUserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
       @Override
       protected Class<?> getBeanClass(Element element) {
          return User.class;
       }
    
       @Override
       protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
          String userName = element.getAttribute("userName");
          String email = element.getAttribute("email");
          String password = element.getAttribute("password");
    
          if (StringUtils.hasText(userName)){
             builder.addPropertyValue("userName", userName);
          }
    
          if (StringUtils.hasText(email)){
             builder.addPropertyValue("email", email);
          }
    
          if (StringUtils.hasText(password)){
             builder.addPropertyValue("password",password);
          }
    
       }
    }
  1. 自定义NamespaceHandler
    public class MyNamespaceHandler extends NamespaceHandlerSupport {
       @Override
       public void init() {
          registerBeanDefinitionParser("user", new MyUserBeanDefinitionParser());
       }
    }
  1. 创建META-INF/spring.handlers
    http://www.zfb.com/schema/user=com.spring.debug.t01_self_tag.MyNamespaceHandler
  1. spring.xml配置文件中使用
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:zfb="http://www.zfb.com/schema/user"
          xmlns="http://www.springframework.org/schema/beans"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
                          http://www.springframework.org/schema/beans/spring-beans.xsd
                      http://www.zfb.com/schema/user http://www.zfb.com/schema/user.xsd
    ">
       <zfb:user id="zfb" userName="zhangfb" email="hello@qq.com" password="password"/>
    </beans>
  1. 输出

202301012058312651.png