Spring bean的作用域

Spring bean的作用域

在默认情况下,应用上下文中的bean都是单例的形式创建的。 也就是不管给定的一个bean注入其他bean多少次,每次注入的都是同一个实例。

在大多数情况下,单例 bean是很理想的,可以反复重用。但是有时候我们所需要的bean是易变的,他会存储一些属于自己的状态,如果使用单例模式,每次操作同一个bean,会对bean造成污染,可能会出现预料不到的问题。

在Spring中定义了多种作用域,可以基于这些作用域创建 bean。

  • 单例(Singleton):在整个应用中,只创建bean的一个实例。
  • 原型(Prototype):每次注入或者通过Spring上下文获取的时候都会创建一个新的 bean实例。
  • 会话(Session):在Web应用中,为每个会话创建一个bean实例。
  • 请求(Request):在Web应用中,为每个请求创建一个bean实例。

配置bean的作用域

怎样指定bean的作用域,我们可以通过@Scope注解。如果是通过组件扫描方式发现和声明 bean,那么它可以和@Component注解搭配使用,将其声明为所配置作用域Bean

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//将其声明为原型bean
public class Demo { ... }
复制代码

若是通过java配置类声明bean,它可以与@Bean搭配使用

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Demo demo(){
    return new Demo();
}
复制代码

若是使用XML来配置bean,我们可以使用<bean>元素的scope属性,配置bean的作用域

<bean id="demo" class="com.abiao.Demo" scope="prototype" />
复制代码

我列举的这几个例子都是声明的prototype(原型)作用域,每次注入或从Spring应用上下文检索该bean时,都会创建新的实例,这样我们每次操作都会得到我们自己的bean实例。


会话作用域和请求作用域

以上我们介绍了,单例与原型作用域的声明。但是在有的场景我们使用前两种作用域,并不合适。例如,在Web应用中实现购物车。

若是使用单例作用域,我们每次往购物车添加商品都是往一个购物车添加,这并不合理。若是使用原型,我们每次添加商品都会创建一个购物车,下次获取购物车也不是同一个,也不合理。按照购物车bean来说,会话作用域是最适合的,为每个用户会话创建一个购物车。

声明会话作用域bean

指定会话作用域bean,我们也是使用@Scope注解。除了给@Scope注解配置value属性,我们还配置了 proxyMode 属性(代理模式),ScopedProxyMode.INTERFACES表示基于接口的动态代理模式(jdk)。配置 proxyMode(代理模式)可以帮我们解决会话作用域bean注入到单例bean所遇到的问题。

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION,//常量值为session
       proxyMode = ScopedProxyMode.INTERFACES)//基于接口实现的代理模式
public class Demo { ... }
复制代码

我们以商店与购物车为例,商店 StoreService(单例bean),购物车 ShoppingCart(会话bean)。在 StoreService的setShoppingCart()方法注入 ShoppingCart bean。

@Component
public class StoreService {

    /** 购物车 */
    private ShoppingCart shoppingCart;

    @Autowired
    public void setShoppingCart(ShoppingCart shoppingCart){
        this.shoppingCart = shoppingCart;
    }
    
}    
复制代码

因为 StoreService 是一个单例bean,会在Spring应用上下文加载时创建。在创建时,Spring会试图将 ShoppingCart 注入到 setShoppingCart()方法。但是 ShoppingCart 是会话作用域的bean,此时并不存在。只有当用户进入系统,创建会话之后,才会出现 ShoppingCart 实例。

那么问题来了,系统中会存在多个 ShoppingCart,每个用户进入系统,创建会话后,都会产生一个 ShoppingCart。但是 StoreService 是单例的,只能注入一个,我们并不想注入一个固定的 ShoppingCart 实例到 StoreService 中。我们所希望的是,当 StoreService 处理购物车时,注入的 ShoppingCart 恰好是当前会话所对应的那一个。

其实Spring并不会将 ShoppingCart bean 注入到 StoreService 中,而是注入一个 ShoppingCart bean 的代理。这个代理会暴露与 ShoppingCart 同样的方法,所以 StoreService 会将他看做一个购物车。当 StoreService 调用 ShoppingCart 的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的 ShoppingCart bean。

proxyMode属性设置

  • 若 ShoppingCart 是个接口,其实现类 @Scope的 proxyMode 属性设置为 ScopedProxyMode.INTERFACES ,表示这个代理要实现 ShoppingCart 接口,并将调用委托给实现bean。该代理模式也是最为理想的。

  • 若 ShoppingCart 是个类,Spring就没办法创建基于接口的代理,这时,他必须使用CGLib来生成基于类的代理。将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,Spring会生成基于目标类扩展类的方式创建代理。

以上我们了解了会话作用域可能产生这类问题,同样的请求作用域bean也会面临相同的装配问题。因此,请求作用域的bean也应该使用作用域代理的方式注入。

如图:作用域代理延迟注入请求和会话作用域的bean

未命名文件.png

XML中声明作用域代理

如果不使用@Scope注解声明作用域代理,我们还可以使用XML文件来声明。 在XML中作用域可通过scope属性直接声明,但是声明作用域代理需要使用Spring aop 命名空间的元素<aop:scoped-proxy />。 <aop:scoped-proxy />默认采用CGLib方式创建目标类的代理。

<?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/aop
       http://www.springframework.org/schema/beans/spring-aop.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="cart" 
          class="com.abiao._03_scope.ShoppingCart" 
          scope="session">
         <aop:scoped-proxy /> 
    </bean>

</beans>
复制代码

我们也可以通过设置 <aop:scoped-proxy /> 标签的 proxy-target-class 属性值为 false,让它基于接口创建作用域代理。

<bean id="cart"
      class="com.abiao._03_scope.ShoppingCart"
      scope="session">
     <aop:scoped-proxy proxy-target-class="false"/>
</bean>
复制代码

以上就是对Spring bean作用域的理解总结。

每天进步一点点,越慢才能越快!


作者:abiao你要努力啊
链接:https://juejin.cn/post/7005456614572425230
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值