Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean 多少次,每次所注入的都是同一个实例。
目录
proxyMode = ScopedProxyMode.INTERFACES
因为类很容易改变属性, 重用是不安全的,在这种情况下,单例就不能用了。
Spring 多种作用域
- 单例:在整个应用中,只创建bean 的一次实例;
- 原型:每次注入或者通过 Spring 上下面获取的时候,都会创建一个新的 bean 实例;
- 会话:在 web 应用中,为每个会话创建一个 bean 实例;
- 请求:在 web 应用中,为每个请求创建一个 bean 实例。
单例是默认实例,如果用于宜变的类的话,就不合适了。如果选择其他的作用域,需要使用@Scope 注解,可以与@Component 和@Bean 一起使用。
比如:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 可以用来组件扫描来 发现和声明 bean 等同于:@Scope("prototype")
使用会话和请求作用域
实例化在会话和请求范围内共享的bean。
例如,在典型的电子商务应用中,可能会有一个 bean代表用户的购物车。如果购物车是单例的话,那么将会导致所有的用户都会向同一个购物车中添加商品。另一方面,如果购物车是原型作用域的,那么在应用中某一个地方往购物车中添加商品,在应用的另外一个地方可能就不可用了,因为在这里注入的是另外一个原型作用域的购物车。
@Component
@Scope(
value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.INTERFACES
)
public ShoppingCart cart() {
return null;
}
这样会告诉 Spring 为 Web 应用中的每个会话创建了一个 ShoppingCart。这样虽然会创建多个实例,但是对于一个会话只有一个实例,也就意味在当前会话相关的操作中,这个 bean 相当于单例的。
proxyMode = ScopedProxyMode.INTERFACES
proxyMode所解决问题的场景:
/**
* 假设我们要将ShoppingCart bean注入到单例StoreService bean的Setter方法中
*
* @author imenger
* @date 2021/4/16 5:27 下午
*/
@Component
public class StoreService {
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart){
this.shoppingCart = shoppingCart;
}
}
因为StoreService是一个单例的 bean,会在 Spring 应用上下文加载的时候创建,当创建的时候,Spring 会试图将 ShoppingCart bean 注入到 setShoppingCart 方法中。但是 ShoppingCart bean 是会话作用域的 bean,此时是不存在的,当用户进入系统的时候,创建了会话。此时才会出现 ShoppingCart 这个实例。
Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理,如图3.1所示。这个 代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调 用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。
/**
* Create a JDK dynamic proxy implementing <i>all</i> interfaces exposed by
* the class of the target object.
*/
INTERFACES,
proxyMode属性被设置成 了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。
上文单单叙述了 会话作用域, 与其类似的 请求作用域 最好 以作用域代理的方式来进行注入。
如果,ShoppingCart 不是专门的作用域类,是 一个 具体的类的话。Spring 就没有办法创建基于接口的代理了,此时,必须使用 CGLib 来生成基于类的代理。将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来表明 要以生成目标类扩展的方式 创建代理。
在XML中声明作用域代理
同样除了 javabean 来声明作用域之外的话,xml 也是另外的一种方式,用 xml 来声明。就不需要@Scope 注解以及 proxyMode 属性了。
<bean>元素的scope属性能够设置bean的作用域:
如何指定作用域的代理模式
<bean id="cart" class="com.myapp.ShoppingCart" scope="session" >
<aop:scoped-proxy/>
</bean>
<aop:scoped-proxy>是与@Scope注解的proxyMode属性功能相同的Spring XML配置元素。
它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理。
<bean id="cart" class="com.myapp.ShoppingCart" scope="session" >
<aop:scoped-proxy proxy-target-class="false"/>
</bean>