写点什么

源码学习之 Spring 容器创建原理

  • 2022-11-14
    北京
  • 本文字数:12769 字

    阅读完需:约 42 分钟

源码学习之Spring容器创建原理

1 前言

众所周知,Spring 可以帮我们管理我们需要的 bean。在我们需要用到这些 bean 的时候,可以很方便的获取到它,然后进行一系列的操作。比如,我们定义一个 bean MyTestBean

public class MyTestBean {    private String testStr = "testStr";
public String getTestStr() { return testStr; }
public void setTestStr(String testStr) { this.testStr = testStr; }
复制代码

然后 xml 配置一下

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">   <bean id = "myTestBean" class="bean.MyTestBean"/></beans>
复制代码

编写一下测试代码,测试一下,就会看到测试通过的结果。

public class BeanFactoryTest {
@Test public void testSimpleLoad() { BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml")); MyTestBean bean = (MyTestBean)bf.getBean("myTestBean"); assert "testStr".equals(bean.getTestStr()); }}
复制代码

直接使用 BeanFactory 作为容器对于 Spring 来说不常见,这里只是用来测试,以便可以更快更好地分析 Spring 内部原理。其涉及到的一些组件,贯穿整个 Spring 容器当中,对于我们了解其他 Spring 容器也有很大的帮助。限于篇幅,这里只介绍该容器创建的部分,即对于 new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”)),Spring 都干了些什么。

2 Spring 容器创建原理

2.1 整体实现流程

首先我们大致了解一下 Spring 容器创建的整体过程。



整体时序图

可以看到,该 Spring 容器创建大致分为以下几部分:


  1. 资源的封装,以 Resource 封装配置文件

  2. 加载 BeanDefinition

  3. 解析配置文件,获取 Document

  4. 解析及注册 BeanDefinition

  5. 标签的解析,分为默认标签和自定义标签的解析

下面我们就以这样的顺序对各个部分从代码实现上进行具体分析。

2.2 核心类介绍

在进行具体创建逻辑之前,我们先对 Spring 容器创建的核心类进行介绍,以便我们更好地掌握它的实现过程。

2.2.1 DefaultListableBeanFactory

XmlBeanFactory 继承自 DefaultListableBeanFactory,而 DefaultListableBeanFactory 是整个 bean 加载的核心部分,是 Spring 注册及加载 bean 的默认实现。XmlBeanFactory 与 DefaultListableBeanFactory 的不同之处在于,XmlBeanFactory 使用了自定义的 XML 读取器 XmlBeanDefinitionReader,实现了个性化的 BeanDefinitionReader 读取。



容器加载相关类图


2.2.2 XmlBeanDefinitionReader

XmlBeanDefinitionReader 用于资源文件读取、解析及 Bean 注册。读取 Xml 配置文件的流程大致为,首先使用 ResourceLoader 将资源文件路径转换为对应的 Resource 文件,然后将 Resource 文件转换为 Document 文件,最后对 Document 及 Element 进行解析。



配置文件读取相关类图


2.2.3 BeanDefinition

在 Spring 中,BeanDefinition 是配置文件元素标签在容器中的内部表示形式,包含了元素的所有信息。Spring 将配置文件中的转换为 BeanDefinition,并将这些 BeanDefinition 注册到 BeanDefinitionRegistry 中。BeanDefinitionRegistry 以 map 形式保存,后续操作直接从 BeanDefinitionRegistry 中读取配置信息。



BeanDefinition 及其实现类


2.3 配置文件的封装

在 Java 中不同的资源都要抽象成 URL,然后使用不同类型的 URLStreamHandler 处理不同的 URL 表示的资源。但是,Spring 对其内部使用到的资源实现了自己的抽象结构:Resource 接口封装底层资源。主要原因有 3 点:

  1. URL 没有默认定义相对 Classpath 或 ServletContext 等资源的 handler

  2. URL 没有提供基本的方法,例如检查当前资源是否存在是否可读等

  3. 自定义 URL handler 需要了解 URL 实现机制

对不同来源的资源文件都有相应的 Resource 实现:文件(FileSystemResource)、Classpath 资源(ClassPathResource)、URL 资源(UrlResource)、InputStream 资源(InputStreamResource)、Byte 数组(ByteArrayResource)等。



资源文件处理相关类图


2.4 加载 BeanDefinition

下面我们就从代码层次看看整个容器究竟是怎么实现的。观察测试代码,我们可以将 XmlBeanFactory 的构造方法作为切入点进行分析。

public XmlBeanFactory(Resource resource) throws BeansException {   this(resource, null);}public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {    super(parentBeanFactory);    // 加载BeanDefinition    this.reader.loadBeanDefinitions(resource);}
复制代码

主要做了两件事,一是调用父类的构造方法,二是加载 BeanDefinition。首先我们先进入父类构造方法,最终进到 AbstractAutowireCapableBeanFactory 构造方法中。

public AbstractAutowireCapableBeanFactory() {   super();   // 忽略BeanNameAware、BeanFactoryAware和BeanClassLoaderAware接口的自动装配功能   ignoreDependencyInterface(BeanNameAware.class);   ignoreDependencyInterface(BeanFactoryAware.class);   ignoreDependencyInterface(BeanClassLoaderAware.class);}
复制代码

主要是 ignoreDependencyInterface 方法,它的主要功能是忽略给定接口的自动装配功能。实现上很简单,就是把这些 Class 加入到 ignoredDependencyInterfaces 集合中,ignoredDependencyInterfaces 是 Set>类型。

再看加载 Bean 的方法,执行的是 XmlBeanDefinitionReader 类的 loadBeanDefinitions 方法。进入方法,可以看到主要就是做了两件事,一是构造 InputSource,这个类全路径名是 org.xml.sax.InputSource,这步的目的就是通过 SAX 读取 XML 文件事先准备一下 InputSource 对象。而真正的加载 Bean 的逻辑在 doLoadBeanDefinitions 方法中。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {   InputStream inputStream = encodedResource.getResource().getInputStream();   // 构建InputSource,用于解析XMLInputSource inputSource = new InputSource(inputStream);   if (encodedResource.getEncoding() != null) {      inputSource.setEncoding(encodedResource.getEncoding());   }// 实际加载BeanDefinition的执行逻辑   return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}
复制代码

doLoadBeanDefinitions 方法首先加载 XML 文件,得到 Document 对象,然后根据 Document 对象注册 Bean。我们首先看下得到 Document 对象的过程。

2.5 获取 Document

获取 Document,首先通过 getValidationModeForResource 获取 XML 验证模式,然后解析得到 Document 对象。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {   // 首先获取XML验证模式,然后SAX方式解析得到Documentreturn this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,         getValidationModeForResource(resource), isNamespaceAware());}
复制代码

常用的 XML 验证模式有两种:DTD 和 XSD,实际使用哪种验证模式在 getValidationModeForResource 中进行了解析。这个方法判断是 DTD 验证还是 XSD 验证,仅仅是判断一下 XML 是否包含 DOCTYPE 字符串。

而解析得到 Document 的方法很简单,就是通过 SAX 解析 XML 文档的套路。这里不再赘述。

2.6 解析及注册 BeanDefinition

当把文件转换为 Document 后,接下来的提取及注册 bean 就是我们的重头戏了。调用了以下方法。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();   int countBefore = getRegistry().getBeanDefinitionCount();// 注册BeanDefinition   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));   return getRegistry().getBeanDefinitionCount() - countBefore;}
复制代码

在这个方法中很好地应用了单一职责原则,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是 BeanDefinitionDocumentReader。BeanDefinitionDocumentReader 是一个接口,实例化的工作在 createBeanDefinitionDocumentReader()中完成,真正的类型是 DefaultBeanDefinitionDocumentReader。而它的 registerBeanDefinitions 方法很简单,仅仅是先根据 Document 获取了 root,实际注册在 doRegisterBeanDefinitions 方法中。

protected void doRegisterBeanDefinitions(Element root) {   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);        // 如果环境变量不包含指定profile,则流程结束 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {            return;        }      }   }   preProcessXml(root);// 解析BeanDefinition   parseBeanDefinitions(root, this.delegate);   postProcessXml(root);}
复制代码

注册过程首先对 profile 进行处理,如果是环境变量定义的则进行处理,否则不进行处理。然后就是解析 bean。这里调用了 preProcessXml(root)和 postProcessXml(root)两个方法,但是发现这两个方法是空方法。这里应用了模板方法模式。如果继承自 DefaultBeanDefinitionDocumentReader 的子类需要在 Bean 解析前后做一些处理的话,只需要重写这两个方法即可。

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);   }}
复制代码

而解析 bean 需要判断元素是否是默认命名空间,如果是则调用 parseDefaultElement(ele, delegate)方法,不是则调用 delegate.parseCustomElement(ele)方法。判断是否是默认命名空间,调用 isDefaultNamespace 方法,元素或者节点的命名空间与 Spring 中固定的命名空间http://www.springframework.org/schema/beans 进行对比,一致则认为是默认的,否则就认为是自定义的。

2.7 默认标签的解析

默认标签的解析逻辑一目了然,分别对 4 种不同标签(import、alias、bean 和 beans)做了不同的处理。

2.7.1 bean 标签的解析及注册

对 bean 标签的解析是通过 processBeanDefinition(ele, delegate)方法进行的。大致逻辑总结如下:

  1. 首先委托 BeanDefinitionDelegate 类的 parseBeanDefinitionElement 方法进行元素解析,返回 BeanDefinitionHolder 类型的实例 bdHolder,经过这个方法后,bdHolder 实例已经包含我们配置文件中配置的各种属性了,例如 class、name、id、alias 等属性。

  2. 如果 bdHolder 不为空,若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。

  3. 解析后,对 bdHolder 进行注册。注册操作委托给了 BeanDefinitionReaderUtils 的 registerBeanDefinition 方法。

  4. 发出响应事件,通知相关监听器,这个 bean 已经加载完了。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {   // 解析元素信息,用bdHolder封装BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);   if (bdHolder != null) {// 如果标签下有自定义属性,则对自定义属性进行解析      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);      // 对bdHolder进行注册      BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());      // 发送注册事件给监听器.      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));   }}
复制代码

1)元素解析及信息提取

首先我们从元素解析及信息提取开始:delegate.parseBeanDefinitionElement(ele)。进入 BeanDefinitionParserDelegate 类 parseBeanDefinitionElement 方法,主要完成如下内容:

  1. 提取元素的 id 和 name 属性

  2. 解析其他所有属性,封装到 GenericBeanDefinition 类型的实例中,对应 this.parseBeanDefinitionElement(ele, beanName, containingBean)方法

  3. 如果 bean 没有指定 beanName,则使用默认规则为此 Bean 生成 beanName

  4. 将获取到的信息封装到 BeanDefinitionHolder 实例中

beanName 取值策略是,首先取 id,如果没有指定 id 则取 name[0](因为 name 可以指定多个),如果 name 也没有指定,则采取自动生成方式生成。

最终 bean 元素的所有属性和子元素信息都保存到 GenericBeanDefinition 中了。至此就完成了 XML 文档到 GenericBeanDefinition 的转换。

2)默认标签中自定义标签元素的解析

如果这个 bean 使用的是默认的标签配置,但是其中的子元素却使用了自定义配置,这时这部分内容就起作用了。入口是 delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)方法。它分别对元素的所有属性和子元素进行了 decorateIfRequired 方法的调用。decorateIfRequired 方法会判断,如果是自定义节点,则找出自定义类型所对应的 NamespaceHandler 并进行进一步解析。

public BeanDefinitionHolder decorateIfRequired(      Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {   String namespaceUri = getNamespaceURI(node);   if (!isDefaultNamespace(namespaceUri)) {// 如果元素是自定义元素,则根据命名空间找到对应的命名空间处理器      NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);      if (handler != null) { // 自定义命名空间处理器处理bdHolder         return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));      }   }   return originalDef;}
复制代码

3)BeanDefinition 的注册

BeanDefinition 注册分为通过 beanName 注册 BeanDefinition 和注册别名两部分。进入 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry())方法内部,如下

public static void registerBeanDefinition(      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)      throws BeanDefinitionStoreException {   String beanName = definitionHolder.getBeanName();// 通过beanName注册BeanDefinition   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());   String[] aliases = definitionHolder.getAliases();   if (aliases != null) {      for (String alias : aliases) { // 注册别名         registry.registerAlias(beanName, alias);      }   }}
复制代码

通过 beanName 注册 BeanDefinition 主要进行了 4 个步骤:

  1. 对 AbstractBeanDefinition 的校验,主要是对于 AbstractBeanDefinition 的 methodOverrides 属性的

  2. 对于 beanName 已经注册的情况的处理,如果已经设置了不允许 bean 的覆盖,会抛出异常,否则进行覆盖

  3. 加入 Map 缓存,beanName 为 key,BeanDefinition 为 value

  4. 清除之前留下的对应 beanName 的缓存

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {   if (beanDefinition instanceof AbstractBeanDefinition) { // 对AbstractBeanDefinition的校验      ((AbstractBeanDefinition) beanDefinition).validate();   }   BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName);   if (oldBeanDefinition != null) {      if (!isAllowBeanDefinitionOverriding()) { // 如果beanName已经注册,并且设置了不允许bean覆盖,会抛出异常         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,               "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +               "': There is already [" + oldBeanDefinition + "] bound.");      } // 将beanDefinition存到Map,beanName为key      this.beanDefinitionMap.put(beanName, beanDefinition);   }   else {      if (hasBeanCreationStarted()) {         synchronized (this.beanDefinitionMap) { // 将beanDefinition存到Map,beanName为key            this.beanDefinitionMap.put(beanName, beanDefinition);         }      }      else {         // 将beanDefinition存到Map,beanName为key         this.beanDefinitionMap.put(beanName, beanDefinition);      }   }   if (oldBeanDefinition != null || containsSingleton(beanName)) {     // 清除之前留下的对应beanName的缓存 resetBeanDefinition(beanName);   }}
复制代码

注册别名的原理相对简单,分为 4 个步骤:

  1. alias 和 beanName 相同情况处理,此时会删除原有的 alias

  2. alias 覆盖处理,若 aliasName 已经使用了并已经指向了另一个 beanName,且设置了别名不能覆盖,则会抛出异常

  3. alias 循环检查,如果出现了别名循环的情况,则抛出异常

  4. 注册 alias

public void registerAlias(String name, String alias) {    if (alias.equals(name)) {        this.aliasMap.remove(alias);    } else {        String registeredName = (String)this.aliasMap.get(alias);        if (registeredName != null) {            if (registeredName.equals(name)) {                return;            } // 若aliasName已经使用了并已经指向了另一个beanName,且设置了别名不能覆盖,则会抛出异常            if (!this.allowAliasOverriding()) {                throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'.");            }        }// alias循环检查        this.checkForAliasCircle(name, alias);// alias注册        this.aliasMap.put(alias, name);    }
}
复制代码

4)通知监听器解析及注册完成

通过代码 this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))完成此功能。这里的实现只是为了扩展,当需要对注册 BeanDefinition 事件进行监听时可以通过注册监听器的方式并将处理逻辑写入监听器中,目前 Spring 没有对此事件进行任何处理。

2.7.2 alias 标签的解析

对 alias 标签的解析是通过 processAliasRegistration(ele)方法处理的。

protected void processAliasRegistration(Element ele) {   String name = ele.getAttribute(NAME_ATTRIBUTE);   String alias = ele.getAttribute(ALIAS_ATTRIBUTE);   boolean valid = true;   if (!StringUtils.hasText(name)) {      valid = false;   }   if (!StringUtils.hasText(alias)) {      valid = false;   }   if (valid) { // alias标签解析      getReaderContext().getRegistry().registerAlias(name, alias); // 通知监听器      getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));   }}
复制代码

进入方法内部可以看到,this.getReaderContext().getRegistry().registerAlias(name, alias)方法实现了 alias 标签的解析,而这个方法实际就是前面注册别名的那个方法。this.getReaderContext().fireAliasRegistered(name, alias, this.extractSource(ele))这个方法用于在别名注册后通知监听器做相应的处理,这里的实现只是为了扩展,目前 Spring 没有对此事件进行任何处理。

2.7.3 import 标签的解析

对 import 标签的解析,Spring 大致分为以下步骤:

  1. 获取 import 标签的 resource 属性配置的路径

  2. 解析路径中的系统属性,格式如“${user.dir}”,对应方法 this.getReaderContext().getEnvironment().resolveRequiredPlaceholders(location)

  3. 判断 resource 属性配置的路径是绝对路径还是相对路径

  4. 如果是绝对路径,则调用 bean 的解析过程进行解析

  5. 如果是相对路径则计算出绝对路径后进行解析

  6. 通知监听器,解析完成

不管是绝对路径下 import 标签的解析还是相对路径下 import 标签的解析,通过跟踪代码发现,最后都会调到 XmlBeanDefinitionReader 类的 loadBeanDefinitions 方法,而这个方法在加载 bean 部分已经了解了。

2.7.4 嵌入式 beans 标签的解析

该标签解析调用的是 DefaultBeanDefinitionDocumentReader 的 doRegisterBeanDefinitions 方法,而这个方法已经在解析及注册 BeanDefinitions 部分了解了。嵌入式 beans 标签和非嵌入式 beans 标签的解析过程其实是一样的。

2.8 自定义标签的解析

自定义标签非常有用,我们熟知的标签就是采用自定义标签的原理实现的。下面来探究一下自定义标签的解析原理。前面提到过解析自定义标签的入口,查看以下具体实现:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {    String namespaceUri = this.getNamespaceURI(ele);    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);    if (handler == null) {        return null;    } else {        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));    }}
复制代码

这里传入的 containingBd 为 null。可以看到自定义标签解析的思路特别简单。无非是根据标签元素获取对应的命名空间,根据命名空间解析对应的处理器,然后根据用户自定义的处理器进行解析。

2.8.1 解析自定义标签处理器

通过元素可以获取它的命名空间,有了命名空间就可以进行 NamespaceHandler 提取了。在 readerContext 初始化的时候其属性 namespaceHandlerResolver 被初始化为 DefaultNamespaceHandlerResolver 的实例。所以调用 resolve 方法实际调用的是 DefaultNamespaceHandlerResolver 的方法。

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;      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;   }}
private Map<String, Object> getHandlerMappings() { if (this.handlerMappings == null) { synchronized (this) { if (this.handlerMappings == null) { // 加载配置文件META-INF/spring.handlers Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } } } return this.handlerMappings;}
复制代码

可以看到自定义标签处理器的解析流程,首先通过 this.getHandlerMappings()方法解析配置文件获取命名空间到处理器的映射关系,Map 保存。然后实例化该命名空间处理器,调用 init()初始化方法。而定义的 this.handlerMappingsLocation 变量在调用构造方法的时候代码写死了,是 META-INF/spring.handlers。这个配置文件是用户自己去编写的,定义命名空间到处理器类的映射。命名空间处理器类的实现也是需要用户去实现,用户可以继承 NamespaceHandlerSupport 抽象类实现一下 init()抽象方法。

2.8.2 标签解析

我们已经得到了由哪个标签处理器进行处理,接下来标签解析由 handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))去实现。

public BeanDefinition parse(Element element, ParserContext parserContext) {// 获取元素解析器,进行解析   return findParserForElement(element, parserContext).parse(element, parserContext);}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { // 根据节点名称获取parserString localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = this.parsers.get(localName); return parser;}
复制代码

在父类 NamespaceHandlerSupport 中可以看到,解析功能首先找到解析器,然后进行解析。查找解析器首先获取节点名称,然后通过 Map parsers 获取对应节点的解析器。而这个 Map 的赋值一般在用户实现的命名空间处理器 init()方法中调用。

而自定义标签的解析任务由 parse 方法完成。可以看到,首先通过 parseInternal 方法将标签元素转换成了 BeanDefinition,然后解析 id 和 name 属性并用 BeanDefinitionHolder 封装元素信息,接着进行 BeanDefinition 的注册,最后通知监听器。

public final BeanDefinition parse(Element element, ParserContext parserContext) { // 解析元素    AbstractBeanDefinition definition = this.parseInternal(element, parserContext);    if (definition != null && !parserContext.isNested()) { // 解析id         String id = this.resolveId(element, definition, parserContext);         String[] aliases = null;         if (this.shouldParseNameAsAliases()) {  // 解析name             String name = element.getAttribute("name");             if (StringUtils.hasLength(name)) {                 aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));             }         } // holder封装元素信息         BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);         // 注册 this.registerBeanDefinition(holder, parserContext.getRegistry());         if (this.shouldFireEvents()) {             BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);             this.postProcessComponentDefinition(componentDefinition);  // 通知监听器             parserContext.registerComponent(componentDefinition);         }    }    return definition;}
复制代码

对于后面三步前面都介绍过了,只需看下 parseInternal 方法逻辑。

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();    // 解析parentName String parentName = this.getParentName(element);    if (parentName != null) {        builder.getRawBeanDefinition().setParentName(parentName);    } // 解析beanClass    Class<?> beanClass = this.getBeanClass(element);    if (beanClass != null) {        builder.getRawBeanDefinition().setBeanClass(beanClass);    } else {        String beanClassName = this.getBeanClassName(element);        if (beanClassName != null) {            builder.getRawBeanDefinition().setBeanClassName(beanClassName);        }    } // 解析source    builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));    if (parserContext.isNested()) {        builder.setScope(parserContext.getContainingBeanDefinition().getScope());    } // 解析lazyInit    if (parserContext.isDefaultLazyInit()) {        builder.setLazyInit(true);    }
this.doParse(element, parserContext, builder); return builder.getBeanDefinition();}
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { this.doParse(element, builder);}
protected void doParse(Element element, BeanDefinitionBuilder builder) {}
复制代码

可以看到,parseInternal 方法实际就是先解析 parentName、beanClass、source、scope 和 lazyInit,以 BeanDefinition 封装。然后调用 doParse 方法,这个方法也是用户自定义解析器需要实现的方法。最后返回这个 BeanDefinition。

3 总结

至此,我们一步一步地分析了 Spring 容器创建基本原理的所有内容。没想到短短一句 new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”)),Spring 做了这么多事:配置文件的封装、Document 获取和解析及注册 BeanDefinition 等。解析及注册 BeanDefinition 对默认标签和自定义标签进行不同的处理。对于默认标签,又分别对 bean 标签、alias 标签、import 标签和 beans 标签进行不同的处理。其中 bean 标签解析逻辑最为复杂也最为基础,而剩下的那几个标签又复用了 bean 标签处理的部分逻辑。

我们从中不仅可以学到 Spring 容器创建的基本原理,还可以学到许多编码规范及技巧,了解到好的代码是什么样子的。比如其中应用到的单一职责原则、模板方法模式等等。而且还可以发现,它的代码逻辑很清晰,往往通过它的方法名称就知道这个方法的功能,并且每个方法也不会特别长,增加了代码的可读性和可维护性。并且代码封装性很好,很多复杂的功能代码都可以复用。

在我们之后的开发工作中需要不断学习好的编码技巧及规范,应用到日常开发工作当中,最终形成我们自己的编码技巧及风格。

4 参考资料

《Spring 技术内幕:深入解析 Spring 架构与设计原理》《Spring 源码深度解析》


作者:曹铭(大件技术工坊)

发布于: 刚刚阅读数: 3
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
源码学习之Spring容器创建原理_xml_京东科技开发者_InfoQ写作社区