欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

我是如何理解Spring Bean的作用域

程序员文章站 2022-05-04 12:52:25
...

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

但有些时候,单例的并不能解决所有问题,

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

单例 Singleton :在整个应用中,只创建bean的一个实例

原型 Prototype: 每次注入或者通过Spring上下文获取的时候,都是一个新的bean实例

会话 Session :在Web应用中,为每个会话创建一个bean实例

请求 Request: 在web应用中,为每个请求创建一个bean实例

 

就像上面说的那样,Spring默认的是使用单例作用域, 如果要选择其他的作用域,要使用@Scope注解,,他可以和@Component注解或者@Bean一起使用

例如你使用组件扫描来发现和声明Bean 那么你可以在你bean的类上使用@Scope注解,将其声明为原型bean:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Noteped{
...
}

当然你也可以使用@Scope("prototype"), 但是使用SCOPE_PROTOTYPE常量会更加安全并且不容易出错。

 

如果你想在Java配置中将Notepad声明为原型bean那么可以使用@Scope和@Bean来指定所需的作用域:

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad noteped (){
    return new Notepad()
}

如果你使用XML来配置的话,也可以在<bean>元祖的scope属性来设置作用域

我是如何理解Spring Bean的作用域

不管你使用哪种方式来声明原型作用域,每次注入或从Spring应用上下文中检索该bean的时候,都会创建新的实例,这样所导致的结果就是每次操作都能得到自己的Notepad实例

下面说一下使用会话和请求作用域!!

我们首先举个例子,比如在电商的场景下,可能有一个bean代表的是购物车ShoppingCart,如果这个购物车是单例的,会怎么样的?那就是所有人都向这一个购物车添加商品,而如果购物车是个原型的呢?也不行,因为在某个地方往购物车中添加商品,在应用的另外一个地方就不能用了,就只能一个一个买了。

对于购物车来说,我们最好用的就是一个会话作用域。

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

这里,我们将value设置成了WebApplicationContext中的SCOPE_SESSION常量(他的值是session),这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart bean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关操作中,这个bean时间上相当于单例的。

要注意的是, @Scope同事还有个proxyMode属性,他被设置成了ScopedProxyMode.INTERFACES.这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇见的问题,在描述proxyMode属性之前,我们先看一下proxyMode所解决问题的场景

 

假设我们要将ShoppingCart bean注入到单例StoreService bean的Setter方法中

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

因为StoreService是一个单例的bean,会在Spring应用上下文加载的时候创建,当他创建的时候,Spring会试图将ShoppingCart bean注入到setShoppingCart()方法中来,但是ShoppingCart bean是会话作用域的。此时并不存在。那怎么办呢?

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

 

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

 

我是如何理解Spring Bean的作用域

如果你想在XML中声明作用域代理

那么需要使用Spring aop命名空间的一个新元素

<bean id="cart"
    class = "com.myapp.ShoppingCart"
    scope="session"
   <aop:scoped-prexy>
</bean>

默认的会使用CGlIB创建目标类的代理。但是我们也可以将proxy-target-class 属性设置为false,进而要求他生成基于接口的代理。