侧边栏壁纸
博主头像
一定会去到彩虹海的麦当

说什么呢?约定好的事就一定要做到啊!

  • 累计撰写 63 篇文章
  • 累计创建 16 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

[Spring源码]——容器基本实现

一定会去到彩虹海的麦当
2022-01-20 / 0 评论 / 0 点赞 / 65 阅读 / 9,349 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-05-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

📚笔记整理自《Spring源码深度解析》(第2版),同时也参考了一些网上资源,具体参考链接在文末

@

核心类介绍

DefaultlistableBeanFactory

DefaultListableBeanFactmy 是整个 bean 加载的核心部分,是 Spring 注册及加载bean的默认实现

DefaultListableBeanFactory

图中各个类的作用:

  • AliasRegistry:定义对alias的简单增删改等操作。
  • SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。
  • SingletonBeanRegistry:定义对单例的注册及获取。
  • BeanFactory:定义获取bean及bean的各种属性。
  • DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现。
  • HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持
  • BeanDefinitionRegistry:定义对BeanDefinition的各种增删改操作
  • FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能
  • ConfigurableBeanFactory:提供配置Factory的各种方法
  • ListableBeanFactory:根据各种条件获取bean的配置清单。
  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能。
  • AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后处理器
  • AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现。
  • ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等。
  • DefaultListableBeanFactory:综合上面所有功能,主要是对bean注册后的处理。

XmlBeanFactory对 DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性。在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。

XmlBeanDefinitionReader

实现XML配置文件读取功能

spring-配置文件读取相关类图

各个类的功能

  • ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource
  • BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能。
  • EnvironmentCapable:定义获取Environment方法。
  • DocumentLoader:定义从资源文件加载到转换为Document的功能。
  • AbstractBeanDefinitionReader:对EnvironmentCapable,BeanDefinitionReader类定义的功能进行实现。
  • BeanDefinitionDocumentReader:定义读取Docuemnt并注册BeanDefinition功能。
  • BeanDefinitionParserDelegate:定义解析Element的各种方法。

处理步骤

1、通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转换为对应的Resource文件。
2、通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Document文件。
3、通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析

容器的基础XmlBeanFactory

分析以下功能代码实现

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

整体分析

XmlBeanFactory初始化

XmlBeanFactory初始化时序图

  1. 用 ClassPathResource的构造函数来构造 Resource 资源文件的实例对象,这样后续的资源处理就可以用 Resource 提供的各种服务来操作

    在Java中,将不同来源的资源抽象成URL,通过注册不同的handler来处理不同来源的资源的读取逻辑,但是URL没有默认定义相对Classpath或ServletContext等资源的handler,且也没有提供基本的方法,比如检查当前资源是否存在,检查当前资源是否可读的等方法。所以Spring使用Resource接口封装底层资源。

  2. 使用Resouce来进行XmlBeanFactory的初始化

  3. 利用XmlBeanDefinitionReader来获取beanDefinition

加载bean

接下来我们分析如何获取beanDefinition

loadeBeanDefinitions函数执行时序图.drawio

处理过程:

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

源码分析

起始

BeanFactory bf= new XmlBeanFactory(new ClassPathResource("be anFactoryTest.xml"));//(1) 

(1)构造 Resource 资源文件

ClassPathResource.java
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());
	}

(2)使用Resouce来进行XmlBeanFactory的初始化

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);//(3)
	}

(3) 获取beanDefinition

XmlBeanDefinitionReader.java
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(new EncodedResource(resource));//(2)
}

EncodedResource用于对资源文件的编码进行处理,当设置了编码属性后Spring会根据相应的编码作为输入流的编码

(4) loadBeanDefinitions

XmlBeanDefinitionReader.java
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    ...
        //从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputstream
        try (InputStream inputStream = encodedResource.getResource().getInputStream()) {   
            //通过inputStream获取inputSource
            InputSource inputSource = new InputSource(inputStream);
            //存在编码要求,进行编码设置
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            //真正进入了逻辑核心部分
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());//(3)
        }
      ...
}

(5)进入逻辑核心部分

XmlBeanDefinitionReader.java
  • 获取对XML文件的验证模式。
  • 加载XML文件,并得到对应的Documento
  • 根据返回的Document注册Bean信息
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    throws BeanDefinitionStoreException {
		...
        //加载对应的Document
        Document doc = doLoadDocument(inputSource, resource);//(6)
        //根据doc注册Bean信息
        int count = registerBeanDefinitions(doc, resource); //(8)
 		...
}

(6)获取Xml的验证模式

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

XML中有两种验证模式:DTD、XSD,保证XML文件的正确性

Spring 中 XmlValidationModeDetector 对验证模式的确认是循环 xml 整个文件的每一行判断是否有 DOCTYPE 字符串, 包含就是 DTD 验证模式, 不包含就是 XSD 模式

(7)获取Document

Spring 中 DocumentLoader 有且仅有一个实现类 DefaultDocumentLoader

DefaultDocumentLoader.java
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

    //创建DocumentBuilderFactory
    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isTraceEnabled()) {
        logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
    //创建DocumentBuilder
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    //解析inutSource,返回Document对象
    return builder.parse(inputSource);
}

当我们将文件转换成Document后,就到接下来提取及注册bean了,我们回到(5),可以看到接下来要调用registerBeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    throws BeanDefinitionStoreException {
		...
        //加载对应的Document
        Document doc = doLoadDocument(inputSource, resource);
        //根据doc注册Bean信息,并获取数量
        int count = registerBeanDefinitions(doc, resource); 
 		...
}

(8) 解析及注册BeanDefinitions

XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 获取 基于 Document 的Bean定义读取器
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 历史已有的bean定义数量
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 注册bean定义
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));  // (9)
     // 注册后的数量-历史数量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

BeanDefinitionDocumentReader 的作用就是进行 BeanDefinition 的注册

(9) 注册bean定义

DefaultBeanDefinitionDocumentReader.java
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    //Spring 进行 Document 对象解析, 并将解析结果包装成 BeanDefinition 进行注册的核心方法
    doRegisterBeanDefinitions(doc.getDocumentElement());//(10)
}

前面都是XML加载解析的准备流程,接下来终于开始进行解析了

(10)doRegisterBeanDefinitions 流程

DefaultBeanDefinitionDocumentReader.java
protected void doRegisterBeanDefinitions(Element root) {
    // 父 BeanDefinitionParserDelegate 一开始为null
    BeanDefinitionParserDelegate parent = this.delegate;
    // 创建 BeanDefinitionParserDelegate
    this.delegate = createDelegate(getReaderContext(), root, parent);

    // 判断命名空间是否为默认的命名空间
    // 默认命名空间: http://www.springframework.org/schema/beans
    if (this.delegate.isDefaultNamespace(root)) {
        // 获取 profile 属性
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        // 是否存在 profile
        if (StringUtils.hasText(profileSpec)) {
            // profile 切分后的数据
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);

            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {

                return;
            }
        }
    }
	// 前置处理
    preProcessXml(root);
    // bean definition 处理
    parseBeanDefinitions(root, this.delegate);
     // 后置 xml 处理
    postProcessXml(root);

    this.delegate = parent;
}

流程分析:

  1. 设置父BeanDefinitionParserDelegate 对象, 值得注意的是这个设置父对象一般情况下是不存在的即 this.delegate = null
  2. 创建 BeanDefinitionParserDelegate 对象 , BeanDefinitionParserDelegate 对象是作为解析的重要方法.
  3. 对于 profile 属性的处理
  4. XML 解析的前置处理
  5. XML 的解析处理
  6. XML 解析的后置处理
  7. 设置成员变量

补充:

  1. 这里提一个拓展点 profile 这个属性在 Spring 中一般用来做环境区分, 在 SpringBoot 中有一个类似的配置spring.profiles . 在 Spring XML 模式中 profile 是属于 <beans/> 的一个属性
  2. preProcessXml(root) postProcessXml(root)这两个方法是为子类而设计的,为模版方法模式,如果需要在Bean解析前后做一些处理的话,那么只需要重写这两个类就可以了

(11)parseBeanDefinitions 分析

DefaultBeanDefinitionDocumentReader.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)) {
               // 处理标签的方法
               parseDefaultElement(ele, delegate);
            }
            else {
               // 处理自定义标签
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      // 处理自定义标签
      delegate.parseCustomElement(root);
   }
}

parseBeanDefinitions 方法主要是对一个 Element 的每个节点进行处理, 节点本身又存在多样性,

  • 节点的多样性

    1. Spring 提供的标签: 即 DTD 或者 XSD 中定义的标签
      如:<bean id="test" class="test.TestBean"/>
    2. 自定义标签
      如:<tx:anntationo-draiven/>

根据节点多样性 Spring 提供了两个方法进行处理 parseDefaultElementdelegate.parseCustomElement(ele)

参考链接:

第三章 IoC 资源读取及注册

0

评论区