spring中的四种作用域
- 单例(singleton):在整个应用中,只创建一个bean的实例
- 原型(prototype):每次注入或者通过spring应用上下文获取的时候,都会创建一个新的bean实例
- 会话(session):每个会话创建一个bean的实例
- 请求(request):每个请求创建一个bean的实例
默认的作用域是单例,如果选择其他作用域,可以在bean声明的地方添加@Scope注解或者在<bena>
标签设置scope属性。
@Scope注解的value属性推荐使用ConfigurableBeanFactory类中的常量,如果是web应用则使用WebApplicationContext中定义的常量。
会话作用域
以电子商城的购物车为例,如果将其设置为单例,则所有用户都使用同一个购物车对象,如果将其设置为原型,则一个用户可能会使用多个购物车对象,请求也是如此,这些都是不合理的,应该为购物车使用会话作用域。
代码清单
@Bean
@Scope(
value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){···}
value = WebApplicationContext.SCOPE_SESSION
设置了购物车bean的作用域为session,表示为web应用中每个session创建一个购物车对象,这个购物车对象在session作用域中是单例的。
proxyMode = ScopedProxyMode.INTERFACES
设置了购物车bean的代理模式为基于接口的。解决了将session和request作用域的bean注入到一个singleton作用域bean的问题。
假设这里有一个singleton作用域的StoreService bean。
@Component
public class StoreService{
@Autowired
private ShoppingCart cart;
}
由于StoreService是singleton作用域的bean,所以会在spring应用上下文加载的时候创建,而ShoppingCart是session作用域的,只有当用户发起请求才会创建。且可能会有多个购物车实例被创建,不应该为StoreService注入一个固定的ShoppingCart。
此时为购物车设置的proxyMode属性就发挥了作用,spring并不会把某个ShoppingCart实例注入到StoreService,而是注入到一个ShoppingCart的代理,这个代理暴露和ShoppingCart接口中相同的方法,此时StoreService以为这个代理就是一个ShoppingCart,而当StoreService调用ShoppingCart方法的时候,代理将调用委托给session作用域中真正的ShoppingCart实例处理。
但是当bean不是一个接口,而是某个具体类的时候,ScopedProxyMode.INTERFACES不能创建基于具体类的代理,此时必须使用CGLib来生成基于具体类的代理,通过将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS来表明要以生成目标类扩展的方式创建代理。
request作用域的bean也同样应该以作用域代理的方式实现注入。
在XML中声明作用域代理
通过设置在<bean>
标签中的scope属性,可以指定bean的作用域。 但是设置代理模式,必须要用aop命名空间的元素<aop:scope-proxy />
。默认是使用CGLib创建目标类的代理,可以通过将proxy-target-class属性设置为false实现创建基于接口的代理<aop:scope-proxy proxy-target-class="false" />