3.4.1 使用会话和请求作用域
在 Web 应用中,如果能够实例化在会话和请求范围内共享的 bean,那将是非常有价值的事情。例如,在典型的电子商务应用中,可能会有一个 bean 代表用户的购物车。如果购物车是单例的话,那么将会导致所有的用户都会向同一个购物车中添加商品。另一方面,如果购物车是原型作用域的,那么在应用中某一个地方往购物车中添加商品,在应用的另外一个地方可能就不可用了,因为在这里注入的是另外一个原型作用域的购物车。
就购物车 bean 来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用 @Scope 注解,它的使用方式与指定原型作用域是相同的:
这里,我们将 value 设置成了 WebApplicationContext 中的 SCOPE_SESSION 常量(它的值是 session)。这会告诉 Spring 为 Web 应用中的每个会话创建一个 ShoppingCart。这会创建多个 ShoppingCart bean 的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个 bean 实际上相当于单例的。
要注意的是,@Scope 同时还有一个 proxyMode 属性,它被设置成了 ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的 bean注入到单例 bean 中所遇到的问题。在描述 proxyMode 属性之前,我们先来看一下 proxyMode 所解决问题的场景。
假设我们要将 ShoppingCart bean 注入到单例 StoreService bean 的 Setter 方法中,如下所示:
因为 StoreService 是一个单例的 bean,会在 Spring 应用上下文加载的时候创建。当它创建的时候,Spring 会试图将 ShoppingCart bean 注入到 setShoppingCart() 方法中。但是 ShoppingCart bean 是会话作用域的,此时并不存在。直到某个用户进入系统,创建了会话之后,才会出现 ShoppingCart 实例。
另外,系统中将会有多个 ShoppingCart 实例:每个用户一个。我们并不想让 Spring 注入某个固定的 ShoppingCart 实例到 StoreService 中。我们希望的是当 StoreService 处理购物车功能时,它所使用的 ShoppingCart 实例恰好是当前会话所对应的那一个。
Spring 并不会将实际的 ShoppingCart bean 注入到 StoreService 中, Spring 会注入一个到 ShoppingCart bean 的代理,如图 3.1 所示。这个代理会暴露与 ShoppingCart 相同的方法,所以 StoreService 会认为它就是一个购物车。但是,当 StoreService 调用 ShoppingCart 的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的 ShoppingCart bean。
现在,我们带着对这个作用域的理解,讨论一下 proxyMode 属性。如配置所示,proxyMode 属性被设置成了 ScopedProxyMode.INTERFACES,这表明这个代理要实现 ShoppingCart 接口,并将调用委托给实现 bean。
如果 ShoppingCart 是接口而不是类的话,这是可以的(也是最为理想的代理模式)。但如果 ShoppingCart 是一个具体的类的话, Spring 就没有办法创建基于接口的代理了。此时,它必须使用 CGLib 来生成基于类的代理。所以,如果 bean 类型是具体类的话,我们必须要将 proxyMode 属性设置为 ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。
尽管我主要关注了会话作用域,但是请求作用域的 bean 会面临相同的装配问题。因此,请求作用域的 bean 应该也以作用域代理的方式进行注入。
Last updated