马野先生
  • 博客
  • 关于我

Spring Bean工作原理详解 - 2020年09月01日

本文由 简悦 SimpRead 转码, 原文地址 www.cnblogs.com

本文 Github 开源项目 https://github.com/Snailclimb/JavaGuide,只供自己学习总结无商业用途,如有侵权,联系删除

前言

在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。

Spring 中的 bean 默认都是单例的,这些单例 Bean 在多线程程序下如何保证线程安全呢? 例如对于 Web 应用来说,Web 容器对于每个用户请求都创建一个单独的 Sevlet 线程来处理请求,引入 Spring 框架之后,每个 Action 都是单例的,那么对于 Spring 托管的单例 Service Bean,如何保证其安全呢? Spring 的单例是基于 BeanFactory 也就是 Spring 容器的,单例 Bean 在此容器内只有一个,Java 的单例是基于 JVM,每个 JVM 内只有一个实例。

bean 的作用域

创建一个 bean 定义,其实质是用该 bean 定义对应的类来创建真正实例的 “配方”。把 bean 定义看成一个配方很有意义,它与 class 很类似,只根据一张“处方” 就可以创建多个实例。不仅可以控制注入到对象中的各种依赖和配置值,还可以控制该对象的作用域。这样可以灵活选择所建对象的作用域,而不必在 Java Class 级定义作用域。Spring Framework 支持五种作用域,分别阐述如下表。

类别说明
singleton在 Spring IoC 容器中仅存在一个 Bean 实例,Bean 以单例方式存在,默认值
prototype每次从容器中调用 Bean 时,都返回一个新的实例,即每次调用 getBean() 时,相当于执行 new XxxBean()
request每次 HTTP 请求都会创建一个新的 Bean,该该作用域仅适用于 WebApplicationContext 环境
session同一个 HTTP Session 共享一个 Bean,不同 Session 使用不同 Bean,仅适用于 WebApplicationContext 环境
globalSession一般用于 Portlet 应用环境,该作用域仅适用于 WebApplicationContext 环境

五种作用域中,request、session 和 global session 三种作用域仅在基于 web 的应用中使用(不必关心你所采用的是什么 web 应用框架),只能用在基于 web 的 Spring ApplicationContext 环境。

singleton

singleton唯一 bean 实例,当一个 bean 的作用域为 singleton,那么 Spring IoC 容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回 bean 的同一实例。 singleton 是单例类型 (对应于单例模式),就是在创建起容器时就同时自动创建了一个 bean 的对象,不管你是否使用,但我们可以指定 Bean 节点的 lazy-init=”true” 来延迟初始化 bean,这时候,只有在第一次获取 bean 时才会初始化 bean,即第一次请求该 bean 时才初始化。 每次获取到的对象都是同一个对象。注意,singleton 作用域是 Spring 中的缺省作用域。要在 XML 中将 bean 定义成 singleton ,可以这样配置:

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

也可以通过 @Scope 注解(它可以显示指定 bean 的作用范围。)的方式

@Service
@Scope("singleton")
public class ServiceImpl{

}

prototype

prototype每次请求都会创建一个新的 bean 实例, 当一个 bean 的作用域为 prototype,表示一个 bean 定义对应多个对象实例。prototype 作用域的 bean 会导致在每次对该 bean 请求(将其注入到另一个 bean 中,或者以程序的方式调用容器的 getBean() 方法)时都会创建一个新的 bean 实例。prototype 是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取 bean 的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的 bean 应该使用 prototype 作用域,而对无状态的 bean 则应该使用 singleton 作用域。 在 XML 中将 bean 定义成 prototype ,可以这样配置:

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
 或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

通过 @Scope 注解的方式实现就不做演示了。

request

request每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效,request 只适用于 Web 程序,每一次 HTTP 请求都会产生一个新的 bean,同时该 bean 仅在当前 HTTP request 内有效,当请求结束后,该对象的生命周期即告结束。 在 XML 中将 bean 定义成 request ,可以这样配置:

<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

session

session每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效,session 只适用于 Web 程序,session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 bean,同时该 bean 仅在当前 HTTP session 内有效. 与 request 作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的 HTTP session 中根据 userPreferences 创建的实例,将不会看到这些特定于某个 HTTP session 的状态变化。当 HTTP session 最终被废弃的时候,在该 HTTP session 作用域内的 bean 也会被废弃掉。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

globalSession

global session 作用域类似于标准的 HTTP session 作用域,不过仅仅在基于 portlet 的 web 应用中才有意义。Portlet 规范定义了全局 Session 的概念,它被所有构成某个 portlet web 应用的各种不同的 portle t 所共享。在 global session 作用域中定义的 bean 被限定于全局 portlet Session 的生命周期范围内。

<bean id="user" class="com.foo.Preferences "scope="globalSession"/>

bean 的生命周期

Spring Bean 是 Spring 应用中最最重要的部分了。所以来看看 Spring 容器在初始化一个 bean 的时候会做那些事情,顺序是怎样的,在容器关闭的时候,又会做哪些事情。

spring 版本:4.2.3.RELEASE
鉴于 Spring 源码是用 gradle 构建的,我也决定舍弃我大 maven,尝试下洪菊推荐过的 gradle。运行 beanLifeCycle 模块下的 junit test 即可在控制台看到如下输出,可以清楚了解 Spring 容器在创建,初始化和销毁 Bean 的时候依次做了那些事情。

Spring容器初始化
=====================================
调用GiraffeService无参构造函数
GiraffeService中利用set方法设置属性值
调用setBeanName:: Bean Name defined in context=giraffeService
调用setBeanClassLoader,ClassLoader Name = sun.misc.Launcher$AppClassLoader
调用setBeanFactory,setBeanFactory:: giraffe bean singleton=true
调用setEnvironment
调用setResourceLoader:: Resource File Name=spring-beans.xml
调用setApplicationEventPublisher
调用setApplicationContext:: Bean Definition Names=\[giraffeService, org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#0, com.giraffe.spring.service.GiraffeServicePostProcessor#0\]
执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=giraffeService
调用PostConstruct注解标注的方法
执行InitializingBean接口的afterPropertiesSet方法
执行配置的init-method
执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=giraffeService
Spring容器初始化完毕
=====================================
从容器中获取Bean
giraffe Name=李光洙
=====================================
调用preDestroy注解标注的方法
执行DisposableBean接口的destroy方法
执行配置的destroy-method
Spring容器关闭

先来看看,Spring 在 Bean 从创建到销毁的生命周期中可能做得事情。

initialization 和 destroy

有时我们需要在 Bean 属性值 set 好之后和 Bean 销毁之前做一些事情,比如检查 Bean 中某个属性是否被正常的设置好值了。Spring 框架提供了多种方法让我们可以在 Spring Bean 的生命周期中执行 initialization 和 pre-destroy 方法。

1. 实现 InitializingBean 和 DisposableBean 接口

这两个接口都只包含一个方法。通过实现 InitializingBean 接口的 afterPropertiesSet() 方法可以在 Bean 属性值设置好之后做一些操作,实现 DisposableBean 接口的 destroy() 方法可以在销毁 Bean 之前做一些操作。

例子如下:

public class GiraffeService implements InitializingBean,DisposableBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行InitializingBean接口的afterPropertiesSet方法");
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("执行DisposableBean接口的destroy方法");
    }
}

这种方法比较简单,但是不建议使用。因为这样会将 Bean 的实现和 Spring 框架耦合在一起。

2. 在 bean 的配置文件中指定 init-method 和 destroy-method 方法

Spring 允许我们创建自己的 init 方法和 destroy 方法,只要在 Bean 的配置文件中指定 init-method 和 destroy-method 的值就可以在 Bean 初始化时和销毁之前执行一些操作。

例子如下:

public class GiraffeService {
    //通过<bean>的destroy-method属性指定的销毁方法
    public void destroyMethod() throws Exception {
        System.out.println("执行配置的destroy-method");
    }
    //通过<bean>的init-method属性指定的初始化方法
    public void initMethod() throws Exception {
        System.out.println("执行配置的init-method");
    }
}

配置文件中的配置:

<bean >
</bean>

需要注意的是自定义的 init-method 和 post-method 方法可以抛异常但是不能有参数。

这种方式比较推荐,因为可以自己创建方法,无需将 Bean 的实现直接依赖于 spring 的框架。

3. 使用 @PostConstruct 和 @PreDestroy 注解

除了 xml 配置的方式,Spring 也支持用 @PostConstruct和 @PreDestroy注解来指定 init 和 destroy 方法。这两个注解均在javax.annotation 包中。为了注解可以生效,需要在配置文件中定义 org.springframework.context.annotation.CommonAnnotationBeanPostProcessor 或 context:annotation-config

例子如下:

public class GiraffeService {
    @PostConstruct
    public void initPostConstruct(){
        System.out.println("执行PostConstruct注解标注的方法");
    }
    @PreDestroy
    public void preDestroy(){
        System.out.println("执行preDestroy注解标注的方法");
    }
}

配置文件:

<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />

实现 XxxAware 接口 在 Bean 中使用 Spring 框架的一些对象

实现XxxAware接口 在Bean中使用Spring框架的一些对象,有些时候我们需要在 Bean 的初始化中使用 Spring 框架自身的一些对象来执行一些操作,比如获取 ServletContext 的一些参数,获取 ApplicaitionContext 中的 BeanDefinition 的名字,获取 Bean 在容器中的名字等等。为了让 Bean 可以获取到框架自身的一些对象,Spring 提供了一组名为 * Aware 的接口。

这些接口均继承于org.springframework.beans.factory.Aware标记接口,并提供一个将由 Bean 实现的 set * 方法, Spring 通过基于 setter 的依赖注入方式使相应的对象可以被 Bean 使用。
网上说,这些接口是利用观察者模式实现的,类似于 servlet listeners,目前还不明白,不过这也不在本文的讨论范围内。
介绍一些重要的 Aware 接口:

  • ApplicationContextAware: 获得 ApplicationContext 对象, 可以用来获取所有 Bean definition 的名字。
  • BeanFactoryAware: 获得 BeanFactory 对象,可以用来检测 Bean 的作用域。
  • BeanNameAware: 获得 Bean 在配置文件中定义的名字。
  • ResourceLoaderAware: 获得 ResourceLoader 对象,可以获得 classpath 中某个文件。
  • ServletContextAware: 在一个 MVC 应用中可以获取 ServletContext 对象,可以读取 context 中的参数。
  • ServletConfigAware: 在一个 MVC 应用中可以获取 ServletConfig 对象,可以读取 config 中的参数。
public class GiraffeService implements   ApplicationContextAware,
        ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware,
        BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{
         @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("执行setBeanClassLoader,ClassLoader Name = " + classLoader.getClass().getName());
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("执行setBeanFactory,setBeanFactory:: giraffe bean singleton=" +  beanFactory.isSingleton("giraffeService"));
    }
    @Override
    public void setBeanName(String s) {
        System.out.println("执行setBeanName:: Bean Name defined in context="
                + s);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("执行setApplicationContext:: Bean Definition Names="
                + Arrays.toString(applicationContext.getBeanDefinitionNames()));
    }
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        System.out.println("执行setApplicationEventPublisher");
    }
    @Override
    public void setEnvironment(Environment environment) {
        System.out.println("执行setEnvironment");
    }
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        Resource resource = resourceLoader.getResource("classpath:spring-beans.xml");
        System.out.println("执行setResourceLoader:: Resource File Name="
                + resource.getFilename());
    }
    @Override
    public void setImportMetadata(AnnotationMetadata annotationMetadata) {
        System.out.println("执行setImportMetadata");
    }
}

BeanPostProcessor

上面的 * Aware 接口是针对某个实现这些接口的 Bean 定制初始化的过程,
Spring 同样可以针对容器中的所有 Bean,或者某些 Bean 定制初始化过程,只需提供一个实现 BeanPostProcessor 接口的类即可。 该接口中包含两个方法,postProcessBeforeInitialization 和 postProcessAfterInitialization。 postProcessBeforeInitialization 方法会在容器中的 Bean 初始化之前执行, postProcessAfterInitialization 方法在容器中的 Bean 初始化之后执行。

例子如下:

public class CustomerBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=" + beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=" + beanName);
        return bean;
    }
}

要将 BeanPostProcessor 的 Bean 像其他 Bean 一样定义在配置文件中

<bean class="com.giraffe.spring.service.CustomerBeanPostProcessor"/>

总结

所以。。。结合第一节控制台输出的内容,Spring Bean 的生命周期是这样纸的:

  • Bean 容器找到配置文件中 Spring Bean 的定义。
  • Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
  • 如果涉及到一些属性值 利用 set 方法设置一些属性值。
  • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName() 方法,传入 Bean 的名字。
  • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader() 方法,传入 ClassLoader 对象的实例。
  • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanClassLoader() 方法,传入 ClassLoader 对象的实例。
  • 与上面的类似,如果实现了其他 * Aware 接口,就调用相应的方法。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessBeforeInitialization() 方法
  • 如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet() 方法。
  • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessAfterInitialization() 方法
  • 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
  • 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

用图表示一下:

与之比较类似的中文版本:

其实很多时候我们并不会真的去实现上面说描述的那些接口,那么下面我们就除去那些接口,针对 bean 的单例和非单例来描述下 bean 的生命周期:

单例管理的对象

当 scope=”singleton”,即默认情况下,会在启动容器时(即实例化容器时)时实例化。但我们可以指定 Bean 节点的 lazy-init=”true” 来延迟初始化 bean,这时候,只有在第一次获取 bean 时才会初始化 bean,即第一次请求该 bean 时才初始化。如下配置:

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" lazy-init="true"/>

如果想对所有的默认单例 bean 都应用延迟初始化,可以在根节点 beans 设置 default-lazy-init 属性为 true,如下所示:

<beans default-lazy-init="true" …>

默认情况下,Spring 在读取 xml 文件的时候,就会创建对象。在创建对象的时候先调用构造器,然后调用 init-method 属性值中所指定的方法。对象在被销毁的时候,会调用 destroy-method 属性值中所指定的方法(例如调用 Container.destroy() 方法的时候)。写一个测试类,代码如下:

public class LifeBean {
    private String name;  

    public LifeBean(){  
        System.out.println("LifeBean()构造函数");  
    }  
    public String getName() {  
        return name;  
    }  

    public void setName(String name) {  
        System.out.println("setName()");  
        this.name = name;  
    }  

    public void init(){  
        System.out.println("this is init of lifeBean");  
    }  

    public void destory(){  
        System.out.println("this is destory of lifeBean " + this);  
    }  
}

 life.xml 配置如下:

<bean id="life\_singleton" class="com.bean.LifeBean" scope="singleton" 
            init-method="init" destroy-method="destory" lazy-init="true"/>

测试代码:

public class LifeTest {
    @Test 
    public void test() {
        AbstractApplicationContext container = 
        new ClassPathXmlApplicationContext("life.xml");
        LifeBean life1 = (LifeBean)container.getBean("life");
        System.out.println(life1);
        container.close();
    }
}

运行结果:

LifeBean()构造函数
this is init of lifeBean
com.bean.LifeBean@573f2bb1
……
this is destory of lifeBean com.bean.LifeBean@573f2bb1

非单例管理的对象

当scope=”prototype”时,容器也会延迟初始化 bean,Spring 读取 xml 文件的时候,并不会立刻创建对象,而是在第一次请求该 bean 时才初始化(如调用 getBean 方法时)。在第一次请求每一个 prototype 的 bean 时,Spring 容器都会调用其构造器创建这个对象,然后调用init-method属性值中所指定的方法。对象销毁的时候,Spring 容器不会帮我们调用任何方法,因为是非单例,这个类型的对象有很多个,Spring 容器一旦把这个对象交给你之后,就不再管理这个对象了。

为了测试 prototype bean 的生命周期 life.xml 配置如下:

<bean id="life\_prototype" class="com.bean.LifeBean" scope="prototype" init-method="init" destroy-method="destory"/>

测试程序:

public class LifeTest {
    @Test 
    public void test() {
        AbstractApplicationContext container = new ClassPathXmlApplicationContext("life.xml");
        LifeBean life1 = (LifeBean)container.getBean("life\_singleton");
        System.out.println(life1);

        LifeBean life3 = (LifeBean)container.getBean("life\_prototype");
        System.out.println(life3);
        container.close();
    }
}

运行结果:

LifeBean()构造函数
this is init of lifeBean
com.bean.LifeBean@573f2bb1
LifeBean()构造函数
this is init of lifeBean
com.bean.LifeBean@5ae9a829
……
this is destory of lifeBean com.bean.LifeBean@573f2bb1

可以发现,对于作用域为 prototype 的 bean ,其destroy方法并没有被调用。如果 bean 的 scope 设为 prototype 时,当容器关闭时,destroy 方法不会被调用。对于 prototype 作用域的 bean,有一点非常重要,那就是 Spring 不能对一个 prototype bean 的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个 prototype 实例后,将它交给客户端,随后就对该 prototype 实例不闻不问了。 不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法。但对 prototype 而言,任何配置好的析构生命周期回调方法都将不会被调用。清除 prototype 作用域的对象并释放任何 prototype bean 所持有的昂贵资源,都是客户端代码的职责(让 Spring 容器释放被 prototype 作用域 bean 占用资源的一种可行方式是,通过使用 bean 的后置处理器,该处理器持有要被清除的 bean 的引用)。谈及 prototype 作用域的 bean 时,在某些方面你可以将 Spring 容器的角色看作是 Java new 操作的替代者,任何迟于该时间点的生命周期事宜都得交由客户端来处理。

Spring 容器可以管理 singleton 作用域下 bean 的生命周期,在此作用域下,Spring 能够精确地知道 bean 何时被创建,何时初始化完成,以及何时被销毁。而对于 prototype 作用域的 bean,Spring 只负责创建,当容器创建了 bean 的实例后,bean 的实例就交给了客户端的代码管理,Spring 容器将不再跟踪其生命周期,并且不会管理那些被配置成 prototype 作用域的 bean 的生命周期。

说明

本文的完成结合了下面两篇文章,并做了相应修改:

  • https://blog.csdn.net/fuzhongmin05/article/details/73389779
  • https://yemengying.com/2016/07/14/spring-bean-life-cycle/

由于本文非本人独立原创,所以未声明为原创!在此说明!

__EOF__

作  者:Y.yang Blog
出  处:https://www.cnblogs.com/yoci/p/10642553.html
关于博主:热爱技术,喜欢专研,一个平凡的架构梦,每天进步一点点,共勉~。
版权声明:- 个人学习笔记 - 非商业性使用,部分资料来自互联网如有侵权联系删除,欢迎转载,但希望能给出原文连接。
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!



返回主页


一孔之见,姑妄言之 | © 马野先生 | B站