使用场景及问题
在Spring的诸多应用场景中bean都是单例形式,当一个单例bean需要和一个原型bean组合使用或者一个原型bean和另一个原型bean组合使用时,我们通常都是将依赖以属性的方式放到bean中来引用,然后以@Autowired来标记需要注入的属性。
但是这种方式在bean的生命周期不一样的时候将会出现问题,假设单例bean A需要一个原型bean B,我们在A中注入bean B,每次调用bean A中的方法时都会用到bean B,Spring Ioc容器只在容器初始化时执行一次,也就是bean A中的依赖bean B只有一次注入的机会,但是实际上bean B我们需要的是每次调用方法时都获取一个新的对象(原型),所以问题明显就是:我们需要bean B是一个原型bean,而事实上bean B的依赖只注入了一次变成了事实上的单例bean。
代码如下:
public interface Fruit {
void showFruitName();
}
@Component
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Apple implements Fruit {
@Override
public void showFruitName() {
System.out.println("fruit Name is apple");
}
}
@Component
public class FruitPlant {
@Autowired
Fruit fruit;
public void showFruit() {
System.out.println(fruit);
fruit.showFruitName();
}
}
// 配置类
@Configuration
@ComponentScan("com.modules.test")
public class TestConfig {}
//Main
public class ProcessorMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(TestConfig.class);
//从上下文中获取FruitPlant对象
FruitPlant fruitPlant = (FruitPlant) context.getBean("fruitPlant");
//第一次显示FruitPlant里的Fruit
fruitPlant.showFruit();
//第二次显示FruitPlant里的Fruit
fruitPlant.showFruit();
}
}
输出结果如下:
com.modules.test.Apple@616ac46a
fruit Name is apple
com.modules.test.Apple@616ac46a
fruit Name is apple
每次输出Apple的HashCode都是一样的,证明我们实际上并没有达到使用原型bean的目的。
解决方法
通过ApplicationContext上下文
引入ApplicationContext
,每次调用方法时用上下文的getBean()
方法去重新获取bean Fruit的实例。
修改代码如下:
@Component
public class FruitPlant {
Fruit fruit;
@Autowired
private ApplicationContext context;
public void showFruit() {
fruit = this.getFruit();
System.out.println(fruit);
fruit.showFruitName();
}
public Fruit getFruit() {
return this.context.getBean(Fruit.class);
}
}
通过@LookUp注解
修改代码:
@Component
public abstract class FruitPlant {
Fruit fruit;
public void showFruit() {
fruit = this.getFruit();
System.out.println(fruit);
fruit.showFruitName();
}
/**
* 此方法调用时会被FruitPlant的CGLIB代理对象的
* CglibSubclassingInstantiationStrategy-LookupOverrideMethodInterceptor拦截器的intercept()方法拦截
* 其中的createBean()流程,会根据这个bean是singleton还是prototype选择从缓存中取还是重新创建新的bean返回
*/
@Lookup
public abstract Fruit getFruit();
}
输出结果如下:
com.modules.test.Apple@1d483de4
fruit Name is apple
com.modules.test.Apple@4032d386
fruit Name is apple
xml形式的lookup-method
xml形式是@LookUp出来之前的,以配置的方式进行设置的方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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.xsd">
<!-- ====================replace-method属性注入==================== -->
<bean id="apple" class="com.modules.test.Apple" scope="prototype"/>
<bean id="fruitPlant" class="com.modules.test.FruitPlant">
<lookup-method name="getFruit" bean="apple" />
</bean>
</beans>
public class ProcessorMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("TestOverrideMethod.xml");
//从上下文中获取FruitPlant对象
FruitPlant fruitPlant = (FruitPlant) context.getBean("fruitPlant");
//第一次显示FruitPlant里的Fruit
fruitPlant.showFruit();
//第二次显示FruitPlant里的Fruit
fruitPlant.showFruit();
}
}
这种方式,lookup-menthod的信息是在AbstractBeanDefinition的MethodOverrides 属性中,通过@LookUp进行设置的,信息不是在MethodOverrides,而是在AnnotationMetadata中