Spring学习笔记之Bean的作用域

自建博客地址:https://www.bytelife.net,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇

本文作者: Jeffrey
本文链接: https://www.bytelife.net/articles/8890.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!


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

在大多数情况下,单例bean是非常理想的方案。初始化和垃圾回收对象实例所带来的成本只留给一些小规模任务,在这些任务中,让对象保持无状态并且在应用中反复重用这些对象可能并不合理。

有时候你所使用的类可能是易变的,它们会保持一些状态,比如我们在Web购物商城中常见的购物车功能,不同的用户不可能同时使用同一个购物车实例,因此重用是不安全的。

(一)Spring中的作用域

Spring提供了多种作用域,可以基于这些作用域来创建bean,包括:

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

如果需要自定义bean的作用域,需要使用@Scope注解,他可以与@Component或@Bean组合使用:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Cake implements Dessert {
}

这里使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常亮设置为原型作用域。当然你也可以使用下面这种方式:

@Component
@Scope("prototype")
public class Cake implements Dessert {
}

但是尽可能使用ConfigurableBeanFactory.SCOPE_PROTOTYPE,这更不容易出错。

当然也可以在Java配置中将作用域设置为原型bean,例如:

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Dessert cake(){
        return new Cake();
    }

同样,也可以在XML中配置,应用元素的scope属性:

<bean id="cake" class="cn.javacodes.spring.beans.Cake" scope="prototype"></bean>

(二)使用会话和请求作用域

在Web应用中,我们经常需要操作两种作用域:会话和请求。

就像前面所说,在购物商城的购物车实例上,单例和原型作用域自然不能满足我们的需求,我们希望为每一个会话都创建一个购物车,那么这里会话作用域就是最完美的选择。

下面来简单模拟一下购物车的作用域场景:

    @Bean
    @Scope(
        value = WebApplicationContext.SCOPE_SESSION,
        proxyMode = ScopedProxyMode.INTERFACES)
    public ShoppingCart cart(){
       // ....
    }

这里,将value值设置成了WebApplicationContext中的SCOPTE_SESSION常量(值为session)。这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。对于每一个会话来说,这个bean实际上相当于是单例的。

这里需要注意,@Scope还有一个proxyMode属性,它被设置为ScopedProxyMode.INTERFACES。我们先不考虑这个属性,先来理解一下对Spring作用域的理解。

现在假设我们要将ShoppingCart bean注入到单例StoreService bean的Setter中,如下所示:

@Component
public class StoreService {
    
    private ShoppingCart shoppingCart ;
    
    @Autowired
    public  void setShoppingCart(ShoppingCart shoppingCart){
        this.shoppingCart = shoppingCart;
    }
}

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

另外,系统中将会存在多个ShoppingCart实例:每个用户一个。我们并不想让Spring注入到某个胡定的ShoppingCart实例到StoreService中。我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一个。

Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为他就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给作用域内真正的ShoppingCart bean。如下图所示:

现在我们来讨论一下proxyMode属性。我们将proxyMode属性设置为了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。

这里我们的ShoppingCart是接口而不是具体的类,这当然是可以的(也是最理想的代理模式)。但如果ShoppingCart是具体的实现类而不是接口的话,Spring就没办法创建基于接口的代理了。此时必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体的类的话,我们必须要将proxyMode设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。

请求作用域与会话作用域十分类似,也应该以作用域代理的方式进行注入,再次不做赘述。

(三)在XML中声明作用域代理

在XML中设置作用域代理需要使用Spring aop命名空间的一个元素:

    <bean id="cart" class="cn.javacodes.spring.beans.ShoppingCart" scope="session">
        <aop:scoped-proxy />
    </bean>

当然了,在使用aop命名空间之前一定要在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">
    ......
</beans>

注意:在使用Spring开发web项目时,需要在web.xml中加入如下内容(web2.4以上):

<web-app>
   ...
  <listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
  </listener>
   ...
</web-app>

web 2.4以下版本需要加入:

<web-app>
 ..
 <filter> 
    <filter-name>requestContextFilter</filter-name> 
    <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
 </filter> 
 <filter-mapping> 
    <filter-name>requestContextFilter</filter-name> 
    <url-pattern>/*</url-pattern>
 </filter-mapping>
   ...
</web-app>

另外,是与@Scope注解的proxyMode属性功能相同的Spring XML配置元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:

<bean id="cart" class="cn.javacodes.spring.beans.ShoppingCart" scope="session">
        <aop:scoped-proxy proxy-target-class="false"/>
    </bean>
posted @ 2021-02-25 01:17  JeffreyHu  阅读(68)  评论(0编辑  收藏  举报