Spring学习笔记(三):简述 Spring IoC 容器的实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Jin_Kwok/article/details/82794406

概述

在上一篇文章《实例解读 IoC 和 DI》中,从实例出发对 IoC 和 DI 进行了解读。本章将更进一步,简述 IoC 容器的实现。

开心一刻:平头哥10大人生格言:

1、生死看淡,不服就干 2、我只想整死各位,或者被各位整死。 3、别人的是我的,我的还是我的。 4、我也不想针对谁,在我眼里你们都能吃。 5、我干起仗来,我自己都怕。 6、平头白发银披风,非洲大地我最凶。 7、我这一生就是太忙碌了,不是干架 就是在干架的路上。 8、从不记仇,有仇当场就报了。 9、张狂不是我的本性,但是惹我就等于自杀。 10、每次打架别和我说对方有多少人,我只要时间和地点。 一獾斗三虎,三獾沉航母,五獾斗上帝,十獾创世纪!!


一、Spring 容器高层视图

Spring 启动时读取应用程序提供的Bean配置信息(一般通过XML文件配置),并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。值得一提的是,Bean缓存池是由HashMap实现的。

上述视图可以划分为两个阶段:

1.1 容器的初始化过程

定位资源:这个过程使用ResourceLoader进行,所谓资源一般指的是定义Bean的xml文件。spring定义了多种资源存放的方式, 如classpath、文件系统等。FileSystemResource、ClassPathResource、ServletContextResoruce。用户可以根据需选择合适的资源实现类访问资源。我们一般都使用类路径或文件系统定位资源。

载入、解析、注册bean:找到了定义bean资源位置,下一步就就是将它们载入到系统中。IoC 容器将xml中定义的bean读入成为Document对象,并将Document对象解析成为BeanDefinition对象。spring将BeanDefinition对象存放中HashMap中,IoC容器载入并解析完bean后,将BeanDefinition存放在BeanFactory的beanDefinitionMap中。至此完成了容器的初始化过程。

1.2 依赖注入过程

容器完成初始化,并没有完成依赖注入。或者说大部分bean实例并没有在这个时候创建出来,也没有注入到需要使用它们的bean中。 依赖注入是发生在第一次调用getBean方法时(除了lazy-init方式)。

创建bean:在SimpleInstantiationStrategy中,通过JDK的反射机制创建了bean实例,也可以通过CGLIB创建bean实例。

注入属性:通过BeanWrapper对bean的属性进行注入。通过BeanDefinition中对bean的定义信息进行递归,注入的是bean,就调用getBean创建和注入bean;注入的是属性,就直接注入属性。

关于Bean实例化的完整流程,将在第二节详述。


二、Bean 实例化的完整流程

下图描述了Spring容器从加载配置文件到创建出一个完整Bean的作业流程:

2.1 上述Bean实例化流程的详细说明:

step1:ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源;

step2:BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个<bean>解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;

step3:容器扫描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射机制自动识别出Bean工厂后处理后器(实现BeanFactoryPostProcessor接口)的Bean,然后调用这些Bean工厂后处理器对BeanDefinitionRegistry中的BeanDefinition进行加工处理。主要完成以下两项工作:

  • 对使用到占位符的<bean>元素标签进行解析,得到最终的配置值,这意味对一些半成品式的BeanDefinition对象进行加工处理并得到成品的BeanDefinition对象;
  • 对BeanDefinitionRegistry中的BeanDefinition进行扫描,通过Java反射机制找出所有属性编辑器的Bean(实现java.beans.PropertyEditor接口的Bean),并自动将它们注册到Spring容器的属性编辑器注册表中(PropertyEditorRegistry);

step4:Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用InstantiationStrategy着手进行Bean实例化的工作;

step5:在实例化Bean时,Spring容器使用BeanWrapper对Bean进行封装,BeanWrapper提供了很多以Java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作;

step6:利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean。

2.2 Spring 容器内在机制

Spring容器确实堪称一部设计精密的机器,其内部拥有众多的组件和装置。Spring的高明之处在于,它使用众多接口描绘出了所有装置的蓝图,构建好Spring的骨架,继而通过继承体系层层推演,不断丰富,最终让Spring成为有血有肉的完整的框架。所以查看Spring框架的源码时,有两条清晰可见的脉络:

  • 接口层描述了容器的重要组件及组件间的协作关系;
  • 继承体系逐步实现组件的各项功能。

接口层清晰地勾勒出Spring框架的高层功能,框架脉络呼之欲出。有了接口层抽象的描述后,不但Spring自己可以提供具体的实现,任何第三方组织也可以提供不同实现, 可以说Spring完善的接口层使框架的扩展性得到了很好的保证。纵向继承体系的逐步扩展,分步骤地实现框架的功能,这种实现方案保证了框架功能不会堆积在某些类的身上,造成过重的代码逻辑负载,框架的复杂度被完美地分解开了。Spring组件按其所承担的角色可以划分为两类:

  • 物料组件:Resource、BeanDefinition、PropertyEditor以及最终的Bean等,它们是加工流程中被加工、被消费的组件,就像流水线上被加工的物料;
  • 加工设备组件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy以及BeanWrapper等组件像是流水线上不同环节的加工设备,对物料组件进行加工处理。

三、IoC 容器介绍

Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化 Bean 并建立 Bean 之间的依赖关系。 Spring 的 IoC 容器在完成这些底层工作的基础上,还提供了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。

Spring 的 IoC 容器有两个核心接口:1.BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;2. ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合我们都直接使用 ApplicationContext 而非底层的 BeanFactory。

3.1 Spring BeanFactory

BeanFactory 是IOC容器的核心接口, 它定义了IOC的基本功能,我们看到它主要定义了getBean方法。getBean方法是IOC容器获取bean对象和引发依赖注入的起点。

在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类(目前已经废弃)。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。

在资源宝贵的移动设备或者基于 applet 的应用当中, BeanFactory 会被优先选择。否则,一般使用的是 ApplicationContext,除非你有更好的理由选择 BeanFactory。

例子:

XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("Beans.xml"));
        
IBookDAO obj = (IBookDAO) factory.getBean("bookdao");

3.2 Spring ApplicationContext

ApplicationContext 是IOC容器另一个重要接口, 它继承了BeanFactory的基本功能, 同时也继承了容器的高级功能,如:MessageSource(国际化资源接口)、ResourceLoader(资源加载接口)、ApplicationEventPublisher(应用事件发布接口)等。

最常被使用的 ApplicationContext 接口实现:

  • FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。

  • ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。

  • WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

在之前的几篇文章中,采用的都是 ClassPathXmlApplicationContext,其它两种用法类似,这里就不在举例说明了。

3.3 Spring Bean 定义

前面已经多次提到bean,那么Spring是怎样定义的bean呢?被称作 bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。这些 bean 是由用容器提供的配置元数据创建的,例如,已经在先前章节看到的,在 XML 的表单中的 定义,此外,还可以通过注解配置,基于Java配置。

bean 定义包含称为配置元数据的信息,这些元数据告诉容器以下信息:

  • 如何创建一个 bean

  • bean 的生命周期的详细信息

  • bean 的依赖关系

上述所有的配置元数据转换成一组构成每个 bean 定义的下列属性。

属性 描述
class 这个属性是强制性的,显式指定用来创建 bean 的 Java 类。
name 这个属性指定唯一的 bean 标识符。在基于 XML 的配置元数据中,你可以使用 ID 或 name 属性来指定 bean 标识符。
scope 这个属性指定基于 bean 的定义所创建的对象的作用域,共有5种。如 1.singleton:表明在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值;2.prototype:每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean();其余的 request、session、global-session三个作用域仅适用于WebApplicationContext环境。
constructor-arg 它是用来注入依赖关系的,在《实例解读 IoC 和 DI》中已经介绍过
properties 它是用来注入依赖关系的,在《实例解读 IoC 和 DI》中已经介绍过
autowiring mode 它是用来注入依赖关系的,在《实例解读 IoC 和 DI​​​​​​​》中已经介绍过
lazy-initialization mode 延迟初始化的 bean, 告诉 IoC 容器在它第一次被请求时创建,而不是在启动时去创建一个 bean 实例。
initialization 方法 在 bean 的所有必需的属性被容器设置之后,调用回调方法。
destruction 方法 当包含该 bean 的容器被销毁时,使用回调方法。注意:只有单例bean(对象),销毁容器时才会去调用该对象指定的destroy方法。

Bean元数据配置实例演示:

1. Bean定义:通过XML配置文件配置Bean的元数据,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!-- 定义一个bean,设置延迟实例化模式 -->
    <bean name="storeInfo" class="com.yingshulan.spring.spring_learning.StoreInfo" lazy-init="true">
    	<!-- 通过set方法注入依赖 -->
    	<property name="id" value="129"></property >
    	<property name="bookshelfNum" value="Noth-A1-002"></property >
    </bean>  
    
    <!-- 定义一个bean,作用域为单例,即这个bean定义在IoC容器中只会存在一个实例-->
    <!-- 为bean生命周期中的初始化和销毁配置回调方法:init和destroy,需要在bean对应Java类中实现 -->
    <bean id="bookdao" class="com.yingshulan.spring.spring_learning.BookDAO" scope="singleton"
    	init-method="init" destroy-method="destroy">
    	<!-- 通过构造方法注入依赖 -->
    	<constructor-arg name="agentName" value="Jack Ma"></constructor-arg>
    	<constructor-arg ref="storeInfo"></constructor-arg>
    </bean>
</beans>

2. Bean相关的类创建:创建BookDAO类和StoreInfo类,如下所示:

/**
 * 图书数据访问实现类,访问数据库不是本文的重点,为简单起见,这里只是模拟访问数据库
 * 并没有真正存储图书信息到数据库中。
 */
public class BookDAO implements IBookDAO
{
    // 经办人
    private String agentName;
 
    // 库房信息
    private StoreInfo storeInfo;
 
    BookDAO(String agentName, StoreInfo storeInfo)
    {
        this.agentName = agentName;
        this.storeInfo = storeInfo;
    }
 
    public String addBook(String bookname)
    {
        return "经办人:" + agentName + "添加图书:" + bookname + "仓库信息:" + storeInfo.getStoreInfo();
    }
    
    // 定义一个初始化方法,对应bean定义配置,当bean实例化时回调此方法
    public void init()
    {
        System.out.println("bean 实例化完成。");
    }
    // 定义一个销毁方法,对应bean定义配置,当bean所在容器被销毁时调用该方法
    // 注意:只有bean定义为单例模式,该方法才会执行
    public void destroy()
    {
        System.out.println("bean 被销毁。");
    }
}

StoreInfo类创建

public class StoreInfo
{
    // 库房id
    private int id;
    // 书架编号
    private String bookshelfNum;
    
    public void setId(int id)
    {
        this.id = id;
    }
    
    public void setBookshelfNum(String bookshelfNum)
    {
        this.bookshelfNum = bookshelfNum;
    }

    public String getStoreInfo()
    {
        return "id: " + id + "bookshelfNum: " + bookshelfNum;
    }
}

3. 测试类创建:创建一个BookService类,用于测试

public class BookService
{
    private static IBookDAO bookDAO;
    
    public static void main(String[] args)
    {
        AbstractApplicationContext  ctx = new ClassPathXmlApplicationContext("IOCBeans01.xml");
        bookDAO = (IBookDAO) ctx.getBean("bookdao");
        
        BookService bookservice = new BookService();
        bookservice.storeBook("《深入浅出理解 Spring 3.X 精要》");
        // 销毁容器,观察bean回调destroy方法
        ctx.close();
    }
 
    /**
     * Service层: 调用DAO层的方法写信息到数据库
     * @param bookname
     */
    public void storeBook(String bookname)
    {
        System.out.println("增加图书信息到数据库");
        String result = bookDAO.addBook(bookname);
        System.out.println(result);
    }
}

运行结果:

bean 实例化完成。
增加图书信息到数据库
经办人:Jack Ma添加图书:《深入浅出理解 Spring 3.X 精要》仓库信息:id: 129bookshelfNum: Noth-A1-002
bean 被销毁。

3.4 Spring Bean 生命周期

在上一节的实例中,在Bean的定义中,我们声明了 init-method 和 destroy-method 参数 。init-method 属性指定一个方法,在实例化 bean 时,立即调用该方法。同样,destroy-method 指定一个方法,只有从容器中移除 bean 之后,才能调用该方法。对于一个bean来说,不必要同时配置两个方法,可任选其一,或者不配置。

需要特别注意的是:只有bean配置为 "singleton" 模式(单例模式),在其所在容器销毁时才会回调destroy-method 指定的方法。

理解 Spring bean 的生命周期很容易。当一个 bean 被实例化时,它可能需要执行一些初始化使它转换成可用状态。同样,当 bean 不再需要,并且从容器中移除时,可能需要做一些清除工作。

尽管还有一些在 Bean 实例化和销毁之间发生的活动,但是本节将只讨论两个重要的生命周期回调方法,它们在 bean 的初始化和销毁的时候是必需的。

3.5 Spring Bean 后置处理器

BeanPostProcessor 接口定义回调方法,你可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。你也可以在 Spring 容器通过插入一个或多个 BeanPostProcessor 的实现来完成实例化,配置和初始化一个bean之后实现一些自定义逻辑回调方法。

你可以配置多个 BeanPostProcessor接口,通过设置 BeanPostProcessor 实现的 Ordered 接口提供的 order 属性来控制这些 BeanPostProcessor 接口的执行顺序。

BeanPostProcessor 可以对 bean(或对象)实例进行操作,这意味着 Spring IoC 容器实例化一个 bean 实例,然后 BeanPostProcessor 接口进行它们的工作。

ApplicationContext 会自动检测由 BeanPostProcessor 接口的实现定义的 bean,注册这些 bean 为后置处理器,然后通过在容器中创建 bean,在适当的时候调用它。

BeanPostProcessor 应用举例

1. 在3.3 节实例的基础上,创建一个实现BeanPostProcessor 接口的类,如下所示:这是实现 BeanPostProcessor 的非常简单的例子,它在任何 bean 的初始化的之前和之后打印输出该 bean 的名称。你可以在初始化 bean 的之前和之后实现更复杂的逻辑,因为你有两个访问内置 bean 对象的后置处理程序的方法。

public class InitBookDAO implements BeanPostProcessor
{

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException
    {
        System.out.println("BeforeInitialization : " + beanName);
        return bean;  // you can return any other object as well
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
    {
        System.out.println("AfterInitialization : " + beanName);
        return bean;  // you can return any other object as well
    }
}

2. 在3.3节实例的定义Bean的XML配置文件中加入一行:InitBookDAO的定义,如下所示

<bean class="com.yingshulan.spring.spring_learning.InitBookDAO"></bean>

3. 运行3.3节中的测试类BookService,结果如下:

BeforeInitialization : storeInfo
AfterInitialization : storeInfo
BeforeInitialization : bookdao
bean 实例化完成。
AfterInitialization : bookdao
增加图书信息到数据库
经办人:Jack Ma添加图书:《深入浅出理解 Spring 3.X 精要》仓库信息:id: 129bookshelfNum: Noth-A1-002
bean 被销毁。

3.6 Spring Bean 定义继承

bean 定义可以包含很多的配置信息,包括构造函数的参数,属性值,容器的具体信息例如初始化方法,静态工厂方法名等等。子 bean 的定义继承父定义的配置数据。子bean定义可以根据需要重写一些值,或者添加其它值。

Spring Bean 定义的继承与 Java 类的继承无关,但是继承的概念是一样的。你可以定义一个父 bean 的定义作为模板和其它子 bean 就可以从父 bean 中继承所需的配置。当你使用基于 XML 的配置元数据时,通过使用父属性,指定父 bean 作为该属性的值来表明子 bean 的定义。

关于这个 特性就不再举例了,很简单。

参考文献:

1. https://www.w3cschool.cn/wkspring/tydj1ick.html

2. https://spring.io/projects

猜你喜欢

转载自blog.csdn.net/Jin_Kwok/article/details/82794406
今日推荐