别再找了, 这就是全网最全的 SpringBean 的作用域管理!
例如对于 Web 应用来说,Web 容器对于每个用户请求都创建一个单独的 Sevlet 线程来处理请求,引入 Spring 框架之后,每个 Action 都是单例的,那么对于 Spring 托管的单例 Service Bean,如何保证其安全呢?
Spring 的单例是基于 BeanFactory 也就是 Spring 容器的,单例 Bean 在此容器内只有一个
Java 的单例是基于 JVM,每个 JVM 内只有一个实例
=========================================================================
创建一个 bean 定义,其实质是用该 bean 定义对应的类来创建真正实例的“配方”。
把 bean 定义看成一个配方很有意义,它与 class 很类似,只根据一张“处方”就可以创建多个实例。
不仅可以控制注入到对象中的各种依赖和配置值,还可以控制该对象的作用域。
这样可以灵活选择所建对象的作用域,而不必在 Java Class 级定义作用域。
Spring Framework 支持五种作用域,分别阐述如下表。
五种作用域中,request、session 和 global session 三种作用域仅在基于 web 的应用中使用(不必关心你所采用的是什么 web 应用框架),只能用在基于 web 的 Spring ApplicationContext 环境。
========================================================================================
当一个 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{
}
3 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
注解的方式实现就不做演示了。
4 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"/>
5 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.UserPrefere
nces" scope="session"/>
==============================================================================
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"/>
Spring 框架支持 5 种作用域,有三种作用域是当开发者使用基于 web 的ApplicationContext
的时候才生效的
下面就是 Spring 内置支持的作用域
| 作用域 | 描述 |
| --- | --- |
| 单例(singleton) | (默认)每一个 Spring IoC 容器都拥有唯一的一个实例对象 |
| 原型(prototype) | 一个 Bean 定义可以创建任意多个实例对象 |
| 请求(request) | 一个 HTTP 请求会产生一个 Bean 对象,也就是说,每一个 HTTP 请求都有自己的 Bean 实例。只在基于 web 的 Spring ApplicationContext
中可用 |
| 会话(session) | 限定一个 Bean 的作用域为 HTTPsession
的生命周期。同样,只有基于 web 的 Spring ApplicationContext
才能使用 |
| 全局会话(global session) | 限定一个 Bean 的作用域为全局 HTTPSession
的生命周期。通常用于门户网站场景,同样,只有基于 web 的 Spring ApplicationContext
可用 |
| 应用(application) | 限定一个 Bean 的作用域为ServletContext
的生命周期。同样,只有基于 web 的 Spring ApplicationContext
可用 |
在 Spring 3.0 中,_线程作用域_是可用的,但不是默认注册的
==========================================================================
全局只有一个共享的实例,所有将单例 Bean 作为依赖的情况下,容器返回将是同一个实例
换言之,当开发者定义一个 Bean 的作用域为单例时,Spring IoC 容器只会根据 Bean 定义来创建该 Bean 的唯一实例。这些唯一的实例会缓存到容器中,后续针对单例 Bean 的请求和引用,都会从这个缓存中拿到这个唯一的实例
单例模式是将一个对象的作用域硬编码,一个 ClassLoader 只有唯一的一个实例
而 Spring 的单例作用域,是
基于每个容器
,每个 Bean 只有一个实例
这意味着,如果开发者根据一个类定义了一个 Bean 在单个的 Spring 容器中,那么 Spring 容器会根据 Bean 定义创建一个唯一的 Bean 实例。默认情况下,它们为每个给定的 org.springframework.context.ApplicationContext 实例存在唯一的一个 bean (有点别扭,也就是可以有多个 Spring 容器,每一个容器内存在唯一 bean 实例).这意味着如果你有两个或更多上下文,所有这些上下文都由同一 Java 的类加载器管理(因为在同一个 jvm 环境中),则可能会有多个给定 bean 的实例。唯一需要做到的是必须在每个上下文中定义此 bean.
所以你可以看到,bean 只是一个上下文的单例
你不应该将 Spring 的单例概念与设计模式中的的单例混淆
单例作用域是 Spring 的默认作用域,下面的例子是在基于 XML 的配置中配置单例模式的 Bean。
<bean id="accountService" class="com.sss.DefaultAccountService"/>
<bean id="accountService" class="com.sss.DefaultAccountService" scope="singleton"/>
==========================================================================
每次请求 Bean 实例时,返回的都是新实例的 Bean 对象
也就是说,每次注入到另外的 Bean 或者通过调用getBean()
来获得的 Bean 都将是全新的实例。
这是基于线程安全性:
有状态的 Bean 对象用 prototype 作用域
无状态的 Bean 对象用 singleton 作用域
下面的例子说明了 Spring 的原型作用域。DAO 通常不会配置为原型对象,因为典型的 DAO 是不会有任何的状态的。
下面的例子展示了 XML 中如何定义一个原型的 Bean:
<bean id="accountService"
class="com.javaedge.DefaultAccountService" scope="prototype"/>
与其他的作用域相比,Spring 不会完全管理原型 Bean 的生命周期:
Spring 容器只会初始化配置以及装载这些 Bean,传递给 Client。
但是之后就不会再去管原型 Bean 之后的动作了。
也就是说,初始化生命周期回调方法在所有作用域的 Bean 是都会调用的,但是销毁生命周期回调方法在原型 Bean 是不会调用的
所以,客户端代码必须注意清理原型 Bean 以及释放原型 Bean 所持有的一些资源。
可以通过使用自定义的bean post-processor
来让 Spring 释放掉原型 Bean 所持有的资源。
在某些方面来说,Spring 容器的角色就是取代了 Java 的new
操作符,所有的生命周期的控制需要由客户端来处理。
2-1 Singleton beans with prototype-bean dependencies
在原型 bean 中放置单例
如果注入的单例对象真的是一个单例的 bean(没有状态),这个真的没一点问题
想象一下,对于我们的购物车,我们需要注入产品服务。此服务只会检查添加到购物车的产品是否库存。由于服务没有状态,并且会基于在方法签名中所传递的对象进行验证,因此不存在风险
当使用单例 Bean 的时候,而该单例 Bean 的依赖是原型 Bean 时,需要注意的是依赖的解析都是在初始化的阶段
因此,如果将原型 Bean 注入到单例的 Bean 之中,只会请求一次原型 Bean,然后注入到单例 Bean 中。这个依赖的原型 Bean 仍然属于只有一个实例的。
然而,假设你需要单例 Bean 对原型的 Bean 的依赖
需要每次在运行时都请求一个新的实例,那么你就不能够将一个原型的 Bean 来注入到一个单例的 Bean 当中了,因为依赖注入只会进行_一次_
当 Spring 容器在实例化单例 Bean 的时候,就会解析以及注入它所需的依赖
如果实在需要每次都请求一个新的实例,可以通过 bean 工厂手动获取实例,也可以参考Dependencies中的方法注入部分。
##使用单例还是原型?
Singleton 适用于无状态的 bean,比如一个 service,DAO 或者 controller
他们都没有自己的状态(举个简单的例子,一个函数 sin(x),这个函数本身就是无状态的,所以我们现在喜欢的函数式编程也遵循这个理念)。而是根据传输的参数执行一些操作(作为 HTTP 请求参数)。
另一方面,我们可以通过状态 bean 管理一些状态。比如购物车 bean,假如它是一个单例,那么两个不同消费者购买的产品将被放置在同一个对象上。而如果其中一个消费者想要删除一个产品,另一个消费者就铁定不高兴。这也就是状态类对象应该是原型
#3. Request
Spring 容器会在每次用到loginAction
来处理每个 HTTP 请求的时候都会创建一个新的LoginAction
实例。也就是说,loginAction
Bean 的作用域是 HTTPRequest
级别的。 开发者可以随意改变实例的状态,因为其他通过loginAction
请求来创建的实例根本看不到开发者改变的实例状态,所有创建的 Bean 实例都是根据独立的请求来的。当请求处理完毕,这个 Bean 也会销毁。
每个请求初始化具有此作用域的 Bean 注解。这听起来像是原型作用域的描述,但它们有一些差异。
原型作用域在 Spring 的上下文中可用。而请求作用域仅适用于 Web 应用程序
原型 bean 根据需求进行初始化,而请求 bean 是在每个请求下构建的
需要说的是,request 作用域 bean 在其作用域内有且仅有一个实例。而你可以拥有一个或多个原型作用域 bean 实例
在以下代码中,你可以看到请求作用域 bean 的示例:
<bean id="shoppingCartRequest" class="com.sss.scope.ShoppingCartRequest" scope="request">
</bean>
当使用注解驱动组件或 Java Config 时,@RequestScope 注解可以用于将一个组件分配给 request 作用域。
@RequestScope
@Component
public class ShoppingCartRequest {
// ...
}
// request bean
// injection sample
@Controller
public class TestController {
@Autowired
private ShoppingCartRequest shoppingCartRequest;
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String test(HttpServletRequest request) {
LOGGER.debug("shoppingCartRequest is :"+shoppingCartRequest);
// ...
}
}
请注意定义内存在的<aop: scoped-proxy />标签。这代表着使用代理对象。所以实际上,TestController 持有的是代理对象的引用。我们所有的调用该对象都会转发到真正的 ShoppingCartRequest 对象。
有时我们需要使用 DispatcherServlet 的另一个 servlet 来处理请求。在这种情况下,我们必须确保 Spring 中所有请求都可用(否则可以抛出与下面类似的异常)。为此,我们需要在 web.xml 中定义一个监听器:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
调用/测试 URL 后,你应该能在日志中的发现以下信息:
评论