spring 中bean的作用域
先上配置文件和bean
spring_scope.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="xiaoming" class="beans.Singer" p:name="xiaoming" p:song="shihaofengqiu"> </bean> <bean id="song" class="beans.Song" scope="prototype" > <property name="context" value="shihaofengqiu"/> </bean> </beans>
Singer.java
package beans; public class Singer { private String name; private String song; public Singer(){ System.out.println("I'm a singer"); } public void setName(String name) { this.name = name; } public void setSong(String song) { this.song = song; } public void play(){ System.out.println("My name is:"+this.name); System.out.println("My song is:"+this.song); } }
Song.java
package beans; public class Song { private String context; public Song(){ System.out.println("this is a song"); } public void setContext(String context) { this.context = context; } }
1.scope(只介绍singleton和prototype)
当一个bean的作用域时singleton的时候,若ApplicationContext作为Spring Bean的工厂类,spring会在创建容器时就提前将bean的对象实例化,不管你是否使用它,它都都存在容器bean缓存中。即:
无论是在其他bean的创建过程的注入该bean,还是多次使用ApplicationContext的getbean方法返回对象实例,都是同一个对象实例,这与单例模式有些相似但不相同。
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring_scope.xml");
创建ApplicationContext后,控制台输出:
但若在<bean/>中将lazy-init置“true”,则容器不会将bean提前实例化。
<bean id="xiaoming" class="beans.Singer" p:name="xiaoming" p:song="shihaofengqiu" lazy-init="true"> </bean>
当一个bean的作用域是prototype时,表示一个bean对应多个对象实例,每次在注入另外一个bean或者调用ApplicationContext的getbean方法时,都会去创建一个新的对象,所以不同于singleton,在创建容器的时候不会预先对bean进行实例化。
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring_scope.xml"); Song song1=ctx.getBean("song",Song.class); Song song2=ctx.getBean("song",Song.class);
结果为,创建了两个Song的实例(执行了两次构造方法)
2.
这样如果作用域为singleton的bean注入作用域为singleton的bean,或者prototype的bean注入prototype的bean,prototype的bean注入singleton的bean,都于我们从逻辑上都与我们想要的时一样的,但是当singleton的bean注入prototype的bean就有了一个问题;
将singer中的song属性改为Song对象的引用,并相应的修改xml配置文件:
执行代码:
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring_scope.xml"); Singer xiaoming = ctx.getBean("xiaoming",Singer.class); // xiaoming.play(); Singer xiaoming2 = ctx.getBean("xiaoming",Singer.class);
结果为:(song的构造函数执行了一次)
问题就出来了,我们将Song设置为prototype,但是因为注入Song的Singer的作用域为singleton,使得Song在容器创建时,Singer实例后注入的Song就无法创建多个对象,即我们想要的是我们多次创建Singer时返回的是单例的Singer,Singer中的song属性却可以是不同的Song实例。
spring官方文档:“A problem arises when the bean lifecycles are different. Suppose singleton bean A needs to use non-singleton (prototype) bean B, perhaps on each method invocation on A. The container creates the singleton bean A only once, and thus only gets one opportunity to set the properties. The container cannot provide bean A with a new instance of bean B every time one is needed.”
解决这个问题可以有下面的方法:
<1>继承ApplicationContextAware,在ApplicationContextAware的子类中,可以注入ApplicationContext,这样就可以通过ApplicationContext在singleton的bean获得prototype作用域的bean
CommandManager.java
package aware; //import beans.Singer; import beans.Song; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class CommandManager implements ApplicationContextAware { private ApplicationContext applicationContext; public Song createSong(){ return this.applicationContext.getBean("song",Song.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } }
修改singer类变为singer_2.java
package beans; import aware.CommandManager; public class Singer_2{ private String name; private Song song; private CommandManager manager; public Singer_2(){ System.out.println("I'm a singer"); } public void setName(String name) { this.name = name; } public void setSong() { this.song = manager.createSong(); } public void setManager(CommandManager manager) { this.manager = manager; } public void play(){ System.out.println("My name is:"+this.name); System.out.println("My song is:"+this.song.getContext()); } }
xml配置文件中添加
<bean id="xiaoming_2"
class="beans.Singer_2"
p:name="xiaoming"
p:manager-ref="manager"
lazy-init="true"
>
</bean>
<bean id="manager"
class="aware.CommandManager"/>
运行:
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring_scope.xml"); Singer_2 xiaoming = ctx.getBean("xiaoming_2",Singer_2.class); // Singer xiaoming_2 = ctx.getBean("xiaoming",Singer.class); xiaoming.setSong();
xiaoming.setSong()
可以通过setSong方法,在容器中放回不同的Song现象实例。
结果:
这样就可以实现不同作用域的bean的注入
<2>通过<look-method/>标签和抽象类与方法在singleton中注入prorotype的bean,其中的abstract方法返回指定的bean
Singer_3.java
package beans; public abstract class Singer_3 { private String name; private Song song; public Singer_3(){ } public void setName(String name) { this.name = name; } public void setSong(){ this.song=createSong(); } public abstract Song createSong(); }
xml配置文件中添加:
<bean id="xiaoming_3" class="beans.Singer_3" p:name="xiaoming"> <lookup-method name="createSong" bean="song"/> </bean>
执行:
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring_scope.xml"); Singer_3 xiaoming = ctx.getBean("xiaoming_3",Singer_3.class); xiaoming.setSong(); xiaoming.setSong();
结果:
<3>通过注解省略<lookup-method/>的配置
@Lookup("song") public abstract Song createSong();
xml配置文件中添加
<context:annotation-config/>