• Bean的作用域


    Spring容器创建的Bean默认是单例的。Spring容器调用配置方法完成Bean的创建之后,Bean就缓存在Spring容器里。之后每次调用同一配置方法创建Bean,Spring容器只会返回缓存在Spring容器里的Bean,不再创建新的Bean。这意味着同一配置方法在同一Spring容器里无论被调用了多少次,都只会返回同一实例的Bean。因此,Spring容器创建的Bean默认是单例的。同时我们也应注意到,这里的单例与单例设计模式里的单例是有区别的,不能混为一谈。单例设计模式里的单例指的是类的实例由类的加载器方法创建,无论类的加载器方法被调用了多少次,都只会返回同一实例。

    在Web开发中,我们通常只需创建单例的Bean。因为诸如控制器之类的Bean是无状态的。无论哪个用户发来请求,都能使用同一控制器实例处理,根本就不需要再创建新的控制器实例。然而对于一些类,比如数据模型类,每个请求所产生或获取的数据都是不一样的。这意味着这样的类是有状态的。把这些有状态的类创建为单例的显然不妥。作为替代,我们通常选择创建这些类的域对象(Domain Object),通过new关键字在Bean的方法中创建这些类的实例。因此,在Web开发中,我们往往只需告诉Spring容器创建单例的Bean

    然而,在某些罕见的应用场景中,我们可能需要创建非单例的Bean。这意味着除了单例(Singleton)作用域之外,Spring容器还需支持创建具有其它作用域的Bean。具体如下:
    1.原型(Prototype):Spring容器每次调用配置方法创建Bean时都会重新创建Bean的实例,调用几次就创建几个实例。
    2.请求(Request):请求指的是Web请求,只有Web相关的Spring容器(比如XmlWebApplicationContext)才支持请求作用域。指定作用域为请求后,同一配置方法在同一Web请求里无论被调用了多少次,都只会创建一个Bean的实例。
    3.会话(Session):会话指的是Web会话,只有Web相关的Spring容器(比如XmlWebApplicationContext)才支持会话作用域。指定作用域为会话后,同一配置方法在同一Web会话里无论被调用了多少次,都只会创建一个Bean的实例。

    Bean的作用域可由@Scope注解配置。@Scope注解有个String类型的value属性。我们可把singleton(单例),prototype(原型),request(请求)或session(会话)这些字符串指给@Scope注解,告诉Spring容器创建具有相应作用域的Bean。如下所示:

    1 @Bean("music")
    2 @Scope(value="singleton")
    3 public Music produceMusic() {
    4     return new Music("Dream");
    5 }

    当然,@Scope注解除了可以加到配置方法之外,也能以同样的方式加到带有@Component注解的组件上。至于XML配置文件,则可使用XML的scope属性这样配置:

    1 <bean id="music" class="com.dream.Music" scope="singleton">
    2     <constructor-arg value="Dream" />
    3 </bean>

    于是,我们弄清楚了单例,原型,请求,会话这些作用域。却也开始感到困惑:“假如把原型作用域的Bean注入到单例作用域的Bean中,这时会怎么样?”

    毫无疑问,这是一个问题。Spring容器创建单例的Bean时就把原型的Bean注入进去了。之后,Spring容器每次用到单例的Bean时都是从Spring容器那里获取的,没再创建新的实例。这意味着原型的Bean只在注入的时候创建了一次,之后一直被单例的Bean引用着,无论单例的Bean用了多少次原型的Bean,原型的Bean始终是注入时的那个实例。如果我们希望单例的Bean每次用到原型的Bean时,原型的Bean都会返回一个新的实例,则需要做些额外的配置。而这配置,其中之一就是查找方法注入(Lookup Method Injection)

    简单来说,查找方法注入就是单例的Bean每次用到原型的Bean时,都会调用指定的方法从Spring容器那里获取原型的Bean。而从Spring容器那里获取原型的Bean时,Spring容器总会返回新的实例。如此一来,单例的Bean每次用到原型的Bean时,原型的Bean的实例就总是新的了。

    假如现有这样一个原型作用域的Bean:

     1 @Scope("prototype")
     2 @Component("music")
     3 public class Music {
     4     private String musicName = null;
     5 
     6     public Music(@Value("Dream") String musicName) {
     7         this.musicName = musicName;
     8     }
     9 
    10     // 省略getter, setter方法
    11 }

    我们希望把它注入到单例作用域的Bean里。这时可以这样定义单例作用域的Bean:

    1 @Component("player")
    2 public abstract class Player {
    3     @Lookup(value="music")
    4     protected abstract Music getPlayingMusic();
    5 
    6     // 省略其它代码
    7 }

    这是一个抽象类,定义了一个抽象方法,用于获取Music类型的Bean。特别引人注目的是,抽象方法上面带着一个神秘的@Lookup(value="music")注解。

    这是怎么回事呢?

    原来,Spring容器瞧见@Lookup注解之后就会生成一个代理类。代理类将重写带有@Lookup注解的抽象方法,使之具有这样的功能:从Spring容器那里查找@Lookup注解指定的Bean,并在找到之后进行返回。这样一来,单例的Bean每次用到原型的Bean时,都会调用代理方法从Spring容器那里获取原型的Bean。而从Spring容器那里获取的原型的Bean的实例总是新的,从而使单例的Bean每次用到原型的Bean时,用的都是新的实例。

    因此,@Lookup注解有个String类型的value属性,用于指定即将查找的Bean的ID。如果没有指定value属性,代理方法就会查找与代理方法的返回值的类型一样的Bean

    在我们的配置中,我们在抽象方法getPlayingMusic上添加了@Lookup(value="music")注解,告诉Spring容器生成代理类,使单例的Player每次用到的原型的Music都是新的实例。

    还有,XML也支持同样的配置。具体如下:

    1 <beans  /* 省略命名空间和XSD模式文件声明 */>
    2     <bean id="music" class="com.dream.Music" scope="prototype">
    3         <constructor-arg value="Dream" />
    4     </bean>
    5 
    6     <bean id="player" class="com.dream.Player">
    7         <lookup-method name="getPlayingMusic" bean="music" />
    8     </bean>
    9 </beans>

    这段代码使用XML配置了两个Bean:

    1.一个Bean是Music类型的,其作用域是原型的。
    2.一个Bean是Player类型的,其作用域没有指定,默认是单例的。

    特别需要留意的是,配置Player类型的Bean时用到了 <lookup-method name="getPlayingMusic" bean="music" /> 元素。Spring容器瞧见这个元素之后,就会生成一个代理类。代理类将重写<lookup>元素的name属性指定的方法,使之每次被调用的时候,都从Spring容器那里获取<lookup>元素的bean属性指定的Bean。如此一来,单例的Player每次用到原型的Music时,用的就都是新的实例了。

    于是,我们弄清楚了怎样把原型的Bean注入单例的Bean里,可这并不意味着我们可以停下探索的脚步。因为把请求作用域的Bean注入单例作用域的Bean里也有同样的问题。

    假如现有这样一个单例作用域的Bean:

    1 @Component
    2 public class Player {
    3     private Music playingMusic = null;
    4 
    5     @Autowired
    6     public Player(Music playingMusic) {
    7         this.playingMusic = playingMusic;
    8     }
    9 }

    我们希望注入Player构造函数的是一个请求作用域的Music类型的Bean。这时可以这样配置Music:

     1 @Component
     2 @Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS)
     3 public class Music {
     4     private String musicName = null;
     5 
     6     public String getMusicName() {
     7         return this.musicName;
     8     }
     9 
    10     @Value("Dream")
    11     public void setMusicName(String musicName) {
    12         this.musicName = musicName;
    13     }
    14 }

    Music类上带着@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS)注解,其value属性的值是 request ,表明该Bean的作用域是请求。同时我们也注意到了,@Scope注解还有一个proxyMode属性,其值是ScopedProxyMode.TARGET_CLASS

    这是怎么回事呢?

    原来,proxyMode属性是ScopedProxyMode枚举类型的,能够告诉Spring容器生成代理的方式。具体如下:
    1.NO:告诉Spring容器无需生成代理。
    2.TARGET_CLASS:告诉Spring容器基于类生成代理。
    3.INTERFACES:告诉Spring容器基于接口生成代理。
    4.DEFAULT:默认的代理方式。默认与NO一样,用于告诉Spring容器无需生成代理。也可通过配置,使之告诉Spring容器默认基于类或接口生成代理。

    由此可知,如果把ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES指给proxyMode属性,Spring容器就会生成代理类。Spring容器创建Bean的时候,只会创建代理类的Bean。因此,Spring容器把请求作用域的Bean注入到单例作用域的Bean时,注入的实际是代理类的Bean。如此一来,单例的Bean用到请求的Bean时,用的实际是代理类的Bean。代理类的Bean会先判断一下当前是不是在同一Web请求里:如果是,则返回缓存在Spring容器里的Bean;如果不是,则再创建一个新的实例进行返回。从而使单例的Bean用到请求的Bean时,不同的Web请求将会返回Bean的不同实例。

    Spring容器生成代理的方式有两种:一种是基于类生成代理;一种是基于接口生成代理。如果希望基于类生成代理,可把@Scope注解的proxyMode属性的值置为ScopedProxyMode.TARGET_CLASS。Music类上的@Scope注解的proxyMode属性的值就是ScopedProxyMode.TARGET_CLASS;如果希望基于接口生成代理,则必须让我们的类实现某个接口。因此,配置之前我们首先需要定义一个接口:

    1 public interface IMusic {
    2     public String getMusicName();
    3     public void setMusicName(String musicName);
    4 }

    之后让Music类实现IMusic接口,并把proxyMode属性的值置为ScopedProxyMode.INTERFACES:

     1 @Component
     2 @Scope(value="request", proxyMode = ScopedProxyMode.INTERFACES)
     3 public class Music implements IMusic {
     4     private String musicName = null;
     5 
     6     @Override
     7     public String getMusicName() {
     8         return this.musicName;
     9     }
    10 
    11     @Override
    12     @Value("Dream")
    13     public void setMusicName(String musicName) {
    14         this.musicName = musicName;
    15     }
    16 }

    最后把注入Player的Music改成IMusic接口:

     1 @Component
     2 public class Player {
     3     private IMusic playingMusic = null;
     4 
     5     public IMusic getPlayingMusic() {
     6         return this.playingMusic;
     7     }
     8 
     9     @Autowired
    10     public Player(IMusic playingMusic) {
    11         this.playingMusic = playingMusic;
    12     }
    13 }

    于是,基于接口生成代理的配置就完成了。当然,这里只讲了怎样进行请求作用域的注入。可实际上,会话作用域也有同样的问题。我们只需进行同样的配置就行了,不再赘叙。还有,如果想用XML进行同样的配置,则可提供一个XML配置文件配置如下:

     1 <beans /* 省略命名空间和XSD模式文件声明 */
     2        xmlns:aop="http://www.springframework.org/schema/aop"
     3        xsi:schemaLocation="
     4        /* 省略命名空间和XSD模式文件声明 */
     5        http://www.springframework.org/schema/aop
     6        http://www.springframework.org/schema/aop/spring-aop.xsd">
     7 
     8     <bean id="music" class="com.dream.Music" scope="request">
     9         <aop:scoped-proxy proxy-target-class="false" />
    10         <property name="musicName" value="Dream" />
    11     </bean>
    12 
    13     <bean id="player" class="com.dream.Player">
    14         <constructor-arg ref="music" />
    15     </bean>
    16 </beans>

    这段配置引入了spring-aop.xsd模式文件。这是一个用于配置面向切面编程的模式文件,我们将在介绍面向切面编程的时候另行介绍。现在只需知道这个模式文件定义了个<aop:scoped-proxy>元素,用于配置作用域代理。里面有个proxy-target-class属性,用于配置生成代理的方式:如果proxy-target-class属性的值是TRUE,则基于类生成代理;如果proxy-target-class属性的值是FALSE,则基于接口生成代理。proxy-target-class属性的值默认是TRUE

    至此,关于Bean的作用域的介绍也就告一段落了。下章,我们将会开始介绍事件的监听与发布。欢迎大家继续阅读,谢谢大家!

    返回目录    下载代码

  • 相关阅读:
    CString与 char *之间的转换
    linux命令行打开图片
    CentOS7 NFS配置
    vs2010 Visula C++ 把CString 转换为string 类型
    1>LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
    mount 命令
    Centos7.0 Vmware10.0.3 网络桥接配置
    Notepad++ 连接远程 FTP 进行文件编辑
    安装PHP的mongodb驱动速记
    CentOS上安装MongoDB速记
  • 原文地址:https://www.cnblogs.com/evanlin/p/16127119.html
Copyright © 2020-2023  润新知