掌握 Spring 必须知道的 BeanDefinition(下)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: xml 配置解析xml 配置文件的解析由 XmlBeanDefinitionReader 来完成。XmlBeanDefinitionReader 加载 BeanDefinition 的入口方法如下。

xml 配置解析


xml 配置文件的解析由 XmlBeanDefinitionReader 来完成。XmlBeanDefinitionReader 加载 BeanDefinition 的入口方法如下。


  @Override
  public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
  }


这里正是将前面提到的 AbstractBeanDefinitionReader 解析出的资源文件作为参数,然后转换为 EncodedResource。继续跟踪源码如下。


  public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    ... 省略部分代码
    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
      InputSource inputSource = new InputSource(inputStream);
      if (encodedResource.getEncoding() != null) {
        inputSource.setEncoding(encodedResource.getEncoding());
      }
      return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    ... 省略部分代码
  }


这里将 EncodedResource 转换为 InputSource ,继续跟踪源码如下。


protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
    try {
      Document doc = doLoadDocument(inputSource, resource);
      int count = registerBeanDefinitions(doc, resource);
      if (logger.isDebugEnabled()) {
        logger.debug("Loaded " + count + " bean definitions from " + resource);
      }
      return count;
    }
    ... 省略部分代码
  }


先把 InputSource 转换为 Document,然后解析 Document 注册 bean。解析 xml 是使用 jdk org.xml.sax 进行完成,查看 doLoadDocument 方法如下。


  protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
        getValidationModeForResource(resource), isNamespaceAware());
  }


这里有一个 EntityResolver 需要留意,其用来获取本地的 dtd 或 xsd ,避免网络请求的开销或网络请求失败。主要的实现为 DelegatingEntityResolver,其会将 dtd 文件的获取委托为 BeansDtdResolver,将 xsd 文件的获取委托给 PluggableSchemaResolver。PluggableSchemaResolver 会从类路径下 META-INF/spring.schemas文件读取 xsd 文件的位置。spring-beans 模块下 spring.schemas 的部分内容如下。


http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool.xsd
http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util.xsd


跟踪注册 BeanDefinition 的方法如下。


  private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
      DefaultBeanDefinitionDocumentReader.class;
  // 注册 BeanDefinition    
  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;
  }
  protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
    return BeanUtils.instantiateClass(this.documentReaderClass);
  } 


这里 XmlBeanDefinitionReader 创建了一个 BeanDefinitionDocumentReader,其实现为 DefaultBeanDefinitionDocumentReader ,并将 BeanDefinition 的解析注册委托为这个对象。跟踪 DefaultBeanDefinitionDocumentReader#registerBeanDefinitions 源码,其调用方法如下。


  protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
    if (this.delegate.isDefaultNamespace(root)) {
      // 如果 <beans/> 标签使用 Spring 默认的命名空间,并且指定的 profile 未被激活,则不处理
      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)) {
          return;
        }
      }
    }
    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);
    this.delegate = parent;
  }


这里已经进入了较为核心的处理逻辑,先获取 <beans/> 标签的 profile 属性,如果未被激活则不会再处理,否则开始解析 <beans/> 标签。后面的逻辑已经比较重要,在看的朋友留意。跟踪 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);
    }
  }


这里对<beans/> 的子标签进行解析,如果发现使用的是默认的命名空间则直接解析,否则解析自定义的命名空间。<beans/> 标签下默认命名空间的标签包括<import/>、<alias/>、<bean/>、<beans/>,按照 xml dtd 或 xsd 的定义中规中矩的解析即可,而非默认命名空间的解析则是 Spring 留给我们扩展使用。下面把重点放到自定义标签的解析,跟踪BeanDefinitionParserDelegate#parseCustomElement(Element)方法,其又会调用如下的方法。


  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));
  }


该方法根据命名空间找到相应的命名空间处理器 NamespaceHandler ,然后进行解析。先看 NamespaceHandler 的定义。


public interface NamespaceHandler {
  // 初始化
  void init();
  // 将标签解析为 BeanDefinition
  @Nullable
  BeanDefinition parse(Element element, ParserContext parserContext);
  // 对 BeanDefinition 再次处理,Spring 使用其解析默认命名空间下标签的非默认命名空间属性或子标签
  @Nullable
  BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);


那么命名空间处理器是如何获取的呢?其实现源码如下。


public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
  @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();
        // 缓存 NamespaceHandler
        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);
      }
    }
  }
}


NamespaceHandler 是从 handlerMappings 获取的,获取到后会先调用其 init 方法进行初始化。再来看 handlerMappings 的获取方法。


public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
  public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
  public DefaultNamespaceHandlerResolver() {
    this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
  }
  public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
    this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
  }
  private Map<String, Object> getHandlerMappings() {
    Map<String, Object> handlerMappings = this.handlerMappings;
    if (handlerMappings == null) {
      synchronized (this) {
        handlerMappings = this.handlerMappings;
        if (handlerMappings == null) {        
          try {
            Properties mappings =
                PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);        
            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;
  }
}


这里看到,NamespaceHandler 最终是定义在 META-INF/spring.handlers 位置中,spring-beans 模块中也定义了这样的文件,用于解析命名空间cputil 。查看如下。


http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler


至此,Spring 默认命名空间和非默认命名空间的 xml 标签,Spring 都完美进行了解析。


自定义 xml 配置解析


根据上面的描述,如果需要解析 Spring xml 配置文件自定义的标签,我们只需要在类路径下 META-INF/spring.handlers 文件中定义自己的 NamespaceHandler 即可,事实上 Spring 提供了一个便于我们实现自己业务逻辑的 NamespaceHandlerSupport。代码较少,这里直接拿来。


public abstract class NamespaceHandlerSupport implements NamespaceHandler {
  private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
  private final Map<String, BeanDefinitionDecorator> decorators = new HashMap<>();
  private final Map<String, BeanDefinitionDecorator> attributeDecorators = new HashMap<>();
  @Override
  @Nullable
  public BeanDefinition parse(Element element, ParserContext parserContext) {
    BeanDefinitionParser parser = findParserForElement(element, parserContext);
    return (parser != null ? parser.parse(element, parserContext) : null);
  }
  @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;
  }
  @Override
  @Nullable
  public BeanDefinitionHolder decorate(
      Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
    BeanDefinitionDecorator decorator = findDecoratorForNode(node, parserContext);
    return (decorator != null ? decorator.decorate(node, definition, parserContext) : null);
  }
  @Nullable
  private BeanDefinitionDecorator findDecoratorForNode(Node node, ParserContext parserContext) {
    BeanDefinitionDecorator decorator = null;
    String localName = parserContext.getDelegate().getLocalName(node);
    if (node instanceof Element) {
      decorator = this.decorators.get(localName);
    }
    else if (node instanceof Attr) {
      decorator = this.attributeDecorators.get(localName);
    }
    else {
      parserContext.getReaderContext().fatal(
          "Cannot decorate based on Nodes of type [" + node.getClass().getName() + "]", node);
    }
    if (decorator == null) {
      parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionDecorator for " +
          (node instanceof Element ? "element" : "attribute") + " [" + localName + "]", node);
    }
    return decorator;
  }
  protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
    this.parsers.put(elementName, parser);
  }
  protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) {
    this.decorators.put(elementName, dec);
  }
  protected final void registerBeanDefinitionDecoratorForAttribute(String attrName, BeanDefinitionDecorator dec) {
    this.attributeDecorators.put(attrName, dec);
  }
}


NamespaceHandlerSupport 将标签、属性、子标签的解析分别委托给 BeanDefinitionParser、BeanDefinitionDecorator。我们在 init 方法进行注册即可。


至此,总结 Spring 自定义 xml 配置文件标签解析的流程如下。


定义自己的命名空间对应的 xsd 文件,并在 META-INF/spring.schemas 文件注册,便于直接查找本地文件。


在 Spring xml 配置文件中指定自己的命名空间及对应的 xsd 文件路径。

实现 NamespaceHandlerSupport 类,在 init 方法注册自己的 BeanDefinitionParser、BeanDefinitionDecorator。

在类路径文件 META-INF/spring.handlers 中定义自己的 NamespaceHandler 。


BeanDefinition 注册


BeanDefinition 由 BeanDefinitionRegistry 进行注册。BeanDefinitionRegistry 定义如下。


public interface BeanDefinitionRegistry extends AliasRegistry {
  // 注册 BeanDefinition
  void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException;
  // 移除注册的 BeanDefinition
  void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
  // 获取注册的 BeanDefinition
  BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
  // 是否注册了给定名称的 BeanDefinition
  boolean containsBeanDefinition(String beanName);
  // 获取已经注册的 BeanDefinition
  String[] getBeanDefinitionNames();
  // 获取注册的 BeanDefinition 的数量
  int getBeanDefinitionCount();
  // 给定名称的 BeanDefinition 是否已经注册,包括别名
  boolean isBeanNameInUse(String beanName);
}


BeanDefinitionRegistry 由 DefaultListableBeanFactory 进行实现,内部使用 map 保存 BeanDefinition,ApplicationContext 应用上下文的实现类中也实现了 BeanDefinitionRegistry ,其底层会委托给 DefaultListableBeanFactory 。如果我们需要注册自定义的 BeanDefinition,只需要通过 BeanFactoryAware 、@Autowire 注入、ConfigurableApplicationContext 等方法获取 DefaultListableBeanFactory 实例即可。


总结

BeanDefinition 作为 bean 定义的元数据,是理解 Spring 的基石,本篇对 BeanDefinition 的分类、解析、注册详细进行了描述,希望对大家有帮忙,欢迎留言讨论。


目录
相关文章
|
2月前
|
XML Java 数据格式
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
67 3
|
10月前
|
XML Java 数据格式
我滴妈!人事竟然问我Spring BeanDefinition是如何帮我们解析和加载的?
我滴妈!人事竟然问我Spring BeanDefinition是如何帮我们解析和加载的?
46 0
我滴妈!人事竟然问我Spring BeanDefinition是如何帮我们解析和加载的?
|
3月前
|
Java Spring 容器
深入理解BeanDefinition和Spring Beans
本文深入探讨了Spring框架中的BeanDefinition和Spring Beans。BeanDefinition是Bean的元数据,包含类名、作用域、构造函数参数和属性值等信息。Spring Beans是根据BeanDefinition实例化的对象。文章详细阐述了BeanDefinition的属性,如类名、作用域(如单例和原型)及构造函数和属性值。此外,还介绍了如何使用BeanDefinition动态注册、延迟加载和实现依赖注入。通过示例代码,展示了如何创建和自定义BeanDefinition以满足特定需求。理解BeanDefinition有助于更高效地开发和维护Spring应用程序。
60 0
|
XML 存储 设计模式
Spring高手之路11——BeanDefinition解密:构建和管理Spring Beans的基石
本文对BeanDefinition进行全面深入的探讨,涵盖BeanDefinition的接口方法、主要信息、类型以及生成过程等方面内容。旨在帮助读者全面理解BeanDefinition的各方面知识,并能够熟练应用。文章通俗易懂,具有很强的指导意义。
102 0
Spring高手之路11——BeanDefinition解密:构建和管理Spring Beans的基石
|
3月前
|
XML 缓存 Java
Spring5源码(18)-Spring注册BeanDefinition
Spring5源码(18)-Spring注册BeanDefinition
35 0
|
9月前
|
XML Java 数据格式
Spring高手之路16——解析XML配置映射为BeanDefinition的源码
本文提供了深入Spring源码的透彻解析,从XML配置文件的加载开始,步入了Spring的内部世界。通过细致剖析setConfigLocations、refresh和loadBeanDefinitions等方法的实现,揭示了Bean从定义到注册的整个生命周期。
100 1
Spring高手之路16——解析XML配置映射为BeanDefinition的源码
|
3月前
|
XML 前端开发 Java
【Spring 源码】 深入理解 Bean 定义之 BeanDefinition
【Spring 源码】 深入理解 Bean 定义之 BeanDefinition
|
XML 存储 Java
Spring高手之路12——BeanDefinitionRegistry与BeanDefinition合并解析
本文深入探讨Spring的BeanDefinition和BeanDefinitionRegistry,详细介绍了BeanDefinition的合并过程及其源码分析,揭示了Spring配置元数据的内在逻辑。读者将通过本文理解Spring Bean定义的继承和重用机制,掌握如何动态注册BeanDefinition。
158 0
Spring高手之路12——BeanDefinitionRegistry与BeanDefinition合并解析
|
XML Java 数据格式
深入理解Spring IOC(二) 、从xml到BeanDefinition(下)
深入理解Spring IOC(二) 、从xml到BeanDefinition(下)
68 0
|
XML Java 数据格式
深入理解Spring IOC(二) 、从xml到BeanDefinition(上)
深入理解Spring IOC(二) 、从xml到BeanDefinition(上)
78 0