Spring学习笔记之Bean的作用域
自建博客地址:https://www.bytelife.net,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇
本文作者: Jeffrey
本文链接: https://www.bytelife.net/articles/8890.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
在默认情况下,Spring的应用上下文中所有的bean都是单例的形式创建的。也就是说,不管给定的一个bean被注入到其它bean多少次,每次注入的都是同一个实例。
在大多数情况下,单例bean是非常理想的方案。初始化和垃圾回收对象实例所带来的成本只留给一些小规模任务,在这些任务中,让对象保持无状态并且在应用中反复重用这些对象可能并不合理。
有时候你所使用的类可能是易变的,它们会保持一些状态,比如我们在Web购物商城中常见的购物车功能,不同的用户不可能同时使用同一个购物车实例,因此重用是不安全的。
(一)Spring中的作用域
Spring提供了多种作用域,可以基于这些作用域来创建bean,包括:
- 单例(Singleton):在整个应用中,只创建bean的一个实例;
- 原型(Prototype):每次注入或者通过Spring上下文获取的时候,都会创建一个新的bean实例;
- 会话(Session):在Web应用中,为每个会话创建一个bean实例;
- 请求(Request):在Web应用中,为每次请求创建一个bean实例;
如果需要自定义bean的作用域,需要使用@Scope注解,他可以与@Component或@Bean组合使用:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Cake implements Dessert {
}
这里使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常亮设置为原型作用域。当然你也可以使用下面这种方式:
@Component
@Scope("prototype")
public class Cake implements Dessert {
}
但是尽可能使用ConfigurableBeanFactory.SCOPE_PROTOTYPE,这更不容易出错。
当然也可以在Java配置中将作用域设置为原型bean,例如:
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dessert cake(){
return new Cake();
}
同样,也可以在XML中配置,应用
<bean id="cake" class="cn.javacodes.spring.beans.Cake" scope="prototype"></bean>
(二)使用会话和请求作用域
在Web应用中,我们经常需要操作两种作用域:会话和请求。
就像前面所说,在购物商城的购物车实例上,单例和原型作用域自然不能满足我们的需求,我们希望为每一个会话都创建一个购物车,那么这里会话作用域就是最完美的选择。
下面来简单模拟一下购物车的作用域场景:
@Bean
@Scope(
value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){
// ....
}
这里,将value值设置成了WebApplicationContext中的SCOPTE_SESSION常量(值为session)。这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。对于每一个会话来说,这个bean实际上相当于是单例的。
这里需要注意,@Scope还有一个proxyMode属性,它被设置为ScopedProxyMode.INTERFACES。我们先不考虑这个属性,先来理解一下对Spring作用域的理解。
现在假设我们要将ShoppingCart bean注入到单例StoreService bean的Setter中,如下所示:
@Component
public class StoreService {
private ShoppingCart shoppingCart ;
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart){
this.shoppingCart = shoppingCart;
}
}
因为StoreService是一个单例bean,会在Spring上下文加载的时候创建,当它创建的时候,Spring会试图将ShoppingCart bean注入到setShoppingCart()方法中。但是ShoppingCart bean是会话作用域的,此时并不存在。直到某个用户进入系统,创建了会话以后,才会出现ShoppingCart实例。
另外,系统中将会存在多个ShoppingCart实例:每个用户一个。我们并不想让Spring注入到某个胡定的ShoppingCart实例到StoreService中。我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一个。
Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为他就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给作用域内真正的ShoppingCart bean。如下图所示:
现在我们来讨论一下proxyMode属性。我们将proxyMode属性设置为了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。
这里我们的ShoppingCart是接口而不是具体的类,这当然是可以的(也是最理想的代理模式)。但如果ShoppingCart是具体的实现类而不是接口的话,Spring就没办法创建基于接口的代理了。此时必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体的类的话,我们必须要将proxyMode设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。
请求作用域与会话作用域十分类似,也应该以作用域代理的方式进行注入,再次不做赘述。
(三)在XML中声明作用域代理
在XML中设置作用域代理需要使用Spring aop命名空间的一个元素:
<bean id="cart" class="cn.javacodes.spring.beans.ShoppingCart" scope="session">
<aop:scoped-proxy />
</bean>
当然了,在使用aop命名空间之前一定要在xml的顶部进行对命名空间进行声明:
<?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">
......
</beans>
注意:在使用Spring开发web项目时,需要在web.xml中加入如下内容(web2.4以上):
<web-app>
...
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
...
</web-app>
web 2.4以下版本需要加入:
<web-app>
..
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
另外,
<bean id="cart" class="cn.javacodes.spring.beans.ShoppingCart" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>