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的作用域的介绍也就告一段落了。下章,我们将会开始介绍事件的监听与发布。欢迎大家继续阅读,谢谢大家!
返回目录 下载代码
来源:https://www.cnblogs.com/evanlin/p/16127119.html
本站部分图文来源于网络,如有侵权请联系删除。