Spring 容器的基本实现

作者:ToPossessLight0902日期:2025/11/9

1、基本用法

1public class MySpringBean {
2    private String beanName = "beanName";
3
4    public String getBeanName() {
5        return beanName;
6    }
7
8    public void setBeanName(String beanName) {
9        this.beanName = beanName;
10    }
11}
12
1<beans xmlns="http://www.springframework.org/schema/beans"
2       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3       xmlns:context="http://www.springframework.org/schema/context"
4       xsi:schemaLocation="http://www.springframework.org/schema/beans
5       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
6
7    <bean id="myBeanName" class="com.reray.spring.study.MySpringBean"/>
8
9</beans>
10
1@Test
2public void test() {
3    XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml"));
4    MySpringBean beanName = (MySpringBean) xmlBeanFactory.getBean("myBeanName");
5    System.out.println(beanName.getBeanName());
6}
7

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

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

上面代码执行逻辑

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

2.3.1 配置文件封装

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

1public interface InputStreamSource {
2    InputStream getInputStream() throws IOException;
3}
4
1public interface Resource extends InputStreamSource {
2    boolean exists();
3    boolean isReadable();
4    boolean isOpen();
5    URL getURL() throws IOException;
6    URI getURI() throws IOException;
7    File getFile() throws IOException;
8    long contentLength() throws IOException;
9    long lastModified() throws IOException;
10    Resource createRelative(String var1) throws IOException;
11    String getFilename();
12    String getDescription();
13}
14

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 处理。

1public XmlBeanFactory(Resource resource) throws BeansException {
2   this(resource, null);
3}
4
5public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
6   super(parentBeanFactory);
7   this.reader.loadBeanDefinitions(resource);
8}
9

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

1public AbstractAutowireCapableBeanFactory() {
2   super();
3   ignoreDependencyInterface(BeanNameAware.class);
4   ignoreDependencyInterface(BeanFactoryAware.class);
5   ignoreDependencyInterface(BeanClassLoaderAware.class);
6}
7

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
1public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
2   return loadBeanDefinitions(new EncodedResource(resource));
3}
4

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

1public Reader getReader() throws IOException {
2    if (this.charset != null) {
3        return new InputStreamReader(this.resource.getInputStream(), this.charset);
4    } else {
5        return this.encoding != null ? new InputStreamReader(this.resource.getInputStream(), this.encoding) : new InputStreamReader(this.resource.getInputStream());
6    }
7}
8

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

1public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
2   Assert.notNull(encodedResource, "EncodedResource must not be null");
3   if (logger.isInfoEnabled()) {
4      logger.info("Loading XML bean definitions from " + encodedResource.getResource());
5   }
6   
7   // 记录已加载的资源
8   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
9   if (currentResources == null) {
10      currentResources = new HashSet<EncodedResource>(4);
11      this.resourcesCurrentlyBeingLoaded.set(currentResources);
12   }
13   if (!currentResources.add(encodedResource)) {
14      throw new BeanDefinitionStoreException(
15            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
16   }
17   try {
18      // 获取 InputStream
19      InputStream inputStream = encodedResource.getResource().getInputStream();
20      try {
21         // 封装为 org.xml.sax.InputSource 便于后面处理 xml 文件
22         InputSource inputSource = new InputSource(inputStream);
23         if (encodedResource.getEncoding() != null) {
24            inputSource.setEncoding(encodedResource.getEncoding());
25         }
26         // 核心处理部分
27         return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
28      }
29      finally {
30         inputStream.close();
31      }
32   }
33   catch (IOException ex) {
34      throw new BeanDefinitionStoreException(
35            "IOException parsing XML document from " + encodedResource.getResource(), ex);
36   }
37   finally {
38      currentResources.remove(encodedResource);
39      if (currentResources.isEmpty()) {
40         this.resourcesCurrentlyBeingLoaded.remove();
41      }
42   }
43}
44

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

1protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
2      throws BeanDefinitionStoreException {
3   try {
4      int validationMode = getValidationModeForResource(resource);
5      Document doc = this.documentLoader.loadDocument(
6            inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
7      return registerBeanDefinitions(doc, resource);
8   }
9   catch (BeanDefinitionStoreException ex) {
10      throw ex;
11   }
12   catch (SAXParseException ex) {
13      throw new XmlBeanDefinitionStoreException(resource.getDescription(),
14            "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
15   }
16   catch (SAXException ex) {
17      throw new XmlBeanDefinitionStoreException(resource.getDescription(),
18            "XML document from " + resource + " is invalid", ex);
19   }
20   catch (ParserConfigurationException ex) {
21      throw new BeanDefinitionStoreException(resource.getDescription(),
22            "Parser configuration exception parsing XML from " + resource, ex);
23   }
24   catch (IOException ex) {
25      throw new BeanDefinitionStoreException(resource.getDescription(),
26            "IOException parsing XML document from " + resource, ex);
27   }
28   catch (Throwable ex) {
29      throw new BeanDefinitionStoreException(resource.getDescription(),
30            "Unexpected exception parsing XML document from " + resource, ex);
31   }
32}
33

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

  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 文件的声明方式如下

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

Spring 的 spring-beans-2.0.dtd 如下

1<!ELEMENT beans (
2   description?,
3   (import | alias | bean)*
4)>
5
6<!ATTLIST beans default-lazy-init (true | false) "false">
7<!ATTLIST beans default-merge (true | false) "false">
8<!ATTLIST beans default-autowire (no | byName | byType | constructor | autodetect) "no">
9<!ATTLIST beans default-dependency-check (none | objects | simple | all) "none">
10<!ATTLIST beans default-init-method CDATA #IMPLIED>
11<!ATTLIST beans default-destroy-method CDATA #IMPLIED>
12
13<!ELEMENT description (#PCDATA)>
14...
15

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 地址
1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4       xsi:schemaLocation="http://www.springframework.org/schema/beans
5       http://www.springframework.org/schema/beans/spring-beans.xsd">
6...
7</beans>
8

Spring 的 spring-beans-3.0.xsd 如下

1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
3<xsd:schema xmlns="http://www.springframework.org/schema/beans"
4      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
5      targetNamespace="http://www.springframework.org/schema/beans">
6
7   <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
8
9   <xsd:annotation>
10....
11

2.4.2 验证模式的读取

1protected int getValidationModeForResource(Resource resource) {
2   // VALIDATION_AUTO
3   int validationModeToUse = getValidationMode();
4   // 如果手动指定了验证模式则使用指定验证模式
5   if (validationModeToUse != VALIDATION_AUTO) {
6      return validationModeToUse;
7   }
8   // 自动检测
9   int detectedMode = detectValidationMode(resource);
10   if (detectedMode != VALIDATION_AUTO) {
11      return detectedMode;
12   }
13   return VALIDATION_XSD;
14}
15

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

1protected int detectValidationMode(Resource resource) {
2   if (resource.isOpen()) {
3      throw new BeanDefinitionStoreException(
4            "Passed-in Resource [" + resource + "] contains an open stream: " +
5            "cannot determine validation mode automatically. Either pass in a Resource " +
6            "that is able to create fresh streams, or explicitly specify the validationMode " +
7            "on your XmlBeanDefinitionReader instance.");
8   }
9
10   InputStream inputStream;
11   try {
12      inputStream = resource.getInputStream();
13   }
14   catch (IOException ex) {
15      throw new BeanDefinitionStoreException(
16            "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
17            "Did you attempt to load directly from a SAX InputSource without specifying the " +
18            "validationMode on your XmlBeanDefinitionReader instance?", ex);
19   }
20
21   try {
22      // 自动检测验证模式
23      return this.validationModeDetector.detectValidationMode(inputStream);
24   }
25   catch (IOException ex) {
26      throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
27            resource + "]: an error occurred whilst reading from the InputStream.", ex);
28   }
29}
30

XmlValidationModeDetectordetectValidationMode(InputStream inputStream) 方法如下

1public int detectValidationMode(InputStream inputStream) throws IOException {
2    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
3
4    byte var4;
5    try {
6        // 是否为 DTD 验证模式
7        boolean isDtdValidated = false;
8
9        while(true) {
10            String content;
11            if ((content = reader.readLine()) != null) {
12                content = this.consumeCommentTokens(content);
13                if (this.inComment || !StringUtils.hasText(content)) {
14                    continue;
15                }
16
17                if (this.hasDoctype(content)) {
18                    isDtdValidated = true;
19                } else if (!this.hasOpeningTag(content)) {
20                    continue;
21                }
22            }
23
24            int var5 = isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD;
25            return var5;
26        }
27    } catch (CharConversionException var9) {
28        var4 = VALIDATION_AUTO;
29    } finally {
30        reader.close();
31    }
32
33    return var4;
34}
35
1private boolean hasDoctype(String content) {
2    return content.indexOf("DOCTYPE") > -1;
3}
4

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

2.5 获取 Document

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

1private DocumentLoader documentLoader = new DefaultDocumentLoader();
2
3Document doc = this.documentLoader.loadDocument(
4      inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
5
1public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
2      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
3
4   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
5   if (logger.isDebugEnabled()) {
6      logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
7   }
8   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
9   return builder.parse(inputSource);
10}
11

通过 SAX 解析 xml 文档

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

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

1protected EntityResolver getEntityResolver() {
2   if (this.entityResolver == null) {
3      ResourceLoader resourceLoader = getResourceLoader();
4      if (resourceLoader != null) {
5         this.entityResolver = new ResourceEntityResolver(resourceLoader);
6      }
7      else {
8         this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
9      }
10   }
11   return this.entityResolver;
12}
13

2.5.1 EntityResolver 用法

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

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

1public interface EntityResolver {
2    public abstract InputSource resolveEntity (String publicId,
3                                               String systemId)
4        throws SAXException, IOException;
5}
6

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

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

1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4       xsi:schemaLocation="http://www.springframework.org/schema/beans
5       http://www.springframework.org/schema/beans/spring-beans.xsd">
6      
7</beans>
8

其中关键部分是 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 验证模式的配置文件,代码如下:

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

上面的两个参数为:

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

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

1public static final String DTD_SUFFIX = ".dtd";
2public static final String XSD_SUFFIX = ".xsd";
3
4public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
5   if (systemId != null) {
6      if (systemId.endsWith(DTD_SUFFIX)) {
7         // DTD 文件从这里解析
8         return this.dtdResolver.resolveEntity(publicId, systemId);
9      }
10      else if (systemId.endsWith(XSD_SUFFIX)) {
11         // XSD 文件从这里解析
12         return this.schemaResolver.resolveEntity(publicId, systemId);
13      }
14   }
15   return null;
16}
17

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

image.png

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

1private static final String DTD_EXTENSION = ".dtd";
2
3private static final String[] DTD_NAMES = {"spring-beans-2.0", "spring-beans"};
4
5public InputSource resolveEntity(String publicId, String systemId) throws IOException {
6   if (logger.isTraceEnabled()) {
7      logger.trace("Trying to resolve XML entity with public ID [" + publicId +
8            "] and system ID [" + systemId + "]");
9   }
10   if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
11      int lastPathSeparator = systemId.lastIndexOf("/");
12      for (String DTD_NAME : DTD_NAMES) {
13         int dtdNameStart = systemId.indexOf(DTD_NAME);
14         if (dtdNameStart > lastPathSeparator) {
15            String dtdFile = systemId.substring(dtdNameStart);
16            if (logger.isTraceEnabled()) {
17               logger.trace("Trying to locate [" + dtdFile + "] in Spring jar");
18            }
19            try {
20               Resource resource = new ClassPathResource(dtdFile, getClass());
21               InputSource source = new InputSource(resource.getInputStream());
22               source.setPublicId(publicId);
23               source.setSystemId(systemId);
24               if (logger.isDebugEnabled()) {
25                  logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
26               }
27               return source;
28            }
29            catch (IOException ex) {
30               if (logger.isDebugEnabled()) {
31                  logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in class path", ex);
32               }
33            }
34
35         }
36      }
37   }
38   return null;
39}
40

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

1public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
2
3public InputSource resolveEntity(String publicId, String systemId) throws IOException {
4   if (logger.isTraceEnabled()) {
5      logger.trace("Trying to resolve XML entity with public id [" + publicId +
6            "] and system id [" + systemId + "]");
7   }
8
9   if (systemId != null) {
10      String resourceLocation = getSchemaMappings().get(systemId);
11      if (resourceLocation != null) {
12         Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
13         try {
14            InputSource source = new InputSource(resource.getInputStream());
15            source.setPublicId(publicId);
16            source.setSystemId(systemId);
17            if (logger.isDebugEnabled()) {
18               logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
19            }
20            return source;
21         }
22         catch (FileNotFoundException ex) {
23            if (logger.isDebugEnabled()) {
24               logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
25            }
26         }
27      }
28   }
29   return null;
30}
31
32/**
33 * Load the specified schema mappings lazily.
34 */
35private Map<String, String> getSchemaMappings() {
36   if (this.schemaMappings == null) {
37      synchronized (this) {
38         if (this.schemaMappings == null) {
39            if (logger.isDebugEnabled()) {
40               logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
41            }
42            try {
43               Properties mappings =
44                     PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
45               if (logger.isDebugEnabled()) {
46                  logger.debug("Loaded schema mappings: " + mappings);
47               }
48               Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
49               CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
50               this.schemaMappings = schemaMappings;
51            }
52            catch (IOException ex) {
53               throw new IllegalStateException(
54                     "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
55            }
56         }
57      }
58   }
59   return this.schemaMappings;
60}
61

2.6 解析及注册 BeanDefinitions

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

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

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

1public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
2   this.readerContext = readerContext;
3   logger.debug("Loading bean definitions");
4   Element root = doc.getDocumentElement();
5   doRegisterBeanDefinitions(root);
6}
7

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

1public static final String PROFILE_ATTRIBUTE = "profile";
2
3protected void doRegisterBeanDefinitions(Element root) {
4   // 处理 profile 属性
5   String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
6   if (StringUtils.hasText(profileSpec)) {
7      Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
8      String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
9            profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
10      if (!this.environment.acceptsProfiles(specifiedProfiles)) {
11         return;
12      }
13   }
14   
15   BeanDefinitionParserDelegate parent = this.delegate;
16   this.delegate = createHelper(this.readerContext, root, parent);
17
18   // 解析前置处理,留给子类实现
19   preProcessXml(root);
20   // 解析并注册
21   parseBeanDefinitions(root, this.delegate);
22   // 解析后置处理,留给子类实现
23   postProcessXml(root);
24
25   this.delegate = parent;
26}
27

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

2.6.1 profile 属性的使用

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

1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4       xsi:schemaLocation="http://www.springframework.org/schema/beans
5       http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7    <beans profile="dev">
8        <bean id="myBeanName" class="com.reray.spring.study.MySpringBeanDev"/>
9    </beans>
10
11    <beans profile="prod">
12        <bean id="myBeanName" class="com.reray.spring.study.MySpringBeanProd"/>
13    </beans>
14
15</beans>
16

2.6.2 解析注册 BeanDefinition

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

1protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
2   if (delegate.isDefaultNamespace(root)) {
3      NodeList nl = root.getChildNodes();
4      for (int i = 0; i < nl.getLength(); i++) {
5         Node node = nl.item(i);
6         if (node instanceof Element) {
7            Element ele = (Element) node;
8            if (delegate.isDefaultNamespace(ele)) {
9               // 对 Bean 处理
10               parseDefaultElement(ele, delegate);
11            }
12            else {
13               // 对 Bean 处理
14               delegate.parseCustomElement(ele);
15            }
16         }
17      }
18   }
19   else {
20      delegate.parseCustomElement(root);
21   }
22}
23

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

1public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
2
3public boolean isDefaultNamespace(String namespaceUri) {
4   return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
5}
6
7public boolean isDefaultNamespace(Node node) {
8   return isDefaultNamespace(getNamespaceURI(node));
9}
10
1public String getNamespaceURI(Node node) {
2   return node.getNamespaceURI();
3}
4

Spring 容器的基本实现》 是转载文章,点击查看原文


相关推荐


Python 的内置函数 hasattr
IMPYLH2025/11/7

Python 内建函数列表 > Python 的内置函数 hasattr Python 的内置函数 hasattr() 用于检查一个对象是否具有指定的属性或方法。该函数的语法为: hasattr(object, name) 参数说明: object:要检查的对象,可以是任何 Python 对象name:要检查的属性或方法名称,以字符串形式传入 返回值: 如果对象具有该属性或方法,返回 True否则返回 False 功能特点: 该函数会在对象及其继承链中查找指定属性对于动态创建的


CSS 的网格布局
hubenchang05152025/11/2

#CSS 的网格布局 CSS 网格布局(Grid Layout)是一个强大的 2D 布局系统,可精确地控制页面的行和列布局,比 Flex 更适合结构化排布。 通过将一个元素样式的 display 属性设为 grid,可以将该元素设为网格布局的 容器。 通过容器的 grid-template-columns 属性可以配置网格的列宽度,通过 grid-template-rows 属性可以配置网络的行高度。 下面这个示例将网格设为二行四列,两行的高度分别为 40px 和 80px,四列的宽度分别为 4


CPU 架构(CPU Architecture)
wenjunna2025/10/31

CPU 架构(CPU Architecture)是指 CPU 的指令集架构(ISA, Instruction Set Architecture),即 CPU 能理解和执行的指令系统。不同架构在设计理念、兼容性、性能与功耗上差别很大。 下面我给你分层次整理一下: 一、主流通用 CPU 架构分类 架构全称特点典型厂商/芯片x86 / x86_64Intel 8086 家族指令集(64 位为 x86_64 / AMD64)高性能、兼容性强、功耗高Intel Core / Xeon、AMD Ryze


某银行大厂面试技术问题深度解析(一)
360_go_php2025/10/28

​ 1. MyBatis的依赖pom包 MyBatis 是一个持久层框架,主要用于简化数据库操作。要在项目中使用 MyBatis,必须在 pom.xml 文件中添加相应的依赖。以下是常用的 MyBatis 依赖: <dependency>     <groupId>org.mybatis</groupId>     <artifactId>mybatis</artifactId>     <version>3.5.5</version> </dependency> <depen


Python 的内置函数 complex
IMPYLH2025/10/25

Python 内建函数列表 > Python 的内置函数 complex Python 的内置函数 complex() 用于创建一个复数对象。复数在数学和科学计算中有着广泛的应用,特别是在信号处理、电气工程和物理学等领域。 class complex(r=0, i=0): ''' 类型转换为 complex :param r: 实部 :param i: 虚部 :return: 转换为 complex 后的值 ''' 示例 # 创建实部为


猿辅导Java面试真实经历与深度总结(三)
360_go_php2025/10/23

​ 在Java面试中,有些问题是每个面试官几乎都会问到的。这些问题涉及到JVM内存管理、Spring框架的核心原理以及一些常见的Java技术点。今天我们来解答一系列常见的Java面试问题,帮助你更好地准备面试。 1. JVM内存区域及内存溢出 JVM内存分为多个区域,每个区域有不同的功能。主要分为以下几个区域:​编辑 程序计数器(Program Counter Register):每个线程有独立的程序计数器,用于指示当前线程所执行的字节码指令的位置。 Java虚拟机栈(JVM Sta


3个技巧让你彻底搞懂JavaScript异步编程
良山有风来2025/10/22

你是不是曾经遇到过这样的情况? 页面上的数据加载了半天就是出不来,控制台报了一堆看不懂的错误。代码写着写着就变成了“回调地狱”,一层套一层,自己都看不懂自己写了什么。 别担心,异步编程确实是很多前端开发者的痛点。但今天,我会用最通俗易懂的方式,带你彻底搞懂JavaScript中的异步编程。 读完本文,你不仅能理解回调、Promise和async/await的区别,还能掌握如何在实际项目中优雅地处理异步操作。最重要的是,你会拥有一套清晰的异步编程思路,再也不用害怕处理复杂的异步逻辑了。 什么是异步


基于SpringBoot的高校迎新管理系统
计算机毕设定制辅导-无忧2025/10/20

一、前言         随着高等教育规模的持续扩大和信息化建设的不断推进,传统高校迎新工作面临着流程繁琐、信息分散、效率低下和管理混乱等挑战。人工办理报到手续、纸质材料收集与核对不仅耗时耗力,还容易出现差错,给新生、家长及迎新工作人员带来诸多不便,难以满足现代化高校管理的需求。         为提升迎新工作的组织效率与服务质量,本文设计并实现一套基于SpringBoot的高校迎新管理系统。系统采用SpringBoot框架构建稳定、高效的后端服务,利用Vue.js开发响应迅速、交互友好


MYSQL学习笔记(个人)(第十五天)
chenzhou__2025/10/19

自学《数据库系统原理与应用》——— 黄宝贵、马春梅、禹继国 —— 清华大学出版社 关系规范化理论: 关系规范化的作用:         为了设计一个合理、可靠、简单、正确的关系数据库,形成了关系规范化理论。其包括两个方面的内容:1.数据依赖,研究属性之间的依赖关系;2.范式,是关系模式符合某种级别的标准。 1.问题的提出: 下面有个关系模式: SCD(sno, sname, dname, office, cno, cname, score) 其中按照现实需求和实际应用,可得语义: (


[嵌入式系统-136]:主流AIOT智能体软件技术栈
文火冰糖的硅基工坊2025/10/18

主流AIoT(人工智能物联网)智能体软件技术栈涵盖了硬件、通信、AI算法、安全防护等多个层面,其核心在于通过“感知-分析-决策-执行”的闭环架构,将物联网的连接能力与人工智能的认知能力深度融合。以下从技术架构、关键技术栈、应用场景三个维度进行解析: 一、技术架构:分层解耦与闭环控制 AIoT智能体的技术架构通常分为四层,每层承担特定功能并协同工作: 感知层 功能:通过传感器和终端设备采集环境数据(如温度、湿度、图像、声音等)。关键技术: 传感器技术:高精度、低功耗的传感器(

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0