Spring 容器的基本实现

62 阅读12分钟

1、基本用法

public class MySpringBean {
    private String beanName = "beanName";

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }
}
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="myBeanName" class="com.reray.spring.study.MySpringBean"/>

</beans>
@Test
public void test() {
    XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml"));
    MySpringBean beanName = (MySpringBean) xmlBeanFactory.getBean("myBeanName");
    System.out.println(beanName.getBeanName());
}

2、核心类介绍

2.1 DefaultListableBeanFactory

XmlBeanFactory 继承自 DefaultListableBeanFactory,DefaultListableBeanFactory 是整个 bean 加载的核心部分,是 Spring 注册和加载 bean 的默认实现。XmlBeanFactory 和 DefaultListableBeanFactory 不同地方在于 XmlBeanFactory 使用了自定义的 xml 读取器 XmlBeanDefinitionReader 个性化的 BeanDefinition 读取。DefaultListableBeanFactory 的结构如下

image.png

XmlBeanFactory 对 DefaultListableBeanFactory 进行拓展,主要用于从 xml 文档中读取 BeanDefinition,注册和获取 bean 都是使用的父类的 DefaultListableBeanFactory 方法实现,增加了 XmlBeanDefinitionReader 对资源文件进行读取和注册

2.2 XmlBeanDefinitionReader

XmlBeanDefinitionReader 对资源文件进行读取、解析和注册

image.png

  • ResourceLoader
  • BeanDefinitionReader
  • EnvironmentCapable
  • DocumentLoader
  • AbstractBeanDefinitionReader
  • BeanDefinitionDocumentReader
  • BeanDefinitionParserDelegate

(1)通过 AbstractBeanDefinitionReader 的 ResourceLoader 将资源文件路径转化为 Resource 文件

(2)通过 DocumentLoader 对 Resource 转化为 Document 文件

(3)通过 BeanDefinitionDocumentReader 的实现类 DefaultBeanDefinitionDocumentReader 对 Document 进行解析,并通过 BeanDefinitionParserDelegate 对 Element 进行解析

2.3 容器基础 XmlBeanFactory

XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml"));

上面代码执行逻辑

  1. 调用 ClassPathResource 构造函数创建 Resource 资源文件实例
  2. Resource 传入 XmlBeanFactory 后调用 loadBeanDefinitions

2.3.1 配置文件封装

在 Java 中将不同来源的资源对象抽象为 URL,通过注册不同的 handler(URLStreamHandler)来处理不同来源的资源读取逻辑。Spring 对其内部使用到的资源实现了自己的抽象结构:Resource 接口来封装底层资源。

public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
    boolean exists();
    boolean isReadable();
    boolean isOpen();
    URL getURL() throws IOException;
    URI getURI() throws IOException;
    File getFile() throws IOException;
    long contentLength() throws IOException;
    long lastModified() throws IOException;
    Resource createRelative(String var1) throws IOException;
    String getFilename();
    String getDescription();
}

InputStreamSource 只有 getInputStream() 返回 InputStream,可以返回任何 InputStream 类。Resource 接口抽象了所有 Spring 内部使用到的底层资源,例如 File、URL、ClassPath 等,不同来源的资源文件都有其 Resource 实现:文件(FileSystemResource)、Classpath 资源(ClassPathResource)、URL 资源(UrlResource)、InputStream 资源(InputStreamResource)、Byte 数组(ByteArrayResource)等。

  • exists:存在性
  • isReadable:可读性
  • isOpen:是否处于打开状态
  • getURL:获取 URL
  • getURI:获取 URI
  • getFile:获取 File 类型
  • lastModified:获取资源最后一次被修改的时间戳
  • getFilename:获取文件名
  • createRelative:基于当前资源创建一个相对资源
  • getDescription:在错误处理中打印信息

通过 Resource 相关类对配置文件进行封装后配置文件的读取工作交给 XmlBeanDefinitionReader 处理。

public XmlBeanFactory(Resource resource) throws BeansException {
   this(resource, null);
}

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
   super(parentBeanFactory);
   this.reader.loadBeanDefinitions(resource);
}

this.reader.loadBeanDefinitions(resource) 是资源加载的真正实现,在此之前调用父类的构造函数初始化过程 super(parentBeanFactory),为父类 AbstractAutowireCapableBeanFactory 的构造函数。

public AbstractAutowireCapableBeanFactory() {
   super();
   ignoreDependencyInterface(BeanNameAware.class);
   ignoreDependencyInterface(BeanFactoryAware.class);
   ignoreDependencyInterface(BeanClassLoaderAware.class);
}

ignoreDependencyInterface 的主要功能是忽略给定接口的自动装配功能,自动装配功能比如 A 中有属性 B,A 在初始化的过程中会默认初始化 B,也是 Spring 的一个重要特性。在某些情况下 B 不会被初始化,例如 B 实现了 BeanNameAware 接口,典型应用是通过其他方式解析 Application 上下文注册依赖,类似 BeanFactory 通过 BeanFactoryAware 注入或者 ApplicationContext 通过 ApplicationContextAware 注入。

2.3.2 加载 Bean

XmlBeanDefinitionReader 的 loadBeanDefinitions 方法是整个资源加载的切入点

  1. new EncodedResource(resource)
  2. loadBeanDefinitions(new EncodedResource(resource))
  3. encodedResource.getResource().getInputStream()
  4. new InputSource(inputStream)
  5. doLoadBeanDefinitions(inputSource, encodedResource.getResource())

调用逻辑如下:

  1. 对 Resource 进行 EncodedResource 类封装,目的是考虑 Resource 可能存在的编码要求情况
  2. 获取 Resource 对应的 InputStream 并构造 InputSource,后续通过 SAX 读取 xml 文件
  3. 根据 InputSource 和 Resource 调用核心方法 doLoadBeanDefinitions
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(new EncodedResource(resource));
}

EncodedResource 的作用是对资源文件的编码进行处理,主要逻辑在 getReader 方法中

public Reader getReader() throws IOException {
    if (this.charset != null) {
        return new InputStreamReader(this.resource.getInputStream(), this.charset);
    } else {
        return this.encoding != null ? new InputStreamReader(this.resource.getInputStream(), this.encoding) : new InputStreamReader(this.resource.getInputStream());
    }
}

回到 loadBeanDefinitions(new EncodedResource(resource)) 方法

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   Assert.notNull(encodedResource, "EncodedResource must not be null");
   if (logger.isInfoEnabled()) {
      logger.info("Loading XML bean definitions from " + encodedResource.getResource());
   }
   
   // 记录已加载的资源
   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
   if (currentResources == null) {
      currentResources = new HashSet<EncodedResource>(4);
      this.resourcesCurrentlyBeingLoaded.set(currentResources);
   }
   if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
   }
   try {
      // 获取 InputStream
      InputStream inputStream = encodedResource.getResource().getInputStream();
      try {
         // 封装为 org.xml.sax.InputSource 便于后面处理 xml 文件
         InputSource inputSource = new InputSource(inputStream);
         if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
         }
         // 核心处理部分
         return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
      }
      finally {
         inputStream.close();
      }
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(
            "IOException parsing XML document from " + encodedResource.getResource(), ex);
   }
   finally {
      currentResources.remove(encodedResource);
      if (currentResources.isEmpty()) {
         this.resourcesCurrentlyBeingLoaded.remove();
      }
   }
}

接下来进入核心处理方法 doLoadBeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
   try {
      int validationMode = getValidationModeForResource(resource);
      Document doc = this.documentLoader.loadDocument(
            inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
      return registerBeanDefinitions(doc, resource);
   }
   catch (BeanDefinitionStoreException ex) {
      throw ex;
   }
   catch (SAXParseException ex) {
      throw new XmlBeanDefinitionStoreException(resource.getDescription(),
            "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
   }
   catch (SAXException ex) {
      throw new XmlBeanDefinitionStoreException(resource.getDescription(),
            "XML document from " + resource + " is invalid", ex);
   }
   catch (ParserConfigurationException ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(),
            "Parser configuration exception parsing XML from " + resource, ex);
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(),
            "IOException parsing XML document from " + resource, ex);
   }
   catch (Throwable ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(),
            "Unexpected exception parsing XML document from " + resource, ex);
   }
}

不考虑异常处理,上面代码逻辑如下:

  1. 获取 xml 文件的验证模式 getValidationModeForResource
  2. 加载 xml 文件,获取对应的 Document loadDocument
  3. 根据 Document 注册 Bean 信息 registerBeanDefinitions

接下来对这三个步骤进行分析

2.4 获取 xml 的验证模式

xml 文件的验证模式保证 xml 文件的正确性,常见的验证模式为 DTD 和 XSD

2.4.1 DTD 和 XSD 区别

DTD(Document Type Definition)即文档类型定义,是一种 xml 约束模式语言,是 xml 文件的验证机制,属于 xml 文件组成的一部分。DTD 是一种保证 xml 文档格式正确的有效方法,可以通过比较 xml 文档和 DTD 文件来看 xml 是否符合规范,元素和标签是否正确。DTD 文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。

Spring 中使用 DTD 文件的声明方式如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
        "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
...
</beans>

Spring 的 spring-beans-2.0.dtd 如下

<!ELEMENT beans (
   description?,
   (import | alias | bean)*
)>

<!ATTLIST beans default-lazy-init (true | false) "false">
<!ATTLIST beans default-merge (true | false) "false">
<!ATTLIST beans default-autowire (no | byName | byType | constructor | autodetect) "no">
<!ATTLIST beans default-dependency-check (none | objects | simple | all) "none">
<!ATTLIST beans default-init-method CDATA #IMPLIED>
<!ATTLIST beans default-destroy-method CDATA #IMPLIED>

<!ELEMENT description (#PCDATA)>
...

XML Schemas 是 XSD(XML Schemas Definition),描述了 xml 文档的结构,可以来指定 xml 文档所允许的结构,并由此来校验 xml 文档的结构是否是有效的。XML Schemas 本身就是一个 xml 文档,符合 xml 的语法结构,可以用通用的 xml 解析器解析。

XML Schemas 需要声明的部分如下:

  • 名称空间 xmlns="http://www.springframework.org/schema/beans
  • XML Schemas 的存储位置 schemaLocation xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    • 名称空间 URI
    • 该名称空间所标识的 XML Schemas 文件位置或 URL 地址
<?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.xsd">
...
</beans>

Spring 的 spring-beans-3.0.xsd 如下

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://www.springframework.org/schema/beans">

   <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>

   <xsd:annotation>
....

2.4.2 验证模式的读取

protected int getValidationModeForResource(Resource resource) {
   // VALIDATION_AUTO
   int validationModeToUse = getValidationMode();
   // 如果手动指定了验证模式则使用指定验证模式
   if (validationModeToUse != VALIDATION_AUTO) {
      return validationModeToUse;
   }
   // 自动检测
   int detectedMode = detectValidationMode(resource);
   if (detectedMode != VALIDATION_AUTO) {
      return detectedMode;
   }
   return VALIDATION_XSD;
}

上面代码如果指定了验证模式(可通过 XmlBeanDefinitionReaderpublic void setValidationMode(int validationMode) 指定)则用指定的验证模式,否则通过 detectValidationMode 自动检测,自动检测验证模式的有专门处理类 XmlValidationModeDetectordetectValidationMode(InputStream inputStream) 实现

protected int detectValidationMode(Resource resource) {
   if (resource.isOpen()) {
      throw new BeanDefinitionStoreException(
            "Passed-in Resource [" + resource + "] contains an open stream: " +
            "cannot determine validation mode automatically. Either pass in a Resource " +
            "that is able to create fresh streams, or explicitly specify the validationMode " +
            "on your XmlBeanDefinitionReader instance.");
   }

   InputStream inputStream;
   try {
      inputStream = resource.getInputStream();
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(
            "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
            "Did you attempt to load directly from a SAX InputSource without specifying the " +
            "validationMode on your XmlBeanDefinitionReader instance?", ex);
   }

   try {
      // 自动检测验证模式
      return this.validationModeDetector.detectValidationMode(inputStream);
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
            resource + "]: an error occurred whilst reading from the InputStream.", ex);
   }
}

XmlValidationModeDetectordetectValidationMode(InputStream inputStream) 方法如下

public int detectValidationMode(InputStream inputStream) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

    byte var4;
    try {
        // 是否为 DTD 验证模式
        boolean isDtdValidated = false;

        while(true) {
            String content;
            if ((content = reader.readLine()) != null) {
                content = this.consumeCommentTokens(content);
                if (this.inComment || !StringUtils.hasText(content)) {
                    continue;
                }

                if (this.hasDoctype(content)) {
                    isDtdValidated = true;
                } else if (!this.hasOpeningTag(content)) {
                    continue;
                }
            }

            int var5 = isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD;
            return var5;
        }
    } catch (CharConversionException var9) {
        var4 = VALIDATION_AUTO;
    } finally {
        reader.close();
    }

    return var4;
}
private boolean hasDoctype(String content) {
    return content.indexOf("DOCTYPE") > -1;
}

Spring 自动检测验证模式的方法是判断是否包含 DOCTYPE,若包含则为 DTD,否则为 XSD

2.5 获取 Document

获取到 xml 验证模式后需要进行 Document 加载,使用的是 DocumentLoader 接口的 DefaultDocumentLoader 实现类 loadDocument 方法进行加载,代码如下

private DocumentLoader documentLoader = new DefaultDocumentLoader();

Document doc = this.documentLoader.loadDocument(
      inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
   if (logger.isDebugEnabled()) {
      logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
   }
   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
   return builder.parse(inputSource);
}

通过 SAX 解析 xml 文档

  1. 创建 DocumentBuilderFactory
  2. 通过 DocumentBuilderFactory 创建 DocumentBuilder
  3. 使用 DocumentBuilder 进行解析

分析下 loadDocument 方法的 EntityResolver 参数,getEntityResolver 方法代码如下:

protected EntityResolver getEntityResolver() {
   if (this.entityResolver == null) {
      ResourceLoader resourceLoader = getResourceLoader();
      if (resourceLoader != null) {
         this.entityResolver = new ResourceEntityResolver(resourceLoader);
      }
      else {
         this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
      }
   }
   return this.entityResolver;
}

2.5.1 EntityResolver 用法

对于解析一个 xml,SAX 会首先读取 xml 文档上的声明,根据声明去寻找相应的 DTD 定义来对文档进行验证。默认的寻找规则通过网络(声明 DTD 的 URI 地址)进行下载 DTD 声明然后验证。但是下载遇到网络中断或者不可用就会报错。

EntityResolver 的作用是项目本身可以提供寻找 DTD 声明的方法,由程序来实现寻找 DTD 的过程。EntityResolver 接口代码如下:

public interface EntityResolver {
    public abstract InputSource resolveEntity (String publicId,
                                               String systemId)
        throws SAXException, IOException;
}

接收两个参数 publicId 和 systemId,返回 InputSource

(1)如果是 XSD 验证模式的配置文件,代码如下:

<?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.xsd">
      
</beans>

其中关键部分是 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 两个参数为:

  • publicId: null
  • systemId: http://www.springframework.org/schema/beans/spring-beans.xsd

(2)如果是 DTD 验证模式的配置文件,代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans> 
... 
</beans>

上面的两个参数为:

  • publicId: -//SPRING//DTD BEAN 2.0//EN
  • systemId: http://www.springframework.org/dtd/spring-beans-2.0.dtd

如果将验证文件通过网络下载用户体验会有延迟,一般做法是将验证文件放到自己的工程里。在 Spring 中使用的是 DelegatingEntityResolver 作为 EntityResolver 的实现类,resolveEntity 实现方法如下:

public static final String DTD_SUFFIX = ".dtd";
public static final String XSD_SUFFIX = ".xsd";

public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
   if (systemId != null) {
      if (systemId.endsWith(DTD_SUFFIX)) {
         // DTD 文件从这里解析
         return this.dtdResolver.resolveEntity(publicId, systemId);
      }
      else if (systemId.endsWith(XSD_SUFFIX)) {
         // XSD 文件从这里解析
         return this.schemaResolver.resolveEntity(publicId, systemId);
      }
   }
   return null;
}

从上面可以看到,DTD 和 XSD 文件使用不同的解析器进行解析

image.png

加载 DTD 类型的 BeansDtdResolver 截取 systemId 作为最后的 .dtd 然后从当前路径下寻找,代码如下:

private static final String DTD_EXTENSION = ".dtd";

private static final String[] DTD_NAMES = {"spring-beans-2.0", "spring-beans"};

public InputSource resolveEntity(String publicId, String systemId) throws IOException {
   if (logger.isTraceEnabled()) {
      logger.trace("Trying to resolve XML entity with public ID [" + publicId +
            "] and system ID [" + systemId + "]");
   }
   if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
      int lastPathSeparator = systemId.lastIndexOf("/");
      for (String DTD_NAME : DTD_NAMES) {
         int dtdNameStart = systemId.indexOf(DTD_NAME);
         if (dtdNameStart > lastPathSeparator) {
            String dtdFile = systemId.substring(dtdNameStart);
            if (logger.isTraceEnabled()) {
               logger.trace("Trying to locate [" + dtdFile + "] in Spring jar");
            }
            try {
               Resource resource = new ClassPathResource(dtdFile, getClass());
               InputSource source = new InputSource(resource.getInputStream());
               source.setPublicId(publicId);
               source.setSystemId(systemId);
               if (logger.isDebugEnabled()) {
                  logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
               }
               return source;
            }
            catch (IOException ex) {
               if (logger.isDebugEnabled()) {
                  logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in class path", ex);
               }
            }

         }
      }
   }
   return null;
}

加载 XSD 类型的 PluggableSchemaResolver 默认从 META-INF/spring.schemas 文件中找到 systemId 对应的 XSD 文件并加载,代码如下:

public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";

public InputSource resolveEntity(String publicId, String systemId) throws IOException {
   if (logger.isTraceEnabled()) {
      logger.trace("Trying to resolve XML entity with public id [" + publicId +
            "] and system id [" + systemId + "]");
   }

   if (systemId != null) {
      String resourceLocation = getSchemaMappings().get(systemId);
      if (resourceLocation != null) {
         Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
         try {
            InputSource source = new InputSource(resource.getInputStream());
            source.setPublicId(publicId);
            source.setSystemId(systemId);
            if (logger.isDebugEnabled()) {
               logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
            }
            return source;
         }
         catch (FileNotFoundException ex) {
            if (logger.isDebugEnabled()) {
               logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
            }
         }
      }
   }
   return null;
}

/**
 * Load the specified schema mappings lazily.
 */
private Map<String, String> getSchemaMappings() {
   if (this.schemaMappings == null) {
      synchronized (this) {
         if (this.schemaMappings == null) {
            if (logger.isDebugEnabled()) {
               logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
            }
            try {
               Properties mappings =
                     PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
               if (logger.isDebugEnabled()) {
                  logger.debug("Loaded schema mappings: " + mappings);
               }
               Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
               CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
               this.schemaMappings = schemaMappings;
            }
            catch (IOException ex) {
               throw new IllegalStateException(
                     "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
            }
         }
      }
   }
   return this.schemaMappings;
}

2.6 解析及注册 BeanDefinitions

获取到 Document 后,接下来对 registerBeanDefinitions(doc, resource) 进行分析,代码如下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   // 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   // 设置环境变量
   documentReader.setEnvironment(this.getEnvironment());
   // 实例化 BeanDefinitionReader 时默认传入 BeanDefinitionRegistry,实现是 DefaultListableBeanFactory。记录统计前的 BeanDefinition 的加载个数
   int countBefore = getRegistry().getBeanDefinitionCount();
   // 加载及注册 bean
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   // 记录本次加载的 BeanDefinition 个数
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

BeanDefinitionDocumentReader 是一个接口,实例化为 DefaultBeanDefinitionDocumentReader,进入后发现核心方法 registerBeanDefinitions 的重要目的之一是提取 root,以便再次使用 root 作为参数继续 BeanDefinition 的注册,代码如下:

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   logger.debug("Loading bean definitions");
   Element root = doc.getDocumentElement();
   doRegisterBeanDefinitions(root);
}

可以看到核心逻辑的底部是 doRegisterBeanDefinitions 方法,代码如下:

public static final String PROFILE_ATTRIBUTE = "profile";

protected void doRegisterBeanDefinitions(Element root) {
   // 处理 profile 属性
   String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
   if (StringUtils.hasText(profileSpec)) {
      Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
      String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
            profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      if (!this.environment.acceptsProfiles(specifiedProfiles)) {
         return;
      }
   }
   
   BeanDefinitionParserDelegate parent = this.delegate;
   this.delegate = createHelper(this.readerContext, root, parent);

   // 解析前置处理,留给子类实现
   preProcessXml(root);
   // 解析并注册
   parseBeanDefinitions(root, this.delegate);
   // 解析后置处理,留给子类实现
   postProcessXml(root);

   this.delegate = parent;
}

可以看到 preProcessXmlpostProcessXml 代码为空,这是面向对象设计方法中,一个类要么是面向继承设计,要么是 final 修饰,而 DefaultBeanDefinitionDocumentReader 没有被修饰为 final,这也是模板方法模式,子类可以继承 DefaultBeanDefinitionDocumentReader 重写这两个方法在 Bean 解析前后做出一些处理。

2.6.1 profile 属性的使用

profile 属性可以根据不同的环境(例如开发、测试、生产)来激活不同的配置,在上面的代码方法中,Spring 会先获取 beans 是否定义了 profile 属性,如果定义了需要从环境变量中获取,因此断言 environment 不为空,因为 profile 可能为多个,因此拆分解析验证每个 profile 是否符合环境变量中的定义。例子如下:

<?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.xsd">

    <beans profile="dev">
        <bean id="myBeanName" class="com.reray.spring.study.MySpringBeanDev"/>
    </beans>

    <beans profile="prod">
        <bean id="myBeanName" class="com.reray.spring.study.MySpringBeanProd"/>
    </beans>

</beans>

2.6.2 解析注册 BeanDefinition

处理完 profile 后对 xml 读取,进入 parseBeanDefinitions(root, this.delegate) 代码如下:

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)) {
               // 对 Bean 处理
               parseDefaultElement(ele, delegate);
            }
            else {
               // 对 Bean 处理
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}

Spring 中 xml 配置有两种 Bean 声明方式,默认的和自定义的。如果节点使用默认命名空间则采用 parseDefaultElement 进行解析,否则使用 parseCustomElement 解析。判断是默认的还是自定义的则使用 delegate.isDefaultNamespace 进行判断,使用 node.getNamespaceURI() 获取命名空间后和 http://www.springframework.org/schema/beans 进行对比

public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

public boolean isDefaultNamespace(String namespaceUri) {
   return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}

public boolean isDefaultNamespace(Node node) {
   return isDefaultNamespace(getNamespaceURI(node));
}
public String getNamespaceURI(Node node) {
   return node.getNamespaceURI();
}