高级装配 —— 如何在不同的作用域中声明 bean?

默认情况下,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 属性中的值?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值