默认情况下,Spring 应用上下文中所有 bean 都是作为以单例的形式创建的。
Q:Spring 定义了哪些作用域?
A:Spring 定义了多种作用域,可以基于这些作用域创建 bean,包括:
- 单例(Singleton):在整个应用中,只创建 bean 的一个实例;
- 原型(Prototype):每次注入或者通过 Spring 应用上下文获取的时候,都会创建一个新的 bean 实例;
- 会话(Session):在 Web 应用中,为每个会话创建一个 bean 实例;
- 请求(Rquest):在 Web 应用中,为每个请求创建一个 bean 实例。
Q:如何设置 bean 的作用域?
A:代码如下:
/**
* ①、使用组件扫描来发现和声明 bean 的情况下。
*/
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad {
}
/**
* ②、在 Java 配置中将 Notepad 声明为原型 bean
*/
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad(){
return new Notepad();
}
<!-- ③、使用 XML 来配置 bean 的情况下 -->
<bean id="notepad" class="com.myapp.Notepad" scope="prototype"/>
Q:如何使用会话和请求作用域?
就购物车 bean 来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。
@Component
public interface ShoppingCart {
/**
* value = "session" 这会告诉 Spring 为 Web 应用中的每个会话创建一个 ShoppingCart。
* proxyMode 的属性值,解决了将会话或请求作用域的 bean 注入到单例 bean 中所遇到的问题。
* 表明了代理要实现 ShoppingCart 接口,并将调用委托给实现 bean 。
*/
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.INTERFACES)
public ShoppingCart cart();
}
Q:将会话或请求作用域的 bean 注入到单例 bean 中会遇到什么问题?
A:如下列代码所示:
// 默认情况下,StoreService 是一个单例的 bean,会在 Spring 应用上下文加载的时候创建。
@Component
public class StoreService {
private ShoppingCart shoppingCart;
/**
* ①、当该 bean 创建的时候,Spring 会试图将 ShoppingCart bean 注入到 setShoppingCart()方法中。
* 但是 ShoppingCart bean 是会话作用域的,此时并不存在,直到用户进入系统,创建会话,才会存在。
*
* ②、系统中将会有多个 ShoppingCart 实例:每个用户一个。
* 我们希望当 StoreService 处理购物车时,所使用的 ShoppingCart 实例恰好是当前会话所对应的实例。
*
* Spring 并不会将实际的 ShoppingCart bean 注入到 StoreService 中,Spring 会注入一个到 ShoppingCart bean 的代理。
* 这个代理会暴露与 ShoppingCart 相同的方法,所以 StoreService 会认为它是一个购物车。
* 但是调用 ShoppingCart 的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的 ShoppingCart bean。
*/
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
}
然而,proxyMode 属性被设置成 ScopedProxyMode.INTERFACES,表明了这个代理要实现 ShoppingCart 接口,并将调用委托给实现 bean 。
如果 ShoppingCart 是接口而不是类的话,这是可以的(也是最为理想的代理模式)。但如果 ShoppingCart 是一个具体的类的话,Spring 就没有办法创建基于接口的代理了。此时,它必须使用 CGLib 来生成基于类的代理。所以,如果 bean 类型是具体的类的话,我们必须要将 proxyMode 属性设置为 ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。
请求作用域的 bean 也应该以作用域代理的方式进行注入。
Q:如何在 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">
<!-- 注意此处的 ShoppingCart 得是一个实体类,不能是一个接口 -->
<bean id="cart" class="com.myapp.ShoppingCart" scope="session">
<!--
与@Scope 注解的 proxyMode 属性功能相同,它会告诉 Spring 为 bean 创建一个作用域代理。
默认情况下,它会使用 CGLib 创建目标类的代理。
但是我们可以将 proxy-target-class 属性设置为 false,进行要求它生成基于接口的代理。
注意:为了使用<aop:scoped-proxy>元素,必须在 XML 配置中声明 Spring 的 aop 命名空间。
-->
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
</beans>
上一篇:高级装配 —— 如何处理自动装配的歧义性?
下一篇:高级装配 —— 如何在运行时计算要注入到 bean 属性中的值?