一、单例作用域
单例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"/>
还可以通过注解定义,例如:
- import java.util.HashSet;
- import java.util.Set;
- import org.springframework.context.annotation.Scope;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- @Controller
- @RequestMapping(value = "/spring")
- @Scope("prototype")
- public class SpringDemo {
- private static Set<Object> set = new HashSet<>();
- @RequestMapping(value="test")
- public void test(){
- set.add(this);
- System.out.println(set.size());
- }
- }
二、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请求级别。 您可以根据需要更改创建的实例的内部状态,因为根据此loginAciton
bean定义创建的其他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-scoped
bean相类似,可以改变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/>
时,共享代理上的每个方法调用都将导致创建一个新的目标实例,然后将该调用转发给该目标实例。
<?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>
自定义作用域 认为是不推荐的做法,并且,你不能覆盖内置的singleton
和prototype
作用域,有兴趣的同学可以百度。