Spring 三部曲(一): Spring 的 IoC 容器

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 本系列只是提供了一些看 Spring 源码前导知识,很多示例做了简化和抽象。真正领略 Spring 设计的玄妙之处,还是应该在学习本系列之后再去 Debug 一遍 Spring 的源码,印象会更加深刻。

Spring 三部曲(一): Spring 的 IoC 容器

[toc]

前言

Spring 是什么?下图是 Spring Framework 官网的一张图,恰到好处的为我们介绍了 Spring IoC 容器的作用。

image-20211204195201864

但是,只知道 “Magic Happens Here” 就满足了吗?学而不思则罔,我们要抱着知其然必知其所以然的态度去学习。Spring Framework 的各种精妙设计非常值得我们去学习,怎么学习呢?答案就是看源码。但是如果没有任何准备,一头扎进去 Spring Framework 的源码中,很容易迷失方向。本系列就是教你如何看懂 Spring Framework 的源码。

首先要说明的是,本系列只是提供了一些看 Spring 源码前导知识,很多示例做了简化和抽象。真正领略 Spring 设计的玄妙之处,还是应该在学习本系列之后再去 Debug 一遍 Spring 的源码,印象会更加深刻。

我们都知道,Spring 的两大核心就是 IoC 和 AOP ,其他功能都是在此基础上衍生而来。我们通常认为,Spring IoC 容器, Spring AOP,以及 Spring 对其它 JavaEE 服务的集成共同组成了 Spring 框架的 "质量三角"

所以,深入 Spring Framework 源码之前,我们至少需要对 Spring IoC 和 Spring AOP 有一定的认识。

本文我们先来了解下 Spring 的 IoC 容器。了解 Spring IoC 容器前,我们有必要先从 IoC 的概念说起。

什么是 IoC?

IoC:它的全称为 Inversion of Control,中文通常翻译为“控制反转”。控制反转(IoC)是一种设计原则(尽管有些人将其称为模式)。

IoC 符合 SOLID 中的 DIP( 依赖翻转原则)。

顾名思义,它用于反转面向对象设计中的各种控件,以实现松耦合。

从主动获取依赖关系的方式转向IoC方式,不只是一个方向上的改变,简单的转变背后实际上蕴藏着更多的玄机。

IOC 带来的好处: 不会对业务对象构成很强的侵入性,IoC 有助于设计松散耦合的类,使它们可测试,可维护和可扩展。

什么是IoC 容器?

IoC 容器是用来更好的实践 IoC 的一种方式。

IoC容器 的职责相对来说比较简单,主要有两个:对象的创建管理和对象间的依赖绑定。

  • 业务对象的构建管理。在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要有人来做。所以,IOC 容器 需要将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现。
  • 业务对象间的依赖绑定。对于IoC 容器来说,这个职责是最艰巨也是最重要的,这是它的最终使命之所在。如果不能完成这个职责,那么,无论业务对象如何的“呼喊”,也不会得到依赖对象的任何响应(最常见的倒是会收到一个NullPointerException)。IOC 容器通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

那么 IoC 容器如何找到我们想要的对象呢?方法有两种:依赖查找(DL),依赖注入(DI)。依赖查找不常使用,这里我们主要介绍 依赖注入的几种方式。

常见的依赖注入的方式有:构造方法注入,setter 方法注入,接口注入。

三种注入方式的比较:

  1. 构造方法注入。这种注入方式的优点就是,对象在构造完成之后,即已进入就绪状态,可以马上使用。缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。
  2. setter方法注入。因为方法可以命名,所以setter方法注入在描述性上要比构造方法注入好一些。 另外,setter方法可以被继承,允许设置默认值,而且有良好的IDE支持。缺点当然就是对象无法在构造完成后马上进入就绪状态。
  3. 接口注入。从注入方式的使用上来说,接口注入是现在不甚提倡的一种方式,基本处于“退役状态”。因为它强制被注入对象实现不必要的接口,带有侵入性。而构造方法注入和setter方法注入则不需要如此。

综上所述,构造方法注入和setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在使用最多的注入方式;而接口注入因为侵入性较强,近年来已经不流行了。

IoC 容器如何知道该创建哪些对象,并且为对象中的哪些属性提供依赖注入呢?这需要有个说明书。说明书的形式不限,只要能标识出需要创建的对象以及需要依赖注入的 属性即可。比如:

    1. 通过最基本的文本文件来记录被注入对象和其依赖对象之间的对应关系;
    1. 通过编写代码的方式来注册这些对应信息;
    1. 通过描述性较强的XML文件格式来记录对应信息;
    1. 通过元数据方式: 如Java5 之后通过注解的方式。
    <bean id="objectA" class="..ObjectA"> 
     <property name="objectB"> 
     <ref bean="objectB"/> 
     </property> 
    </bean> 
    
    <bean id="objectB" 
     class="..impl.ObjectB"> 
    </bean> 

一张图看懂 DIP , DI , IoC 的关系

img

使用各种设计模式也好,设计原则也好,我们的最终目的都是设计出 高内聚,低耦合,可测试,可扩展,可维护的软件。

了解完 IoC 容器的概念之后,我们来看下 IoC容器 的具体实现产品有哪些。比较知名的 IoC 容器有 google 的 guice ,Spring IoC 容器。

guice 是一款轻量级的 IoC 容器,可以看做 是 Spring 的 BeanFactory IoC 容器。关于 guice 可以去官网了解。这里我们主要介绍 Spring 为我们提供的 IoC 容器。

在深入 Spring IoC 容器实现之前,我们可以先停下来思考下,通过前面的了解,如果让我们实现一个简单的 IoC 容器,我们会怎么实现呢?

Spring 的IoC 容器

Spring的IoC容器是一个提供IoC支持的轻量级容器。除了基本的IoC支持,它作为轻量级容器还提供了IoC之外的支持。如在Spring的IoC容器之上,Spring还提供了相应的AOP框架支持、企业级服务集成等服务。

Spring IoC 容器相较于普通 IoC 容器的区别:

Spring提供了两种容器类型:BeanFactoryApplicationContext

  • BeanFactory:

基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的IoC容器选择。

  • ApplicationContext

ApplicationContext 在 BeanFactory 的基础上构建,是相对比较高级的容器实现,除了拥有 BeanFactory 的所有支持,ApplicationContext 还提供了其他高级特性,比如事件发布、国际化信息支持,AOP 等。ApplicationContext 所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext 类型的容器是比较合适的选择。

先来看 BeanFactory

BeanFactory,顾名思义,就是生产Bean的工厂。Spring框架提倡使用 POJO,把每个业务对象看作一个 JavaBean 对象,或许更容易理解为什么Spring的IoC基本容器会起这么一个名字。作为Spring提供的基本的IoC容器,BeanFactory 可以完成作为IoC的所有职责,包括业务对象的注册和对象间依赖关系的绑定。

org.springframework.beans.factory.BeanFactory 接口定义:

public interface BeanFactory {

    Object getBean(String name) throws BeansException;


    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;


    <T> T getBean(Class<T> requiredType) throws BeansException;


    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;


    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);


    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    
    boolean containsBean(String name);


    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;


    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;


    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;


    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;


    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;

    String[] getAliases(String name);

}
javaBean 的由来:

在java1996年发布,当年12月即发布了java bean 1.00-A, 有什么用呢? 通过统一的规范可以设置对象的值(get,set方法),这是最初的java bean;

在实际企业开发中,需要实现事务,安全,分布式, javabean 就不好用了。sun 公司就开始往上面堆功能,这里 java bean 就复杂为 EJB;

EJB 功能强大,但是太重了、此时出现 DI(依赖注入),AOP(面向切面) 技术,通过简单的 java bean 也能完成EJB的事情,这里的 java bean简化为POJO;

POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和 EJB 混淆所创造的简称。

Spring诞生了。

--- Spring In Action

延伸阅读:Java 帝国之Java bean (上)

BeanFactory 只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理,DefaultListableBeanFactory 就是这么一个比较通用的 BeanFactory 实现类。DefaultListableBeanFactory 除了间接地实现了 BeanFactory 接口,还实现了 BeanDefinitionRegistry 接口,该接口才是在 BeanFactory 的实现中担当 Bean 注册管理的角色。

基本上,BeanFactory 接口只定义如何访问容器内管理的Bean的方法,各个BeanFactory 的具体实现类负责具体 Bean 的注册以及管理工作。

BeanFactoryDefaultListableBeanFactory,BeanDefinitionRegistry 的关系如下图:

BeanDefinitionRegistry 接口定义抽象了Bean的注册逻辑。通常情况下,具体的 BeanFactory 实现类会实现这个接口来管理Bean的注册。

在 Spring IoC 容器中,每一个受管的对象,在容器中都会有一个BeanDefinition的实例与之相对应,该 BeanDefinition 的实例负责保存创建 Bean 的所有必要信息,包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等。当客户端向BeanFactory请求相应对象的时候,BeanFactory 会通过这些信息为客户端返回一个完备可用的对象实例。

参考:org.springframework.beans.factory.config.BeanDefinition doc 说明

略...

org.springframework.beans.factory.support.BeanDefinitionRegistry

public interface BeanDefinitionRegistry extends AliasRegistry {

    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException;

    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    boolean containsBeanDefinition(String beanName);


    String[] getBeanDefinitionNames();


    int getBeanDefinitionCount();

    boolean isBeanNameInUse(String beanName);
}

说了这么多,了解了 BeanDefinition 的作用有啥用呢?要是你只用 Spring 来进行简单 业务开发,那当然没什么用,但是如果要对 Spring 进行扩展,尤其是在容器启动阶段想做一些小动作,那不了解 BeanDefinition 可就无从下手了。

下面我们举例来看下 BeanDefinition 在 BeanFactory 中的作用吧。

直接编码方式

前面讲到,Spring IoC 容器支持四种方式的说明书,为了更好的理解 BeanFactory 的结构体系, 我们使用编码方式来说明举例:

@Data
public class School {

    private String name ;

    private Student student;
}

public static void main(String[] args) {
        DefaultListableBeanFactory registry = new DefaultListableBeanFactory();
       BeanFactory beanFactory =  bind(registry);

        School school = beanFactory.getBean("school", School.class);

        System.out.println(school);

    }

    private static BeanFactory bind(DefaultListableBeanFactory registry) {

        RootBeanDefinition schoolDefinition = new RootBeanDefinition(School.class);
        schoolDefinition.setLazyInit(true);
        RootBeanDefinition studentDefinition = new RootBeanDefinition(Student.class);

        // 给属性 name 赋值
        MutablePropertyValues schoolPropertyValues = new MutablePropertyValues();
        schoolPropertyValues.addPropertyValue("name","测试");
        // 给属性 student 赋值
        schoolPropertyValues.addPropertyValue(new PropertyValue("student",studentDefinition));
        
        schoolDefinition.setPropertyValues(schoolPropertyValues);
        registry.registerBeanDefinition("school",schoolDefinition);

        return registry;
    }

XML 方式

上面就是原始的编程方式初始化 BeanFactory,比较繁琐,上面代码仅帮助我们来理解 BeanDefiniation ,实际上我们用的比较多的是 通过 XML 配置文件来创建 BeanFactory 的,

<?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
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="school" class="com.itguang.pojo.bean.School">
        <!-- 属性注入-->
        <property name="name" value="测试"></property>
        <property name="student" ref="student"></property>
    </bean>
  
      <bean id="student" class="com.itguang.pojo.bean.Student"></bean>
</beans>
public static void main(String[] args) {
        DefaultListableBeanFactory registry = new DefaultListableBeanFactory();
       BeanFactory beanFactory =  bindWithXMLFile(registry);

        School school = beanFactory.getBean("school", School.class);

        System.out.println(school);

    }

    private static BeanFactory bindWithXMLFile(DefaultListableBeanFactory registry) {

        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
        reader.loadBeanDefinitions("classpath:spring.xml");

        return registry;
    }

注解方式

Java5 之后 Spring 开始支持注解方式初始化 BeanFactory。

如果不使用classpath-scanning功能的话,仍然部分依赖于“基于XML配置文件”的依赖注入方式。

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="com.itguang"/>

</beans>
@Data
@Component
public class School {

    private String name ;

    @Autowired
    private Student student;
}

@Data
@Component
public class Student {

    private String number;

    private Integer rank;

    private Score score;

    private User user;

}

    public static void main(String[] args) {
        
        BeanFactory beanFactory  = new ClassPathXmlApplicationContext("classpath:spring.xml");
        
        School school = beanFactory.getBean("school", School.class);

        System.out.println(school);
    }

到这里,我们可以对比下我们实现的 IoC 容器,和 Spring 的 BeanFactory 容器有什么区别呢?

Spring IoC 容器背后的秘密

Spring IoC 容器的作用,简单来说就是 以某种方式按照 说明书 ConfigurationMetadata 来创建我们的 POJO,进而组装成一个可用的轻量级容器系统。这么说还是笼统,接下来我们一起来看下 Spring IoC 容器的实现细节。

战略性观望

Spring IoC 容器的启动过程,战略上看可分为两阶段。即 容器启动阶段 和 Bean 实例化阶段。Spring 在每个阶段都加入了相应的扩展点,便于我们加入自定义的扩展逻辑。

1.容器启动阶段

总地来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。

容器启动开始,会从 Configuration MetaData 读取配置信息组装为 BeanDefinition ,然后把这些保存了 Bean 实例化必要信息的 BeanDefinition 注册到 BeanDefinitionRegistry ,这样容器的启动阶段就完成。

插手容器的启动

Spring提供了一种叫做 BeanFactoryPostProcessor 的容器扩展机制,该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition 所保存的信息做相应的修改。

@FunctionalInterface
public interface BeanFactoryPostProcessor {

    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

上面我们知道 ConfigurableListableBeanFactory 的实现类 DefaultListableBeanFactory 实现了 BeanDefinitionRegistry 接口,相应的,也就拥有了 BeanDefinitionRegistry 的所有能力。我们就可以拿到 所有的 BeanDefinition 做一些自己的处理了。

如何 @Value() 注解绑定的占位符参数解析

org.springframework.beans.factory.config.PlaceholderConfigurerSupport // 占位符解析器,实现类为 org.springframework.context.support.PropertySourcesPlaceholderConfigurer

当 BeanFactory 在第一阶段加载完成所有配置信息时,BeanFactory中保存的对象的属性信息还只是以占位符的形式存在,如${jdbc.url}${jdbc.driver}。当 PlaceholderConfigurerSupport 作为 BeanFactoryPostProcessor 被应用时,它会使用 properties 配置文件中的配置信息来替换相应 BeanDefinition 中占位符所表示的属性值。这样,当进入容器实现的第二阶段实例化bean时,bean定义中属性值就是最终替换完成的了。

PlaceholderConfigurerSupport 不仅会 从 yml properties 等文件中加载配置项,还会检查 java 的System 类中的 Properties 。

配置文件中的属性有的是需要加密配置的,此功能就可以在 PropertyResourceConfigurer 中来实现

2. Bean 实例化阶段

经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求方通过容器的getBean方法明确地请求某个对象,就会触发第二阶段的活动。

该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的 BeanDefinition 所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。

  • BeanFactory。对于 BeanFactory来说,对象实例化默认采用延迟初始化。只有调用 getBean()方法的时候才有可能触发容器启动的第二个阶段 ---- Bean实例化。

    为什么说可能呢,因为只有当 Bean 第一次被创建时,Bean 的实例化阶段才会触发。之后都会返回容器第一次缓存的Bean实例。

    参考 org.springframework.beans.factory.support.AbstractBeanFactory#getBean 方法查看 BeanFactory 的getBean 逻辑。

  • 对 ApplicationContext 来说,容器启动后后实例化所有的 Bean。但 ApplicationContext 仍然遵循容器启动的两个阶段,只不过在容器启动阶段完成之后,会立即启动 Bean 的实例化。

    参考 org.springframework.context.support.AbstractApplicationContext#refresh ,让所有的 Bean 都变为可用。

Bean 的一生

Spring 容器会对其所管理的对象基于统一的生命周期管理。这些Bean 完全摆脱了那种 “new 完之后被使用,脱离作用域后即被回收的命运”。Bean 的实例化过程如下图:

  • 实例化Bean对象

    容器在内部实现的时候,采用“策略模式(Strategy Pattern)”来决定采用何种方式初始化bean实例。通常,可以通过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类。默认情况下,容器内部采用的是CglibSubclassingInstantiationStrategy。容器只要根据相应bean定义的BeanDefintion取得实例化信息,结合CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。但是,返回方式上有些“点缀”。不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的对象实例进行包裹,返回相应的BeanWrapper实例。这里之所以返回 BeanWrapper 实例而并不是真正的 Bean 对象,就是为了第二步: 设置对象属性。

    使用 BeanWrapper 对 Bean 实例操作很方便,可以免去直接使用Java反射 API 操作对象实例的烦琐。

    拿到 BeanDefinition 真正创建 Bean 的方法。

    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#instantiateBean

        protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {
            try {
                Object beanInstance;
                if (System.getSecurityManager() != null) {
                    beanInstance = AccessController.doPrivileged(
                            (PrivilegedAction<Object>) () -> getInstantiationStrategy().instantiate(mbd, beanName, this),
                            getAccessControlContext());
                }
                else {
            // CglibSubclassingInstantiationStrategy
                    beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
                }
                BeanWrapper bw = new BeanWrapperImpl(beanInstance);
                initBeanWrapper(bw);
                return bw;
            }
            catch (Throwable ex) {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
            }
        }

    创建Bean 的过程最好亲自动手 Debug 一遍流程。

    至此,第一步结束。

  • 检查 Aware 接口并回调。当Bean 实例化完并且相关属性及依赖也设置完后。Spring 容器会检查当前Bean 是否实现了xxxAware 接口。对于 BeanFactory 容器来说, Aware 接口的设置是在 设置完Bean 的属性之后做的;但对于 ApplicationContext 类型的容器来说,Aware 接口的处理是在 BeanPostProcessor 这个后置处理器中完成的。

    org.springframework.beans.factory.Aware

    更多查看 AbstractAutowireCapableBeanFactory的 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean 方法

  • BeanPostProcessor。BeanPostProcessor 的概念容易与 BeanFactoryPostProcessor 的概念混淆。但只要记住BeanPostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段,这两个概念就比较容易区分了。与 BeanFactoryPostProcessor 通常会处理容器内所有符合条件的 BeanDefinition 类似,BeanPostProcessor会处理容器内所有符合条件的实例化后的对象实例。

    更多查看 AbstractAutowireCapableBeanFactory的 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean 方法

    Aware 接口处理:org.springframework.context.support.ApplicationContextAwareProcessor

    AOP 实现:org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator

    合适自定义 BeanPostProcessor 的时机:当我们需要对某一类 Bean 做一些特殊处理的时候,如我们相对 加了 @XXX 注解的 Bean 最一些处理,通常用在自定义注解的地方。

  • InitializingBeaninit-method

    实现了 InitializingBean 接口的作用在于,在对象实例化过程调用过“BeanPostProcessor的前置处理”之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用其 afterPropertiesSet() 方法进一步调整对象实例的状态。

    更多查看 AbstractAutowireCapableBeanFactory的 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean 方法
  • DisposableBeandestroy-method

    与 InitializingBean 和 init-method 用于对象的自定义初始化相对应,DisposableBean和 destroy-method 为对象提供了执行自定义销毁逻辑的机会。

    在对象实例初始化完成并注册了相关的回调方法之后,并不会马上执行。回调方法注册后,返回的对象实例即处于使用状态,只有该对象实例不再被使用的时候,才会执行相关的自定义销毁逻辑,此时通常也就是 Spring 容器关闭的时候。但 Spring 容器在关闭之前,不会聪明到自动调用这些回调方法。所以,需要我们告知容器,在哪个时间点来执行对象的自定义销毁方法。

    对于 BeanFactory 类型的容器来说:调用 ConfigurableBeanFactory 提供的 destroySingletons() 方法销毁容器中管理的所有singleton类型的对象实例。

    ((ConfigurableListableBeanFactory)beanFactory).destroySingletons();

    对于 ApplicationContext 类型的容器来说:道理是一样的。调用 ConfigurableApplicationContext 提供的registerShutdownHook 方法销毁容器中管理的所有 singleton 类型的对象实例。 AbstractApplicationContext 为我们实现了registerShutdownHook() 方法,该方法底层使用标准的 Runtime 类的 registerShutdownHook() 方式来调用相应 bean 对象的销毁逻辑,从而保证在Java虚拟机退出之前,这些 singtleton 类型的 bean 对象实例的自定义销毁逻辑会被执行。当然AbstractApplicationContext 注册的 shutdownHook 不只是调用对象实例的自定义销毁逻辑,也包括 ApplicationContext 相关的事件发布等。

    public static void main(String[] args) {
            ConfigurableApplicationContext ctx = (ConfigurableApplicationContext)new AnnotationConfigApplicationContext(ApplicationListenerDemo.class);
            // 注册关闭钩子
            ctx.registerShutdownHook();
           
            ctx.stop();
            ctx.close();
        }

BeanFactory小结

上面我们主要介绍 Spring 的 BeanFactory 容器,BeanFactory是Spring提供的基础IoC容器,但并不是Spring提供的唯一IoC容器。我们之前提到过,ApplicationContext构建于BeanFactory之上,提供了许多BeanFactory之外的特性。接下里我们就一起看下 Spring Ioc 容器: ApplicationContext 为我们提供的完整能力吧。

ApplicationContext

作为Spring提供的较之BeanFactory更为先进的IoC容器实现,ApplicationContext除了拥有BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProcessor、BeanPostProcessor以及其他特殊类型bean的自动识别、容器启动后bean实例的自动初始化、国际化的信息支持、容器内事件发布等。

ApplicationContext 容器的完整启动过程就在 org.springframework.context.support.AbstractApplicationContext#refresh 方法中。

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

ApplicationContext 容器启动过程比较复杂,篇幅所限,本文我们只讲 容器内事件发布 功能如何实现的,其他功能留给大家自行探究。

容器内事件发布

Spring 的 ApplicationContext 容器内部允许以 org.springframework.context.ApplicationEvent 的形式发布事件,容器内注册的org.springframework.context.ApplicationListener 类型的 bean 会被 ApplicationContext 容器自动识别,它们负责监听容器内发布的所有 ApplicationEvent 类型的事件。也就是说,一旦容器内发布 ApplicationEvent 及其子类型的事件,注册到容器的ApplicationListener 就会对这些事件进行处理。

下面是Spring 容器内事件发布实现类图。

  • ApplicationEvent:Spring容器内自定义事件类型,继承自java.util.EventObject,它是一个抽象类,需要根据情况提供相应子类以区分不同情况。默认情况下,Spring提供了几个实现。ContextClosedEvent,ContextRefreshedEvent,RequestHandledEvent 等。

    org.springframework.web.servlet.FrameworkServlet#publishRequestHandledEvent
  • ApplicationListener:ApplicationContext容器在启动时,会自动识别并加载EventListener类型bean定义,一旦容器内有事件发布,将通知这些注册到容器的EventListener。
  • ApplicationContext :实现了 ApplicationEventPublisher 接口,也就拥有了事件发布的能力。

    ApplicationContext容器的事件发布功能全部委托给了 ApplicationEventMulticaster 来做,所以,容器启动时,就会检查容器内是否存在名称为 applicationEventMulticaster 的 ApplicationEventMulticaster 对象实例。有的话就使用提供的实现,没有则默认初始化一个 SimpleApplicationEventMulticaster 作为将会使用的 ApplicationEventMulticaster 。

    org.springframework.context.support.AbstractApplicationContext#refresh

    从 ApplicationContext 的实现上来看,虽然其实现了很多接口,但是具体的方法实现都是通过内部持有一个个的 接口实现类来完成的。

总结

本文从 IoC 讲起,先介绍了 IoC 的概念和起源。又介绍了 Spring IoC 的容器的两个实现,一个 BeanFactory,一个ApplicationContext ,这两个容器是理解 Spring 的核心,通过上面的学习,相信大家已经对 Spring IoC 容器有了一个新的认识,关于其它功能的实现细节,需要大家亲自翻阅源码,Debug,这样才能对 Spring IoC 容器的整个流程更加熟悉。

下一篇,Spring 三部曲(二):Spring AOP 我们会介绍 Spring AOP 如何实现的,敬请期待。。。

目录
相关文章
|
11天前
|
Java 测试技术 开发工具
ApplicationArguments读取应用程序参数并注入到IOC容器
ApplicationArguments读取应用程序参数并注入到IOC容器
ApplicationArguments读取应用程序参数并注入到IOC容器
|
11天前
|
存储 前端开发 Java
springboot中的第二个IOC容器BootstrapContext
springboot中的第二个IOC容器BootstrapContext
springboot中的第二个IOC容器BootstrapContext
|
13天前
|
Java 容器 Spring
【spring(一)】核心容器总结
【spring(一)】核心容器总结
|
17天前
|
Java 开发者 容器
【Java】深入了解Spring容器的两个关键组件
【Java】深入了解Spring容器的两个关键组件
8 0
|
18天前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (下)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界
|
18天前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
|
15天前
|
Java Go 开发者
Docker容器技术简介及其与Go语言的结合点
【2月更文挑战第23天】本文首先概述了Docker容器技术的核心概念和优势,接着探讨了Go语言与Docker容器技术的结合点。通过阐述Docker的轻量级、可移植性和版本控制等特性,以及Go语言在容器化应用中的优势,本文旨在说明两者结合能够实现更高效、灵活的应用开发和部署。
|
16天前
|
Oracle 关系型数据库 数据库
|
25天前
|
开发者 Docker Python
深入浅出:使用Docker容器化部署Python Web应用
在当今快速发展的软件开发领域,Docker作为一个开放平台,为开发者提供了将应用打包在轻量级、可移植的容器中的能力,从而简化了部署和管理应用程序的复杂性。本文将通过一个简单的Python Web应用示例,引导读者理解Docker的基本概念、容器化的优势以及如何使用Docker来容器化部署Python Web应用。我们将从零开始,逐步探索创建Dockerfile、构建镜像、运行容器等关键步骤,旨在为读者提供一个清晰、易于理解的指南,帮助他们掌握使用Docker容器化部署应用的技能。
|
4天前
|
监控 数据可视化 虚拟化
Docker容器常用命令笔记分享
Docker容器常用命令笔记分享
33 2

相关产品

  • 容器镜像服务
  • 容器服务Kubernetes版