Spring 容器的基本实现

1、基本用法

java 复制代码
public class MySpringBean {
    private String beanName = "beanName";

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }
}
xml 复制代码
<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>
java 复制代码
@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 的结构如下

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

2.2 XmlBeanDefinitionReader

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

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

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

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

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

2.3 容器基础 XmlBeanFactory

java 复制代码
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml"));

上面代码执行逻辑

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

2.3.1 配置文件封装

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

java 复制代码
public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}
java 复制代码
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 处理。

java 复制代码
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 的构造函数。

java 复制代码
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
java 复制代码
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(new EncodedResource(resource));
}

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

java 复制代码
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)) 方法

java 复制代码
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

java 复制代码
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 复制代码
<?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 如下

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 复制代码
<?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 如下

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 验证模式的读取

java 复制代码
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) 实现

java 复制代码
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) 方法如下

java 复制代码
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;
}
java 复制代码
private boolean hasDoctype(String content) {
    return content.indexOf("DOCTYPE") > -1;
}

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

2.5 获取 Document

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

java 复制代码
private DocumentLoader documentLoader = new DefaultDocumentLoader();

Document doc = this.documentLoader.loadDocument(
      inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
java 复制代码
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 方法代码如下:

java 复制代码
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 接口代码如下:

java 复制代码
public interface EntityResolver {
    public abstract InputSource resolveEntity (String publicId,
                                               String systemId)
        throws SAXException, IOException;
}

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

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

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.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 复制代码
<?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 实现方法如下:

java 复制代码
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 文件使用不同的解析器进行解析

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

java 复制代码
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 文件并加载,代码如下:

java 复制代码
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) 进行分析,代码如下:

java 复制代码
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 的注册,代码如下:

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

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

java 复制代码
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 复制代码
<?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) 代码如下:

java 复制代码
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 进行对比

java 复制代码
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));
}
java 复制代码
public String getNamespaceURI(Node node) {
   return node.getNamespaceURI();
}
相关推荐
程序定小飞5 小时前
基于springboot的学院班级回忆录的设计与实现
java·vue.js·spring boot·后端·spring
郝开7 小时前
Spring Boot 2.7.18(最终 2.x 系列版本)1 - 技术选型:连接池技术选型对比;接口文档技术选型对比
java·spring boot·spring
知兀7 小时前
【Spring/SpringBoot】SSM(Spring+Spring MVC+Mybatis)方案、各部分职责、与Springboot关系
java·spring boot·spring
伊布拉西莫9 小时前
Spring 6.x HTTP interface 使用说明
spring·restclient
YDS82911 小时前
苍穹外卖 —— Spring Cache和购物车功能开发
java·spring boot·后端·spring·mybatis
Elieal11 小时前
Spring 框架核心技术全解析
java·spring·sqlserver
组合缺一11 小时前
(对标 Spring)OpenSolon v3.7.0, v3.6.4, v3.5.8, v3.4.8 发布(支持 LTS)
java·后端·spring·web·solon
♡喜欢做梦12 小时前
Spring IOC
java·后端·spring
葡萄城技术团队21 小时前
迎接下一代 React 框架:Next.js 16 核心能力解读
javascript·spring·react.js