先了解一下Bean的作用域
spring提供了6种作用域:singleton、prototype、request、session、applicaton、websocket
我们常用的singleton和prototype
singleton是默认值,singleton作用域的Bean只生成一个实例
prototype:通过容器的getBean()方法获取实例时,将会产生不同的实例
例如:
beans.xml
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="Son" class="configrelax.Son" scope="prototype"/>
<bean id="man" class="configrelax.man"/>
</beans>
configrelax.Son.java
package configrelax;
public class Son {
public int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
configrelax.man.java
package configrelax;
public class man {
public Son son;
public Son getSon() {
return son;
}
public void setSon(Son son) {
this.son = son;
}
}
maintest.java
package configrelax;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class maintest {
public static void main(String[] args)
{
ApplicationContext cn=new ClassPathXmlApplicationContext("beans.xml");
System.out.println(cn.getBean("Son",Son.class));
System.out.println(cn.getBean("Son",Son.class));
System.out.println(cn.getBean("man",man.class));
System.out.println(cn.getBean("man",man.class));
}
}
运行结果
configrelax.Son@56235b8e
configrelax.Son@3632be31
configrelax.man@5abca1e0
configrelax.man@5abca1e0
显然singleton作用域的实例只有一个,prototype则会创建多个实例
那问题就来了
首先我们知道bean实例化的时机是:
Spring什么时候实例化bean,首先要分2种情况
第一:如果你使用BeanFactory作为Spring Bean的工厂类,则所有的bean都是在第一次使用该 Bean的时候实例化
第二:如果你使用ApplicationContext作为Spring Bean的工厂类,则又分为以下几种情况:
(1):如果bean的scope是singleton的,并且lazy-init为false(默认是false,所以可以不用设置),则ApplicationContext启动的时候就实例化该Bean,并且将实例化的Bean放在一个map结构的缓存中,下次再使用该Bean的时候,直接从这个缓存中取
(2):如果bean的scope是singleton的,并且lazy-init为true,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
(3):如果bean的scope是prototype的,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
对于singleton作用域的Bean依赖prototype作用域的Bean时
对于singleton 作用域的Bean,如果没有强行取消其预初始化行为,系统会在创建spring容器时实例化所有的singletonBean,于此同时,所依赖的prototype Bean也一起被实例化,并将prototype Bean注入singleton Bean中。这就导致无论何时通过singleton Bean去访问prototype Bean,得到的永远是最初的prototype Bean,这就相当于prototype也变成了singleton,显然不合适
解决方法
1、放弃依赖注入。每次需要prototype Bean实例时,主动向容器请求新的实例(可用不可取)
2、利用方法注入
先看下lookup方法注入的执行过程再分析注意事项
为了使用lookup方法注入,大致分为两步
1、将调用者Bean的实现类定义为抽象类,并定义一个抽象方法获取被依赖的Bean,spring容器会自行重写容器中的Bean的抽象类的抽象方法
2、<bean…/>中添加<lookup-method…/>说明去实现哪个抽象方法,有两个属性
1)name:spring去实现的方法
2)bean:实现该方法的返回值
实现一个人每次带着不同的狗去打猎
configrelax.Person.java
package configrelax;
public interface Person
{
public void hunt();
}
congigrelax.Chinese.java
package configrelax;
public abstract class Chinese implements Person {
public abstract Dog getDog();
public void hunt()
{
System.out.println("猎人带着"+getDog()+"去打猎");
System.out.println(getDog().run());
}
}
configrelax.Dog.java
package configrelax;
public interface Dog {
public String run();
}
configrelax.gunDog.java
package configrelax;
public class gunDog implements Dog
{
private String name;
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public String run()
{
return "狗去追";
}
}
beans.xml
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="gunDog" class="configrelax.gunDog" scope="prototype"/>
<bean id="Chinese" class="configrelax.Chinese">
<lookup-method name="getDog" bean="gunDog"/>
</bean>
</beans>
maintest.java
package configrelax;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class maintest {
public static void main(String[] args)
{
ApplicationContext cn=new ClassPathXmlApplicationContext("beans.xml");
Person per1=cn.getBean("Chinese",Person.class);
Person per2=cn.getBean("Chinese",Person.class);
per1.hunt();
per2.hunt();
}
猎人带着configrelax.gunDog@6950e31去打猎
狗去追
猎人带着configrelax.gunDog@b7dd107去打猎
狗去追
spring容器自行负责重写getDog()
public Dog getDog()
{
ApplicationContext cn=new ClassPathXmlApplicationContext("beans.xml");
return cn.getBean("gunDog");
}
spring会采用运行时动态增强的方式实现<lookuo-method…/>指定的抽象方法
如果目标抽象类如上Chinese类实现过接口,spring会使用JDK动态代理实现该抽象类,并实现抽象方法
如果目标抽象类如上Chinese类没有实现过接口,spring会使用cglib实现该抽象类,并实现抽象方法,spring5.0已经有此类库