Spring高级装配之Bean的作用域

我们知道在默认情况下,Spring应用上下文中所有Bean都是使用单例(singleton)模式创建的。在大多数情况下,单例Bean是很理想的方案。初始化和垃圾回收对象实例所带来的成本都是很低的。
但有时候,你可能会发现,所使用的类是易变的(mutable),他们会保持一些状态,因此重用是不安全的。在这种情况下,将class声明为单例的Bean就不是什么好主意了,因为对象会被污染,然后重用被污染的对象时会出现意想不到的问题。
不要担心,强大的Spring已经给出了解决方案。Spring定义了多种作用域,可以基于这些作用域创建bean,如下:

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

单例是默认作用域,但正如之前所述,对于易变的类型,这并不合适。其实Spring提供的@Scope注解,它可以与@Component或@Bean一起使用。现在我们就声明NotePad的作用域为原型:

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class NotePad {

}

这里使用的ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了原型作用域。
如果咱们使用的是显示方式声明的Bean,@Scope也可以与@Bean联合使用:

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public NotePad notePad(){
    return new NotePad();
}

同样如果你使用的是XML来配置bean的话,可以使用<bean>元素的scope属性来设置作用域:

<bean id="notePad" class="com.scope.service.NotePad" scope="prototype" />

1.使用会话和请求作用域

在实际企业级开发过程中,我们常用@Scope来定义Bean的作用域。如用户的购物车信息,如果将购物车类声明为单例(Singleton),那么每个用户都向同一个购物车中添加商品,这样势必会造成混乱;你也许会想到使用原型模式声明购物车,但这样同一用户在不同请求时,所获得的购物车信息是不同的,这也是一种混乱。
这时候将购物车作用域设置为会话(Session)就再适合不过了:

@Service
@Scope(value=WebApplicationContext.SCOPE_SESSION,
    proxyMode=ScopedProxyMode.INTERFACES)
public class Cart {

}

这里,我们将value的值设置为WebApplicationContext中的SCOPE_SESSION常量(他的值是session)。这样就会告诉Spring每个会话创建一个Cart。
细心的朋友可能已经发现了,@Scope的里面还有一个属性proxyMode,他被设置成了ScopeProxyMode.INTERFACES。这个属性解决了将会话或请求的bean注入到单例bean中所遇到的问题。在此之前我们先看看这个问题是什么。
假如我们将Cart bean注入到单例StoreService bean的setter方法中,如下所示:

@Service
public class StoreService {
    
    @Autowired
    private Cart cart;

}

因为SroreService是一个单例的bean,会在Spring应用上下文加载的时候创建。当他创建的时候,Spring会试图将Cartbean注入到它的属性中。但是cart bean是会话作用域的,此时并不存在。直到某个用户进入系统之后,才会创建Cart实例。
另外,系统中将会有多个Cart 实例:每个会话一个。我们不想让Spring注入某个固定的Cart,我们希望Spring注入的是本次会话的Cart bean。
其实,Spring这时并不会将实际的Cart bean注入到StoreService中,Spring 会注入一个Cart bean 的代理。这个代理会暴露与Cart相同的方法,所以认为他就是一个购物车。当StoreService调用Cart方法时,代理会对其进行赖解析,并调用委托给会话作用域内真正的Cart bean。
现在,我i们带着对这个作用域的理解,讨论一下proxyMode属性,proxyMode属性被设置成了ScopeProxyMode.INTERFACES,这表明这个代理要实现Cart接口,并调用委托给实现bean。
如果Cart是接口而不是类也是可以的(最理想的代理模式就是实现接口)。但如果Cart是一个具体类的话,他必须使用CGLib来生成基于类的代理,此时必须要将proxyMode属性设置为ScopeProxyMode.TAGER_CLASS,以此看来要以生成目标类扩展的方式创建代理。
请求(Request)作用域的装配机制也是这样的。

2.在XML中声明作用域代理

在xml中声明代理模式,要使用到<bean>的一个 属性scope;设置代理模式要使用Spring AOP命名空间中的一个新元素:

<bean id="card" class="com.scope.service.Cart" scope="session"
    <aop:scoped-proxy />
</bean>

<aop:scope-proxy />是与@scope注解的proxyMode属性功能相同Spring XML配置元素。他会告诉Spring为bean创建一个作用域代理。默认情况下他创建的是CGLib模式的代理。我们也可以指定他创建代理的模式:

<bean id="card" class="com.scope.service.Cart" scope="session"
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

为了使用<aop:scope-proxy>元素,我们必须在XML中配置声明Spring AOP的命名空间:

<?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:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd">
...
</beans>

至此,关于Spring bean作用域的配置我们就大致介绍完了,下节我们会介绍Spring更高级的配置--运行时注入。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 123,340评论 18 134
  • 文章作者:Tyan博客:noahsnail.com 3.5 Bean scopes When you create...
    SnailTyan阅读 1,376评论 0 1
  • 本章内容: Spring profile 条件化的bean声明 自动装配与歧义性 bean的作用域 Spring表...
    谢随安阅读 889评论 0 5
  • 这两天,大果和图图在内蒙古武川县的葵花地里玩得很开心,跳到瓜子湖里,在葵花地穿行,寻找双胞胎瓜子,开心极了。大果来...
    董大果阅读 64评论 0 0
  • 最近在家教我妈瑜伽,练习后睡眠质量有很大提升,也想教会姥姥姥爷,很多人一听,都会抱着怀疑的态度,觉得老年人不适合瑜...
    九月易安阅读 206评论 0 0