Spring中Bean的作用域—Spring官方文档系列

spring bean的作用域分为以下五种:
1、singleton(默认模式):单例,指一个bean容器中只存在一份
2、prototype:每次请求(每次使用)创建新的实例,destroy方式不生效
3、request:每次http请求创建一个实例且仅在当前request内有效
4、session:同上,每次http请求创建,当前session中有效
5、global session:基于portlet的web中有效(portlet定义了global sessio),如果在web中,同session

一、单例作用域

            

单例bean只会产生一个实例,并且所有对具有与该bean定义匹配的id或id的bean的请求Spring容器都只会返回一个实例。

换句话说,当你定义一个bean定义,并且作为一个singleton,Spring IoC容器创建该bean所定义的对象的一个实例。 这个单个实例存储在这样的单例bean的缓存中,所有后续的请求和引用返回缓存的这个对象。 

Spring的单例bean的概念不同于Singleton模式,和在Gang of Four(GoF)模式的书中定义的不同的地方。 GoF Singleton硬编码对象的范围,使得每一个类加载器内会产生单例类的一个实例。 Spring单例的作用域恰如其名:*一个容器对应一个bean *。 这意味着如果你在一个Spring容器中为一个特定的类定义了一个bean,那么Spring容器将创建一个实例,并且只有一个由该bean定义定义的类的实例。 *singleton scope (单例作用域)*是Spring 中的默认配置。 要将bean定义为XML中的单例,您可以编写,例如:

<bean id="accountService" class="com.foo.DefaultAccountService"/>

<!-- 和下面的写法等价,因为单例作用域是默认的 -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

    还可以通过注解定义,例如:

  1. import java.util.HashSet;  
  2. import java.util.Set;  
  3.   
  4. import org.springframework.context.annotation.Scope;  
  5. import org.springframework.stereotype.Controller;  
  6. import org.springframework.web.bind.annotation.RequestMapping;  
  7.   
  8. @Controller  
  9. @RequestMapping(value = "/spring")  
  10. @Scope("prototype")  
  11. public class SpringDemo {  
  12.   
  13.     private static Set<Object> set = new HashSet<>();  
  14.   
  15.     @RequestMapping(value="test")  
  16.     public void test(){  
  17.         set.add(this);  
  18.         System.out.println(set.size());  
  19.     }  

二、prototype  作用域

设置bean作用域为prototype,就是非单例,bean部署的prototype scope导致每次对该特定bean的请求时都会创建新的bean实例。 也就是说,bean被注入到另一个bean中,或者通过容器上的getBean()方法调用来请求它。 通常,对所有有状态bean使用prototype scope,对无状态bean使用singleton scope。

下图说明了Spring prototype scope。 数据访问对象(DAO)通常不被配置为 prototype scope,因为通常DAO不持有任何会话状态; 

如何在XML中定义prototype bean:

<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>

与其他作用域相比,Spring不管理prototype bean的完整生命周期:容器实例化,配置和以其他方式组装原型对象,并将其传递给客户端,没有原型实例的进一步记录。因此,尽管初始化生命周期回调方法在所有对象上被调用,不管范围如何,在prototype的情况下,配置的销毁生命周期回调被调用。客户端代码必须清理prototype作用域对象,并释放prototype bean持有的昂贵资源。要使Spring容器释放prototype作用域的bean所拥有的资源,请尝试使用自定义bean post-processor,它持有需要被清理bean的引用。

在某些方面,Spring容器在prototype作用域bean中的作用是Java“new”运算符的替代。经由该点的所实例化的bean的所有生命周期管理必须由客户端处理。


Singleton beans 与prototype-bean依赖关系:

    这里简单说一下 Singleton  bean 与 prototype bean 之间的依赖关系 :

    在大多数应用场景中,容器中的大多数bean都是 singletons。 当单例bean需要与另一个单例bean协作或非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个的属性来处理依赖关系。不过对于具有不同生命周期的bean 来说这样做就会出现问题。 假设单例bean A需要使用非单例(原型)bean B,也许在A上的每个方法调用上。容器仅创建单例bean A一次,因此只有一次机会来设置属性。 这样就没办法 在需要的时候每次让容器为bean A提供一个新的bean B实例。

解决方案是放弃一些控制的反转。 您可以通过实现以下操作来 定义一个类实现 ApplicationContextAware接口,并通过对容器调用getBean(“B”)在每次bean A需要它时调用SpringContextHolder的静态方法getBean()来获取实例B, 如下:

@Service()
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {

    private static ApplicationContext applicationContext = null;

    /**
     * 实现ApplicationContextAware接口, 注入Context到静态变量中.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        if (SpringContextHolder.applicationContext != null) {
            System.out.println("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
        }
        System.out.println("Spring容器启动,将容器实例注入到SpringContextHolder实例bean中");
        SpringContextHolder.applicationContext = applicationContext;
    }

    /**
     * 实现DisposableBean接口,重写destroy方法,相当于destroy-method,bean被销毁的时候被调用,
     * 实现在Context关闭时清理静态变量的目的
     * 令:还有InitializingBean接口,重写afterPropertiesSet方法,相当于init-method,bean被实例化的时候被调用
     */
    @Override
    public void destroy() throws Exception {
        applicationContext = null;
    }

    /**
     * 取得存储在静态变量中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }
}

三、Request, session, application, and WebSocket scopes

    request,session,global session作用域,只有在spring web ApplicationContext的实现中(比如XmlWebApplicationContext)才会起作用,若在常规Spring IoC容器中使用,比如ClassPathXmlApplicationContext中,就会收到一个异常IllegalStateException来告诉你不能识别的bean作用域

Request scope

考虑下面这种bean定义:

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>

Spring容器通过对每个HTTP请求使用loginAction bean定义来创建一个LoginAction bean的新实例。 也就是说,loginAction bean的作用域是在HTTP请求级别。 您可以根据需要更改创建的实例的内部状态,因为根据此loginAcitonbean定义创建的其他bean实例并不会看到这些状态的改变;他们为各自的request拥有。 当reqeust完成处理,request作用的bean就被丢弃了。

当使用注解驱动组件或Java Config时,@RequestScope注解可以用于将一个组件分配给request范围。

@RequestScope
@Component
public class LoginAction {
	// ...
}

Session scope

考虑下面这种bean的xml配置定义:

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

Spring容器通过对单个HTTP会话的生命周期使用userPreferences bean定义来创建UserPreferences bean的新实例。 换句话说,userPreferences bean在HTTPSession级别有效地作用域。 和request-scopedbean相类似,可以改变bean实例的内部状态,不管bean创建了多少实例都可以,要知道,使用相同的userPreferences定义创建的其他的bean实例看不到这些状态的改变,因为他们都是为各自的HTTP Session服务的。 当HTTPSession最终被丢弃时,被限定为该特定HTTPSession的bean也被丢弃。

当使用注解驱动组件或Java Config时,@SessionScope注解可用于将一个组件分配给session范围。

@SessionScope
@Component
public class UserPreferences {
	// ...
}

Application scope

考虑下面这种bean定义:

<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>

Spring容器通过对整个web应用程序使用appPreferences bean定义来创建一个AppPreferences bean的新实例。 也就是说,appPreferences bean是在ServletContext级别定义的,存储为一个常规的ServletContext属性。 这有点类似于Spring单例bean,但在两个重要方面有所不同:1、他是每一个ServeltContext一个实例,而不是SpringApplicationContext范围。2、它是直接暴露的,作为ServletContext属性,因此可见。

当使用注解驱动组件或Java Config时,@ApplicationScope注解可用于将一个组件分配给application作用域。

@ApplicationScope
@Component
public class AppPreferences {
	// ...
}

不同级别作用域bean之间依赖

Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖关系)。 如果要将(例如)一个HTTP请求作用域bean注入到较长期作用域的另一个bean中,您可以选择注入一个AOP代理来代替该作用域bean。 也就是说,您需要注入一个代理对象,该对象暴露与作用域对象相同的公共接口,但也可以从相关作用域(例如HTTP请求)查找实际目标对象,并将方法调用委托给真实对象。

你也可以在定义为“singleton”的bean之间使用<aop:scoped-proxy/>,然后通过引用一个可序列化的中间代理,因此能够在反序列化时重新获得目标单例bean。
当对prototype scope的bean声明<aop:scoped-proxy/>时,共享代理上的每个方法调用都将导致创建一个新的目标实例,然后将该调用转发给该目标实例。
这里对<aop:scoped-proxy/> 不做详细解释 。
<?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:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- an HTTP Session-scoped bean exposed as a proxy -->
	<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
		<!-- instructs the container to proxy the surrounding bean -->
		<aop:scoped-proxy/>
	</bean>

	<!-- a singleton-scoped bean injected with a proxy to the above bean -->
	<bean id="userService" class="com.foo.SimpleUserService">
		<!-- a reference to the proxied userPreferences bean -->
		<property name="userPreferences" ref="userPreferences"/>
	</bean>
</beans>


自定义作用域 认为是不推荐的做法,并且,你不能覆盖内置的singletonprototype作用域,有兴趣的同学可以百度。

此文章参考 



阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页