Spring中Bean的作用域
Spring Bean,就像JavaBeans中一样,有其使用的作用域。前面的文章中我们已经看到其中的两个:singleton 和prototype。这次来讲讲另外2个作用域(总共六个,参考本人Spring5文档翻译)。
本文将分为两部分。每个部分描述一个bean作用域。所以,在第一个,我们将探讨下request请求
作用域。第二个描述的是session
和全局session
(此在Spring5文档中已经消失)的作用域。每一部分将由理论和实践组成。需要注意的是:这些概念仅在Web Spring应用程序上下文中有效。
Spring中request请求作用域是什么?
每个请求初始化具有此作用域的Bean注解。这听起来像是原型作用域的描述,但它们有一些差异。第一个区别是原型作用域在Spring的上下文中可用。而请求作用域仅适用于Web应用程序。第二个是原型bean根据需求进行初始化,而请求bean是在每个请求下构建的。需要说的是,request作用域bean在其作用域内有且仅有一个实例。而你可以拥有一个或多个原型作用域bean实例。
在以下代码中,你可以看到请求作用域bean的示例:
1 | <bean id="shoppingCartRequest" class="com.migo.scope.ShoppingCartRequest" scope="request"> |
当使用注解驱动组件或Java Config时,@RequestScope
注解可以用于将一个组件分配给request
作用域。
1 |
|
1 | // request bean |
请注意<bean>
定义内存在的<aop: scoped-proxy />
标签。这代表着使用代理对象。所以实际上,TestController持有的是代理对象的引用。我们所有的调用该对象都会转发到真正的ShoppingCartRequest
对象。
有时我们需要使用DispatcherServlet
的另一个servlet
来处理请求。在这种情况下,我们必须确保Spring中所有请求都可用(否则可以抛出与下面类似的异常)。为此,我们需要在web.xml
中定义一个监听器:
1 | <listener> |
调用/测试URL后,你应该能在日志中的发现以下信息:
1 | shoppingCartRequest is :com.migo.scope.ShoppingCartRequest@2586b11c |
如果我们尝试在单例bean中使用request作用域的bean,则会在应用程序上下文加载阶段抛出一个BeanCreationException
:
1 | org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.migo.scope.ShoppingCartRequest com.migo.controller.TestController.shoppingCartRequest; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shoppingCartRequest': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. |
什么是Spring的Session作用域?
Session作用域的bean与request 作用域的bean没有太大的不同。它们也与纯Web应用程序上下文相关联。注解为Session作用域的Bean对于每个用户的会话仅创建一次。他们在会话结束时被破坏销毁掉。
由Session作用域限制的Bean可以被认为是面向Web的单例,因为给定环境(用户会话)仅存在一个实例。但请记住,你无法在Web应用程序上下文中使用它们(说个好理解点的,就是一个函数内部自定义变量所在的作用域,函数执行完就销毁了,没有什么逃逸,关于此处更深入的理解请看我的博文由域联系到的逃逸分析)。
想知道Session作用域bean在Spring中的操作,我们需要在配置文件中定义一个bean:
1 | <bean id="shoppingCartRequest" class="com.migo.scope.ShoppingCartSession" scope="session"> |
通过@Autowired
注解,查找这个bean的方式与request 作用域的bean相同。可以看到以下结果:
1 | shoppingCartSession is :com.migo.scope.ShoppingCartSession@3876e5d |
你可以看到,前5个打印输出代表相同的对象。最后一个是不同的。这是什么意思 ?简单来说,这代表 着一个新的用户使用自动注入的Session作用域访问该页面。我们可以通过打开两个浏览器的测试页(/test)来观察它。每个都将初始化一个新的会话Session,因此也就创建新的ShoppingCartSession bean
实例。
关于全局会话作用域(Global session scope)属于4.3x的范畴了,Spring5已经没有了,Spring5文档是去掉了因为4的存在所以还是说两句,它保留给portlet应用程序。 是不是一脸懵逼,so,来解释一下portlet是什么。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与servlet不同,每个portlet都有不同的会话。在这种情况下,Spring提供了一个名为global-session
的作用域。通过它,一个bean可以通过应用程序中的多个portlet共享。
1 | <bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/> |
至此,我们解释了请求和面向会话的作用域。第一个的作用是在每个request请求上创建新的bean。第二个在Session会话开始的时候初始化bean。