SpringIoC容器的注册流程(源码解析 )

本章节目录:

  (一) Bean与BeanDefinition 的 关 系

  (二) 简    单   容   器

  (三) 高    级   容   器

  (四)容 器 初 始 化 主 要 做 的 事 情

  (五)Resource、ResourceLoader、容器之间的微妙关系

  (六) ResourceLoader的使用者  :BeanDefinitionReader

  (七)BeanDefinition  的 注 册 流 程

  (八)最终SpringIoC容器执行流程自述

 

 


 

 

 

(一) Bean与BeanDefinition 的 关 系

  1.Bean是Spring的一等公民(核心)

    1.1:Bean的本质就是Java对象,只是这个对象的声明周期由容器来管理

    1.2:不需要为了创建Bean而在原来的Java类上添加任何额外的限制(侵入性极低)

      1.3:对Java对象的控制方式体现在配置上(如:注解、xml配置文件)

           

 

  2.BeanDefinition(Bean的定义):根据配置,生成用来描述Bean的BeanDefinition

    常用属性:

      作用范围scope(@Scope) 

      懒加载lazy-init(@Lazy):决定Bean实例是否延迟加载

      首选primary(@Primary) : 设置为true的Bean会是优先的实现类

      factory-bean和factory-method(@Configuration和@Bean)      

 

BeanDefinition关系图:  

 

 

 

 

1.在Spring中大量运用到了模板模式,图中我们可以看到BeanDefinition是一个接口来的。其定义了主要的功能抽象接口。其主要的实现类有图中三个(RootBeanDefinition、GenericBeanDefinition、ChildBeanDefinition)

2.在Spring2.5之前大部分但是使用的RootBeanDefinition实现类,在2.5版本后出现了GenericBeanDefinition实现类,随之该实现类成了主流

注:

(在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式)

 

 

 

 


 

(二) 简    单   容   器

 

 

BeanFactory的介绍

Spring中要求所有的Ioc容器都要实现BeanFactory接口(org.springframework.beans.factory)

BeanFactory是一个顶级的接口,其内部定义了一个String类型的变量,该变量主要用来获取FactoryBean的实例

该变量的使用例子:

 

 

 

 

 

BeanFactory和FactoryBean的区别:

  BeanFactory:

    它是Spring容器的根接口,定义了Bean工厂的最基础的功能特性。是用作Spring中管理Bean的容器。

  FactoryBean:

    它的本质也是一个Bean,但是这个Bean不像Service和Dao一样用来注入使用的,它的作用是用来生成这些普通的Bean的。

    实现了该接口后呢,Spring容器在初始化时,会把实现了这个接口的Bean取出来,然后使用这个Bean中的getObject()方法生成我们想要的Bean。

 

BeanFactory的方法

  getBean():根据名字获取Bean的实例

  getAliases():根据别名来获取Bean的实例

  isSingleton():校验Bean是否为单例创建

  isPrototype():校验Bean是否为多例创建

  getType():根据名字获取Bean实例的Class类型

 

 

BeanFactory的关系图:

 

 ListableBeanFactory:

  正如它的名字所示,该工厂的方法可以以列表(Listable)的形式提供Bean的相关信息。最大的特点就是可以批量的列出工厂生产的实例信息

  比如该工厂的getBeanDefinitionNames()方法,可以获取容器中所有的Bean名字

  getBeanDefinitionCount方法,可以获取容器中注册的BeanDefinition总数

 

 

 


 

(三) 高   级   容   器

 

高级容器的关系图:

 

高级容器均实现了ApplicationContext这个接口,为了区别于简单容器,高级容器通常被称为Context及上下文。BeanFactory主要面向于Spring内部框架使用,而ApplicationContext则提供使用的开发者。

ApplicationContext之所以可以称为高级容器,就是因为它比BeanFactory实现了更多的接口(功能)。为了有别于简单容器,ApplicationContext系列被放置于org.springframework.context包下。

 

 

ApplicationContext实现的接口有:

  EnvironmentCapable:该接口只有一个getEnvironment()方法。主要用来获取Environment。(Environment说白了就是获取容器的启动参数)

    ListableBeanFactory:可以通过列表的方式来管理bean

  HierarchicalBeanFactory:支持多层级的容器来对每一层Bean的管理

  ResourcePatternResolver:该接口继承自ResourceLoader,可以用来加载资源文件

  MessageSource:用来管理Message,实现国际化等功能

  ApplicationEventPublisher:意味着具有事件发布的能力

  

 

ApplicationContext常用容器:

  传统的基于XML配置的经典容器:

    FileSystemXmlApplicationContext:从文件系统加载配置

    ClassPathXmlApplicationContext:从classpath加载配置

    XmlWebApplicationContext:用于Web应用程序的容器

  目前比较流行的容器:(注解)

    AnnotationConfigServletWebServerApplicationContext(Spring Boot模块)

    AnnotationConfigReactiveWebServerApplicationContext(Spring Boot模块)

    AnnotationConfigApplicationContext(Spring模块)

 

学习容器可以从refresh()开始,该方法在ApplicationContext接口并没有声明,在ApplicationContext方法都是只读方法(Get),而是在其子类ConfigurableApplicationContext接口开始有的该方法。

 

ConfigurableApplicationContext

  ConfigurableApplicationContext接口继承了Lifecycle接口:用于对容器的生命周期的管理

  ConfigurableApplicationContext接口继承了Closeable接口:用于关闭容器释放资源

 

Spring高级容器中最重要的一个类:AbstractApplicationContext

  该类就应用到了模板方法模式:定义好了容器的通用逻辑和模板结构,剩下的部分逻辑由子类实现

 

 

 


 

  (四)容 器 初 始 化 主 要 做 的 事 情

 

 

 

 

 

 

容器的初始化的主要运行流程如下:

  首先读取注解与xml配置文件,将这些配置文件载入到内存中,在内存中这些配置文件会被当作一个个的Resource对象,

  之后这些对象会被解析成BeanDefinition实例,在最后注册到Spring的容器中

 

 

 


(五)Resource、ResourceLoader、容器之间的微妙关系

 

 

Resource:

  Resource是一个抽象接口,位于org.springframework.core.io包下。

  Resource继承自InputStreamSource接口,该接口只提供了一个方法getInputStream()。

  在接口中定义资源的基本操作:

    exists():资源是否存在

    isReadable():资源是否可读

    isOpen():资源是否打开

  

Resource关系图:

 

 

 

 EncodeResource类:主要实现对资源文件的编码处理,其具体的实现在getReader()里,当我们给资源设置了编码属性之后呢,Spring会使用相应的编码作为输入流的编码。

AbstractResource类:主要提供了对Resource接口的大部分方法的公共实现

 

在Resource接口中提供的大部分都是读方法,只有在其子实现类中才有明确的写方法,比如FileSystemResource类为了支持写的功能,继承了WritableResource接口,该接口提供了getOutputStream()方法。

 

 

 

 

 

 

ResourceLoader:

  实现了不同的Resource加载策略,按需返回特定类型的Resource

  ResourceLoadzer是一个接口,该接口主要方法有getResource(String location),根据提供的资源路径返回Resource实例。

  其次还有getClassLoader()方法,该方法主要将ClassLoader暴露出来,对于想要获取ResourceLoader,所使用的ClassLoader实例的用户可直接调用该方法获得。

 

 

ResourceLoader关系图:

 

 

其关键的类:DefaultResourceLoader。这个类提供了ResourceLoader接口的实现,最主要的方法就是getResource()方法的实现(获取Resource的具体实现类实例)

ResourceLoader接口的getResource()方法,本身就只能通过资源路径获取一个资源,并不支持Ant风格路径的解析。

如果我们要通过Ant风格路径解析去批量获取Resource,那么需要对getResource方法进行包装重铸。

 

ResourcePatternResolver:

  由于该方法的需求,Spring提供了ResourcePatternResolver接口,该接口提供了getResources方法,支持根据路径匹配模式返回多个Resource实例,同时声明了一个变量声明了多一种路径匹前缀

  

   该协议前缀由子类提供实现。

 

PathMatchingResourcePatternResolver:

  该类为ResourcePatternResolver接口最为常用的子类,该类包含了对ResourceLoader的引用,这也就意味着在对继承自ResourceLoader的方法的实现会代理给该引用。

  同时在getResources()方法的实现中,当找到一个匹配的资源Location时就可以使用该ResourceLoader的引用将其解析成Resource实例返回。

  也提供了对父类接口ResourcePatternResolver新增的资源匹配前缀提供了实现

  也提供了Ant解析风格

 

 

 

ApplicationContext:

  在我们观察ResourceLoader的关系图的时候,可以发现我们实习的面孔:ApplicationContext。该类继承了ResourcePatternResolver,也就间接的继承了ResourceLoader接口

  

AbstractApplicationContext:

  AbstractApplicationContext类中我们可以看到容器和ResourceLoader的关系,其继承了DefaultResourceLoader。

  该类中有个方法getResourcePatternResolver()用来生成PathMatchingResourcePatternResolver这个实例,该方法在AbstractApplicationContext的构造函数中调用。

  在方法生成实例的时候呢我们可以看到它将自己的实例传递了进去

 

 

 

 

 


 

 

(六) ResourceLoader的使用者  :BeanDefinitionReader

 

BeanDefinitionReader:(主要负责读取BeanDefinition)

  该类会利用ResourceLoader或getResourcePatternResolver将配置信息解析成一个个BeanDefinition并借助一个叫做BeanDefinitionRegistry的注册器接口将BeanDefinition注册到容器中

  该类提供了一系列用来加载BeanDefinition的接口,方法主要是针对单/多个配置文件的加载,或者是单/多个Resource实例的加载。

  该类主要方法:

    getRegistry():可以获取BeanDefinitionRegistry对象,这个类的主要作用将BeanDefinition注册到BeanDefinition的注册表中。

    getBeanNameGenerator():Bean的名字生成器,为匿名Bean生成一个名字,就是id

 

BeanDefinitionReader的关系图:

 

 

 

AbstractBeanDefinitionReader是一个抽象类,该类的主要方法为loadBeanDefinitions()。

 

AbstractBeanDefinitionReader.loadBeanDefinitions():

org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions()负责将加载到的bean definitions 文件解析为具体的bean实例

从指定的 location 资源路径加载 bean 定义信息。location 可以是简单的路径,但是也可以是 ResourcePatternResolver 类型,这样的话就需要对 ResourcePatternResolver 进行处理了,在初始阶段其包含一个 资源的set。

location 值可能为空,表示调用者对于资源不感兴趣。最终返回发现的bean definitions 的数量

 

 1 public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
 2    //获取 资源加载器
 3   ResourceLoader resourceLoader = getResourceLoader();
 4    if (resourceLoader == null) {
 5       throw new BeanDefinitionStoreException(
 6             "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
 7    }
 8   //如果资源加载器属于 ResourcePatternResolver,则需要解析
 9 
10    if (resourceLoader instanceof ResourcePatternResolver) {
11     // 对 location 进行解析,Pattern模式可以生成多个 Resource 类型数组
12       try {
13          Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
14       //具体的对资源进行解析-你可以看到,这里是一个递归调用
15          int count = loadBeanDefinitions(resources);
16          if (actualResources != null) {
17             Collections.addAll(actualResources, resources);
18          }
19          if (logger.isTraceEnabled()) {
20             logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
21          }
22          return count;
23       }
24       catch (IOException ex) {
25          throw new BeanDefinitionStoreException(
26                "Could not resolve bean definition resource pattern [" + location + "]", ex);
27       }
28    }
29    else {
30     // 使用 绝对URL只能加载单个的资源
31       Resource resource = resourceLoader.getResource(location);
32       int count = loadBeanDefinitions(resource);
33       if (actualResources != null) {  
34          actualResources.add(resource)
35       }
36       if (logger.isTraceEnabled()) {
37          logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
38       }
39       return count;
40    }
41 }

 

 


 

 

 

(七)BeanDefinition  的 注 册 流 程

 

BeanDefinitionRegistry:

  该类负责对BeanDefinition进行注册

  该类实现了AliasRegistry接口, 定义了一些对 bean的常用操作

    关于AliasRegistry它大概有如下功能:

    1. 以Map<String, BeanDefinition>的形式注册bean
    2. 根据beanName 删除和获取 beanDefiniation
    3. 得到持有的beanDefiniation的数目
    4. 根据beanName 判断是否包含beanDefiniation

  该类的重点方法:registerBeanDefinition(),该方法就是网注册表中注册一个新的BeanDefinition实例。

 

BeanDefinitionRegistry类的描述:

 1 // 它继承自 AliasRegistry 
 2 public interface BeanDefinitionRegistry extends AliasRegistry {
 3 
 4   // 关键 -> 往注册表中注册一个新的 BeanDefinition 实例 
 5   void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException;
 6   // 移除注册表中已注册的 BeanDefinition 实例
 7   void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
 8   // 从注册中心取得指定的 BeanDefinition 实例
 9   BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; 
10   // 判断 BeanDefinition 实例是否在注册表中(是否注册)
11   boolean containsBeanDefinition(String beanName);
12   // 取得注册表中所有 BeanDefinition 实例的 beanName(标识)
13   String[] getBeanDefinitionNames();
14   // 返回注册表中 BeanDefinition 实例的数量
15   int getBeanDefinitionCount();  
16   // beanName(标识)是否被占用
17   boolean isBeanNameInUse(String beanName);
18 }

 

 

重点铺垫:

  DefaultListableBeanFactory中,我们可以看到该类实现了BeanDefinitionRegistry接口。

  同时它内部还有beanDefinitionMap成员变量,所谓的注册呢最终便是把beanName和BeanDefinition实例作为键值对存放到该Map中(ConcurrentHashMap是为了容器安全)

 

 

 

DefaultBeanDefinitionDocumentReader :

 

 

DefaultBeanDefinitionDocumentReader主要方法:

  processBeanDefinition():

         在DefaultBeanDefinitionDocumentReader 类中的 processBeanDefinition()方法完成了对BeanDefinition的注册

      方法中的Registry参数在跟进参数后可以发现返回的是AbstractBeanDefinitionReader中的Registry变量,该Registry变量便是BeanDefinitionRegistry的实例。

 

 1 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
 2   //BeanDefinitionHolder是对BeanDefinition的封装,即Bean定义的封装类
 3   //对Document对象中<Bean>元素的解析由BeanDefinitionParserDelegate实现
 4    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
 5    if (bdHolder != null) {
 6       bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
 7       try {
 8          //向SpringIOC容器注册解析得到的BeanDefinition,这是BeanDefinition向IOC容器注册的入口
 9       //传递的参数bdHolder为BeanDefinition的包装类,另一个参数为registry实例。
10          BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
11       }
12       catch (BeanDefinitionStoreException ex) {
13          getReaderContext().error("Failed to register bean definition with name '" +
14                bdHolder.getBeanName() + "'", ele, ex);
15       }
16       //在完成BeanDefinition注册之后,往容器发送注册完成的事件
17       getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
18    }
19 }

 

重点:

  其中的 BeanDefinitionReaderUtils 调用了 registerBeanDefinition() 方法来将我们的 BeanDefinition 注册到 DefaultListableBeanFactory 中的 beanDefinitionMap 中。

 

 

 

DefaultBeanDefinitionDocumentReader主要方法:

  doRegisterBeanDefinitions:

    该方法主要操作Document的解析,将Xml通过SAX解析成Document对象,然后通过Delegate代理解析,解析成一个个的document

    方法中标有⭐号的两行代码,创建了delegate代理解析对象,然后在末尾红色行代码处将delegate对象传递到方法中进行下一步的解析

 

protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;        //
        this.delegate = createDelegate(getReaderContext(), root, parent);//

        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                // We cannot use Profiles.of(...) since profile expressions are not supported
                // in XML config. See SPR-12458 for details.
                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;
    }

 

 注:

  由document对象解析成BeanDefinition对象之后,将BeanDefinition对象注册进入容器当中,详细可查看  Spring: BeanDefinition的注册(源码跟进)

 


 

 

 

(八)最终SpringIoC容器执行流程自述

 

 

XML配置的(资源定位、加载、解析、注册)全链路分析

Main() 

public static void main(String[] args) {
        String xmlPath="D:\\Project\\spring-framework-5.2.0.RELEASE\\springdemo\\src\\main\\resources\\spring\\spring-config.xml";
        ApplicationContext applicationContext = new FileSystemXmlApplicationContext(xmlPath);
        WelcomeService welcomeService = (WelcomeService)applicationContext.getBean("welcomeService");
        welcomeService.sayHello("强大的spring框架");
}

 

执行大图:

 

 

大致流程自述:

  1.首先我们根据location(Xml路径)new了一个FileSystemXmlApplicationContext的高级容器,

  2.FileSystemXmlApplicationContext构造函数调用了另一个构造函数

  3.FileSystemXmlApplicationContext在调用构造方法的时候设置了数组类型的配置路径(ConfigLocation)由此我们可以推测出它使用的配置文件加载器是支持Ant路径扫描的多资源加载器,后调用了自身的refresh()方法进行容器的初始化。

  4.此时进入到AbstractApplicationContext类中的refresh()方法。该方法中的obtainFreshBeanFactory()创建了新的内部容器,该容器负责Bean的创建和管理。obtainFreshBeanFactory()方法调用了子容器进行初始化

  5.AbstractRefreshableApplicationContext(子容器)实现了初始化。在初始化中构造了创建了主要的容器(DefaultListableBeanFactory),并将其作为参数传递进loadBeanDefinitions()方法中,进行请求加载BeanDefinitons

  6.在AbstractXmlApplicationContext的loadBeanDefinitions()方法中用传递过来的参数(DefaultListableBeanFactory)创建了XmlBeanDefinitionReader。并将自身(AbstractXmlApplicationContext)设置给XmlBeanDefinitionReader的

     ResourceLoader。随后接着请求加载BeanDefinitions

  7.遍历Locations看看是否有多个路径,然后逐一加载配置文件

  8.调用EncodeedResource来包装资源实例

  9.XmlBeanDefinitionReader 的 loadBeanDefinitions中对资源进行了编码处理。随后进入了实际干活的doLoadBeanDefinitions()方法中

  10.XmlBeanDefinitionReader 的 doLoadBeanDefinitions()方法:将Xml解析成Document对象,然后将对象和资源传递到registerBeanDefinitions(),该方法调用了doRegisterBeanDefinitions()进行资源注册

  11.DefaultBeanDefinitionDocumentReader 的 doRegisterBeanDefinitions()方法:该方法创建了Delegate代理解析,传递delegate()调用了parseBeanDefinitions()将Document对象解析成一个个的BeanDefinition对象

 

  12.DefaultBeanDefinitionDocumentReader 的 parseBeanDefinitions()方法:该方法会进行一系列的逻辑判断后调用parseDefaultElement()方法开始根据Spring的XMl命名规范进行解析出BeanDefinition对象

  13.解析BeanDefinition对象时会进入到内部:DefaultBeanDefinitionDocumentReader 的 processBeanDefinition()方法,该方法会把BeanDefinition对象包装成BeanDefinitionHolder类,随后调用registerBeanDefinition()方法,

    将BeanDefinition实例注册到容器中

 

  14.BeanDefinitionReaderUtils 的 registerBeanDefinition()方法 :该方法真正的开始将BeanDefinition注册到容器。如果BeanDefinition实例先前已经注册过了,则清空先前的注册信息,(如果是单例,将先前已经创建的Bean实例清除掉重新注册)

 

 

 简述:

  Locations   -->   高级容器    -->  refresh方法(资源刷新)    -->  创建内部容器进行Bean创建 / 管理   -->  创建适用于Xml的Reader(资源加载器) -->  遍历资源路径依次加载

     -->    资源编码处理    -->    解析资源  -->    BeanDefinition包装  -->   存入容器   -->   刷新容器

 

posted @ 2021-02-14 10:43  _kerry  阅读(263)  评论(0编辑  收藏  举报