Spring中bean的生命周期详解

一、前言

        上一篇《Spring之依赖注入》,清楚了Spring中bean的装配过程,bean在被Spring容器装配时,作用域(Scope)分为singleton、prototype、request、session,其中request和session适用于WebApplicationContext环境。

  • singleton——单例模式,Spring的容器中只有一个实例,有Spring容器创建,创建时间点,分为启动创建和延迟加载,根据bean对象配置的属性lazy-init属性决定,默认为false,配置为true时,会在第一次调用时创建该对象。
  • prototype——非单例模式,每次从Spring容器中调用时,都返回一个新实例。创建bean对象的时间点为bean被调用时。所以都是lazy-init。
  • request——每次request请求都会创建一个新的bean。
  • session——同一个session共享一个bean。

二、Bean的作用域

        在Spring中,由Spring IoC容器所管理的对象,称之为bean,由IoC容器初始化、装配及管理。

        bean的作用域类别包含:singleton、prototype、request、session。即常说的scope类别。

2.1 singleton

        Spring装配bean默认类别,单例模式,Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。

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

2.2 Prototype

        多实例对象模式,即每次请求该bean对象时,都会创建一个新的bean实例。

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

2.3 Request

        表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。

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

        针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。

2.4 Session

        表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

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

        针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

三、不同作用域的Bean的生命周期

        Bean实例,生命周期的执行过程如下:

  1. Spring对bean进行实例化,默认bean时Singleton
  2. Spring对bean进行依赖注入,
  3. 如果bean实现了BeanNameAware接口,Spring将bean的名称,传给setBeanName()方法。
  4. 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory(),将beanFactoryc实例传入
  5. 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将应用的上下文引用传入bean中。
  6. 如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization()方法将被调用。
  7. 如果bean中有方法添加了@PostConstruct注解,那么该方法将被调用。
  8. 如果bean实现了InitializingBean接口,Spring将调用它的afterPropertiesSet()接口方法,类似bean在xml中配置,使用init-method属性声明了初始化方法,该方法也会被调用。
  9. 此时bean已经就绪,可以被应用程序使用,它们将一直在应用山下文中,直到Spring容器销毁。
  10. 如果bean中有方法使用了@PreDestory注解,在Spring容器销毁时,将被调用。
  11. 如果bean中实现了DisposableBean接口,Spring容器在销毁时,将调用它的destroy()方法。同样的xml配置了destroy-method属性,一个原理。

        Aware接口,正常情况下不需要使用,除非不得不用,用的比较多的也就是为bean起别名。

3.1 singleton模式

        scope="singleton",是单例模式,Spring容器启动时会初始化,除非配置了lazy-init="true",配置了延迟加载后,会在第一次调用时创建bean实例。该bean实例的生命周期为Spring容器的生命,会一直在Spring容器内,期间由Spring容器管理bean实例,直到Spring容器关闭

        ps: Spring容器关闭,也是将初始化的所有bean对象引用只想null,有JVM回收。

<bean id="hello" class="com.cc.pojo.hello" lazy-init="true"/>

3.2 prototype模式

        每次调用bean实例时,Spring容器都会创建一个bean对象,并返回给调用者。创建完对象返回后,Spring容器将不在管理bean对象。有调用者自己控制对象的销毁,最常见的,就是调用程序使用完,由JVM垃圾回收掉。

<bean id="hello" class="com.cc.Hello" scope="prototype" init-method="init" destroy-method="destory"/>

3.3 request模式

        一次request请求都会创建一个新的bean,当request请求结束,bean结束,有JVM回收。

3.4 session模式

        用户的session会共享一个bean,待session断开,bean生命结束,会被JVM回收。

四、思考

        回忆前面的样例,每次向获取bean对象时,都是下面的代码方式。

ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        每次都会产生一个上下文的引用对象,这样不太好,其实只需要在程序启动时,产生这个工厂就可以了,后面获取bean,都调用同样的工厂实例。

        解决这个问题,也比较简单,在web容器启动的时候,将ApplicationContext转移到ServletContext中,因为Web应用中,所有的Servlet都共享一个ServletContext对象。使用ServletContextListener去监听ServletContext,当web应用启动的时候,将ApplicationContext加载到ServletContext中。

        可以看下源码的实现,在加载类的初始化方法中,参考代码中注释

package org.springframework.web.context;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    /**
    * 初始化方法加入ApplicationContext
    */
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

        初始化方法,注释部分是将ApplicationContext上下文信息加入到ServletContext,其中键值为:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
        } else {
            servletContext.log("Initializing Spring root WebApplicationContext");
            Log logger = LogFactory.getLog(ContextLoader.class);
            if (logger.isInfoEnabled()) {
                logger.info("Root WebApplicationContext: initialization started");
            }

            long startTime = System.currentTimeMillis();

            try {
                if (this.context == null) {
                    this.context = this.createWebApplicationContext(servletContext);
                }

                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                    if (!cwac.isActive()) {
                        if (cwac.getParent() == null) {
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }

                        this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }

    /**
    * 重点,将context加入到servletContext,key值为:        
    * WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
    */  
  
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                if (ccl == ContextLoader.class.getClassLoader()) {
                    currentContext = this.context;
                } else if (ccl != null) {
                    currentContextPerThread.put(ccl, this.context);
                }

                if (logger.isInfoEnabled()) {
                    long elapsedTime = System.currentTimeMillis() - startTime;
                    logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
                }

                return this.context;
            } catch (Error | RuntimeException var8) {
                logger.error("Context initialization failed", var8);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
                throw var8;
            }
        }
    }

        接着,还有点小说明,在取出上述以key值WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE存储在ServletContext中的ApplicationContext时,我们却不需要感知这个复杂的key值,WebApplicationContextUtil工具类做了封装,如下:

WebApplicationContext context= WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());

        可以看下getWebApplicationContext()方法的实现,就可以明白了,封装方法自动传入了存储在ServletContext中ApplicationContext对象的key值。

@Nullable
    public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
        return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值