《Spring源码深度解析 第2版 郝佳》阅读总结 Spring的整体架构 容器的基本实现 XML文件的加载 Spring5.3.21源码阅读

书籍:《Spring源码深度解析 第2版 郝佳》

Spring源码版本5.3.21

源码地址:spring-projects/spring-framework: Spring Framework (github.com)

建议在早上、安静的地方阅读


一、Spring的整体架构

Spring框架是一个分层架构,它包含一系列的功能要素,并被分为大约20个模块。

image-20220708143416291

这些模块被总结为以下几部分:

  • Core Container
  • Data Access/Integration
  • Web
  • AOP

1.Core Cotainer

核心容器。Core 和 Bean 是框架的基础部分,提供 IoC(控制反转)和依赖注入特性。

  • Core:包含 spring 框架的基本的核心工具类,其他组件的基本核心。
  • Bean:包含访问配置文件、创建和管理 bean 以及 IoC/DI 操作相关的所有类。
  • Context:于 Core 和 Bean 上建立。提供了一种类似于 JNDI 注册器的框架式的对象访问方法,继承了 Beans 的特性,为 spring 提供了大量的扩展。ApplicationContext 接口是 Context 模块的关键。
  • Expression Language:提供了强大的表达式语言,在运行时查询和操纵对象。支持 设置、获取属性的值、属性的分配、方法的调用、访问数组上下文等。

2.Data Access/Integration

数据库访问相关

  • JDBC:为不同的数据库连接访问提供抽象类。
  • ORM:为流行的对象——映射API(JPA\JDO\Hibernate等)提供了一个交互层。
  • OXM:提供了一个对Object/XML映射实现的抽象层。
  • JMS:包含一些制造和雄安飞消息的特性。
  • Transaction:支持编程和声明性的事务管理,这些事务必须实现特定的接口,对所有POJO都适用。

3.Web

建立在应用上下文之上

  • Web:提供了基础的面向Web的集成特性。如多文件上传。
  • Web-Servlet:web.servlet.jar 包含了Spring的MVC的实现。
  • Web-Status:提供了对Status的支持,使得类在Spring应用中能够与一个典型的Status Web 层集成在一起。
  • Web-Porlet:提供了用于Porlet环境和Web-Senlet模块的MVC的实现。

4.AOP

Spring AOP 直接将面向切面编程功能集成到了spring框架中。

  • Aspects:提供了对Aspect的集成支持
  • Instrumentation:提供了class instrumentation 支持和class loader 实现,使得可以在特点的应用服务器上实现

二、容器的基本实现

  1. 读取配置文件 beanFactoryTest.xml
  2. 根据 beanFactoryTest.xml 中的配置找到对应的类的配置,并实例化
  3. 调用实例化后的实例
@Test
public void testSimpleLoad(){
    BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
    MyTestBean bean = (MyTestBean) bf.getBean("myTestBean");
    assertEquals("testStr",bean.getTestStr());
}

=============================beanFactoryTest.xml=======================================
<bean id="myTestBean" class="bean.MyTestBean"/>

先主要来看这行代码:

 BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));

XmlBeanFactory 继承于 DefaultListableBeanFactory,所以接下来先学习DefaultListableBeanFactory。

beans包下的两个核心类

  • DefaultListableBeanFactory
  • XmlBeanDefinitionReader

1.核心类-DefaultListableBeanFactory

如果根据书上的定义和自己的结构图来看一个一个对应太慢了,所以做了一张图来看起来方便点。根据自己不理解、想了解的知识有根据的查看源码。

image-20220710110128118

而我们创建获取bean配置的XmlBeanFactory 就继承了DefaultListableBeanFactory

image-20220710110813220

在XmlBeanFactory 的源码中,我们可以看到XmlBeanDefinitionReader实例对象被创建了。

官方定义:XmlBeanDefinitionReader是用于XML Bean定义的Bean定义读取器。

接下来学习XmlBeanDefinitionReader

image-20220710111035568

2.核心类-XmlBeanDefinitionReader

image-20220710115040102

img

3.容器的基础XmlBeanFactory

接下类深入分析以下功能的代码实现:

 BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml")); 

ClassPathResource()

new ClassPathResource(“beanFactoryTest.xml”)

image-20220710191417413

InputStreamSource 封装任何能返回InputStream的类

public interface InputStreamSource {
    
   InputStream getInputStream() throws IOException;

}

Resource

  • 对不同的资源文件都有响应的Resource实现:文件、ClassPath资源、URL资源、InputStream资源等
  • 定义了三个判断当前资源状态的方法
  • 提供了不同资源URL、URI、File类型的转换
  • 获取资源属性的方法(getFileName() 获取不带路径的文件名)
public interface Resource extends InputStreamSource {

   boolean exists();

   default boolean isReadable() {
      return exists();
   }

   default boolean isOpen() {
      return false;
   }

   default boolean isFile() {
      return false;
   }

   URL getURL() throws IOException;

   URI getURI() throws IOException;

   File getFile() throws IOException;

   default ReadableByteChannel readableChannel() throws IOException {
      return Channels.newChannel(getInputStream());
   }

   long contentLength() throws IOException;

   long lastModified() throws IOException;

   Resource createRelative(String relativePath) throws IOException;

   @Nullable
   String getFilename();

   String getDescription();

}

ClassPathResource 中的实现方法是通过class或者classloader提供的底层方法进行调用的

public class ClassPathResource extends AbstractFileResolvingResource {

   private final String path;

   @Nullable
   private ClassLoader classLoader;

   @Nullable
   private Class<?> clazz;

   public ClassPathResource(String path) {
      this(path, (ClassLoader) null);
   }

   public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
      Assert.notNull(path, "Path must not be null");
      String pathToUse = StringUtils.cleanPath(path);
      if (pathToUse.startsWith("/")) {
         pathToUse = pathToUse.substring(1);
      }
      this.path = pathToUse;
      this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
   }

   public ClassPathResource(String path, @Nullable Class<?> clazz) {
      Assert.notNull(path, "Path must not be null");
      this.path = StringUtils.cleanPath(path);
      this.clazz = clazz;
   }

   @Deprecated
   protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz) {
      this.path = StringUtils.cleanPath(path);
      this.classLoader = classLoader;
      this.clazz = clazz;
   }

   public final String getPath() {
      return this.path;
   }

   @Nullable
   public final ClassLoader getClassLoader() {
      return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
   }

   @Override
   public boolean exists() {
      return (resolveURL() != null);
   }

   @Override
   public boolean isReadable() {
      URL url = resolveURL();
      return (url != null && checkReadable(url));
   }

   @Nullable
   protected URL resolveURL() {
      try {
         if (this.clazz != null) {
            return this.clazz.getResource(this.path);
         }
         else if (this.classLoader != null) {
            return this.classLoader.getResource(this.path);
         }
         else {
            return ClassLoader.getSystemResource(this.path);
         }
      }
      catch (IllegalArgumentException ex) {
         return null;
      }
   }

   @Override
   public InputStream getInputStream() throws IOException {
      InputStream is;
      if (this.clazz != null) {
         is = this.clazz.getResourceAsStream(this.path);
      }
      else if (this.classLoader != null) {
         is = this.classLoader.getResourceAsStream(this.path);
      }
      else {
         is = ClassLoader.getSystemResourceAsStream(this.path);
      }
      if (is == null) {
         throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
      }
      return is;
   }

   @Override
   public URL getURL() throws IOException {
      URL url = resolveURL();
      if (url == null) {
         throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
      }
      return url;
   }

   @Override
   public Resource createRelative(String relativePath) {
      String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
      return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
            new ClassPathResource(pathToUse, this.classLoader));
   }

   @Override
   @Nullable
   public String getFilename() {
      return StringUtils.getFilename(this.path);
   }

   @Override
   public String getDescription() {
      StringBuilder builder = new StringBuilder("class path resource [");
      String pathToUse = this.path;
      if (this.clazz != null && !pathToUse.startsWith("/")) {
         builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
         builder.append('/');
      }
      if (pathToUse.startsWith("/")) {
         pathToUse = pathToUse.substring(1);
      }
      builder.append(pathToUse);
      builder.append(']');
      return builder.toString();
   }

   @Override
   public boolean equals(@Nullable Object other) {
      if (this == other) {
         return true;
      }
      if (!(other instanceof ClassPathResource)) {
         return false;
      }
      ClassPathResource otherRes = (ClassPathResource) other;
      return (this.path.equals(otherRes.path) &&
            ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
            ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
   }

   @Override
   public int hashCode() {
      return this.path.hashCode();
   }

}

​ ClassPathResource 的 getInputStream() 方法是利用 ClassLoader 的 getResourceAsStream(path) 得到 InputStream 进而转化为 Resource

4.XmlBeanFactory详细

再来看XmlBeanFactory是如何处理接收到的Resource资源的

4.1.XmlBeanFactory的构造方法

image-20220710213500274

4.1.1.调用父类的构造方法

在资源加载的真正实现(this.reader.loadBeanDefinitions(resource);)之前,调用了父类构造函数的初始化

跟踪代码到父类AbstractAutowireCapableBeanFactory的构造函数中。

/**
 * Create a new AbstractAutowireCapableBeanFactory.
 */
public AbstractAutowireCapableBeanFactory() {
   super();
    
   // 忽略给定接口的自动转配,Bean、BeanFactory、BeanClassloader可以通过实现下面3个接口的时候进行相应Bean的注入
   //当其他Bean的属性包含上面情况的Bean的时候,上面情况的Bean不会因为依赖注入被自动初始化
   ignoreDependencyInterface(BeanNameAware.class);
   ignoreDependencyInterface(BeanFactoryAware.class);
   ignoreDependencyInterface(BeanClassLoaderAware.class);
    
    
   if (NativeDetector.inNativeImage()) {
      this.instantiationStrategy = new SimpleInstantiationStrategy();
   }
   else {
      this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
   }
}

img

4.1.2 资源加载的真正实现

资源加载的真正实现调用了 XmlBeanDefinitionReader 类型的reader属性提供的方法 :this.reader.loadBeanDefinitions(resource);

在进入XmlBeanDefinitionReader 后的执行流程:

  1. 封装资源文件。对参数Resource使用EncodedResource类进行封装
  2. 获取输入流。从Resource中获取对应的InputStream并构造InputSource
  3. 通过构造的InputSource实例和Resource实例调用函数doLoadBeanDefinitions
1.使用EncodedResource类进行封装

考虑到Resource可能存在编码要求的情况,首先对参数Resource使用EncodedResource类进行封装

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

EncodedResource这个类主要是对资源文件的编码进行处理的。主要体现在getReader()中,当设置了编码属性的时候spring会使用相应的编码作为输入流的编码。

public Reader getReader() throws IOException {
   if (this.charset != null) {
      return new InputStreamReader(this.resource.getInputStream(), this.charset);
   }
   else if (this.encoding != null) {
      return new InputStreamReader(this.resource.getInputStream(), this.encoding);
   }
   else {
      return new InputStreamReader(this.resource.getInputStream());
   }
}
2.获取输入流

上面代码构造了一个有编码(encoding)的InputStreamReader。当构造好encodedResource对象后,再次转入可复用方法,真正的数据准备阶段开始了

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   Assert.notNull(encodedResource, "EncodedResource must not be null");
   if (logger.isTraceEnabled()) {
      logger.trace("Loading XML bean definitions from " + encodedResource);
   }
	//通过属性来记录已经加载的资源
   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

   if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
   }

    //从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的InputStream
   try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
       	//InputSource这个类并不来自于spring,它的全路径是org.xml.sax.InputSource
      InputSource inputSource = new InputSource(inputStream);
      if (encodedResource.getEncoding() != null) {
         inputSource.setEncoding(encodedResource.getEncoding());
      }
       //获取输入流InputSource后,封装和准备工作完成
       //真正进入了逻辑核心部分
      return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(
            "IOException parsing XML document from " + encodedResource.getResource(), ex);
   }
   finally {
      currentResources.remove(encodedResource);
      if (currentResources.isEmpty()) {
         this.resourcesCurrentlyBeingLoaded.remove();
      }
   }
}
3.调用函数doLoadBeanDefinitions

封装资源文件和获取输入流 之后就可以调用函数doLoadBeanDefinitions

在spring5.3.21版本中 将获取对XML文件的验证模式的代码放到了doLoadDocument里,这样做也确实让代码分工显得更加明确

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {

   try {
       //doLoadDocument做了两件事,下面有doLoadDocument的源码
       //1.获取对XML文件的验证模式
       //2.加载XML文件,并得到对应的Document
      Document doc = doLoadDocument(inputSource, resource);
       //3.返回Document注册Bean的信息
      int count = registerBeanDefinitions(doc, resource);
      if (logger.isDebugEnabled()) {
         logger.debug("Loaded " + count + " bean definitions from " + resource);
      }
      return count;
   }
    
    
    //冗长的异常类代码
   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);
   }
}

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    //getValidationModeForResource(resource)获取对XML文件的验证模式
	return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
			getValidationModeForResource(resource), isNamespaceAware());
}

doLoadBeanDefinitions(inputSource,encodedResource.getResource)做了三件事:

  • 1.获取对XML文件的验证模式
  • 2.加载XML文件,并得到对应的Document
  • 3.返回Document注册Bean的信息

这三个步骤支撑这整个Spring容器部分的实现,尤其是第三步对配置文件的解析,逻辑非常复杂,我们先看1.获取XML文件的验证模式。

5.获取XML的验证模式

先来了解上什么是XML文件的验证模式

XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有:DTD和XSD

5.1.DTD 与 XSD

简单了解一下DTD 与 XSD

  • DTD(Document Type Definition)文档类型定义
  • XSD(XML Schemas Definition)文档结构定义
5.1.1.DTD
  • DTD是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分
  • 可以通过比较XML文档和DTD文件来看文档是否符合规范,元素、标签是否使用正确
  • 一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则

要使用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>

image-20220711085810531

5.1.2.XSD

XML Schema 语言就是XSD

  • XML Schema 描述了XML文档的结构。XML Schema 本身就是XML文档,它符合 XML 语法结构

  • 可以用一个指定的XML Schema 来验证某个 XML 文档,以检查XML文档是否符合要求

  • 可以通过XML Schema 指定 XML 文档所允许的结构和内容,并可据此检查 XML 文档是否是有效的

    在使用XML Schema 文档对XML实例文档进行检验时,除了要声明名称空间(xmlns=“http://www.springframework.org/schema/beans”)外,还必须指定该名称空间所对应的XML Schema 文档的存储位置,通过schemaLocation来指定(xsi:schemaLocation=“http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.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 https://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

   <!-- intentionally empty: only needed so that the ContextLoader can find this file -->

</beans>

5.2.验证模式的读取

了解了DTD与XSD,现在我们回归正题

之前我们说到获取XML文件的验证模式 是在 doLoadDocument 的 getValidationModeForResource(resource) 方法实现的

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    //getValidationModeForResource(resource)获取对XML文件的验证模式
	return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
			getValidationModeForResource(resource), isNamespaceAware());
}

getValidationModeForResource(resource)

protected int getValidationModeForResource(Resource resource) {
   int validationModeToUse = getValidationMode();
    //已指定验证模式
   if (validationModeToUse != VALIDATION_AUTO) {
      return validationModeToUse;
   }
   //没有得到指定的声明,默认用XSD
   int detectedMode = detectValidationMode(resource);
   if (detectedMode != VALIDATION_AUTO) {
      return detectedMode;
   }
   // Hmm, we didn't get a clear indication... Let's assume XSD,
   // since apparently no DTD declaration has been found up until
   // detection stopped (before finding the document's root tag).
   return VALIDATION_XSD;
}

在detectValidationMode(resource)方法中把自动检测验证模式的工作委托给了XmlValidationModeDetector,调用了XmlValidationModeDetector的validationModeDetector方法。

public int detectValidationMode(InputStream inputStream) throws IOException {
   this.inComment = false;

   // Peek into the file to look for DOCTYPE.
   try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
      boolean isDtdValidated = false;
      String content;
      while ((content = reader.readLine()) != null) {
         content = consumeCommentTokens(content);
         if (!StringUtils.hasText(content)) {
            continue;
         }
          //判断是否包含Doctype,包含就是DTD 否则XSD
         if (hasDoctype(content)) {
            isDtdValidated = true;
            break;
         }
         if (hasOpeningTag(content)) {
            // End of meaningful data...
            break;
         }
      }
      return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
   }
   catch (CharConversionException ex) {
      // Choked on some character encoding...
      // Leave the decision up to the caller.
      return VALIDATION_AUTO;
   }
}

spring用来检测验证模式的方法就是判断是否包含Doctype,包含就是DTD 否则XSD

6.获取Document

对于Document的加载,XmlBeanFactoryReader 同样没有亲力亲为,而是委托给了DocumentLoader去执行,DocumentLoader是一个接口,真正调用的是DefaultDocumentLoader

  1. 创建DocumentBuilderFactory(用到了之前上一步获取的验证模式)
  2. 通过DocumentBuilderFactory创建DocumentBuilder
  3. 解析inputSource来返回Document对象
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

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

loaderDocument 方法中涉及了一个参数 EntityResolver ,EntityResolver 的作用是项目本身就可以提供一个如何寻找DTD声明的方法

这里不再多作阐述

img

7.解析及注册BeanDefinitions

历经了千辛万苦,终于来到了这一步。

将文件转换为Document后,接下类的提取及注册bean就是我们的重头戏。

再来回顾一下资源加载的过程

image-20220711094323450

我们走到了registerBeanDefinitions(doc, resource) 这一步

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    //在实例化BeanDefinitionDocumentReader 时会将 BeanDefinitionRegistry 传入,默认使用继承自己的DefaultListableBeanFactory 的子类
    //1.记录统计前BeanDefinition的加载个数
   int countBefore = getRegistry().getBeanDefinitionCount();
    //2.加载和注册bean
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    //3.记录本次加载的BeanDefinition个数
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

着重来看第二步,不过我们先来研究一下BeanDefinitionDocumentReader

BeanDefinitionDocumentReader其实是一个接口,而实例化的操作是在createBeanDefinitionDocumentReader()中完成的,而通过此方法BeanDefinitionDocumentReader的真正类型其实已经是DefaultBeanDefinitionDocumentReader了

image-20220711095237001

进入DefaultBeanDefinitionDocumentReader

这个方法在通过 doc.getDocumentElement() 获取到root后,就继续执行BeanDefinitions的注册

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   doRegisterBeanDefinitions(doc.getDocumentElement());
}

doRegisterBeanDefinitions(root)

//任何嵌套的 <beans> 元素都将导致此方法中的递归
protected void doRegisterBeanDefinitions(Element root) {
    //专门处理解析
   BeanDefinitionParserDelegate parent = this.delegate;
   this.delegate = createDelegate(getReaderContext(), root, parent);

    //确定给定的 URI 是否指示默认命名空间及默认的spring配置
   if (this.delegate.isDefaultNamespace(root)) {
       //处理Profile属性
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      if (StringUtils.hasText(profileSpec)) {
         String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
         if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            if (logger.isDebugEnabled()) {
               logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                     "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }

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

   this.delegate = parent;
}

这里着重来看parseBeanDefinitions(root, this.delegate);不过在这之前,我们先了解一下profile属性

7.1 profile属性的使用

有了这个属性,我们可以同时配置两套环境:生产环境、开发环境。方便切换开发、部署环境、更换不同的数据库

<beans profile="test">  
<jdbc:embedded-database id="dataSource">  
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>  
</jdbc:embedded-database>  
</beans>  
  
<beans profile="production">  
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>  
</beans>  

7.2 解析并注册BeanDefinition

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    //对beans处理
   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;
             //对bean处理
            if (delegate.isDefaultNamespace(ele)) {
               parseDefaultElement(ele, delegate);
            }
            else {
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}

很清晰的代码,但是也是因为这是对默认Spring配置的解析

Spring的XML配置中又两种声明bean的配置

一种是默认的:

<bean id="test" class="test.TestBean"/>

一种是自定义的:

<tx:annotation-driven/>

使用自定义的话,就需要用户实现一些接口及配置

判断是否使用默认的命名空间配置是在 doRegisterBeanDefinitions(root) 中,处理profile属性之前就会进行判断

image-20220711102903307

总结

就此容器的基本实现已经完成,对于最后的默认、自定义标签的解析 都分了两大章来阅读。会在后续更新。

第一次阅读源码,中间放弃了好多次,终于在今天早上完成了,spring5.3.21版本的源码和书上有些许出入,但是不影响阅读。

整个早上下来,给我的经验就是阅读源码最好在早上进行,就和初高中时候的早读一样,早上的人的心思没有受到一天中接下来事情的影响,是最适合记忆和探索的时间,一日之计在于晨!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HotRabbit.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值