问题描述
在实际的开发过程中,我们有可能会遇到这样一个场景:多例对象 A 需要作为属性注入给单例对象 B,但是我们每次获取 B 的时候,发现注入的 A 每次都是同一个,并不是多例的。也就是下面这个代码:
@Component
@Scope("prototype")
public class A {
}
@Component
public class B {
@Autowired
private A a;
@Override
public String toString() {
return "B{" +
"a=" + a +
'}';
}
}
@Test
public void test1() {
B b1 = context.getBean("b", B.class);
B b2 = context.getBean("b", B.class);
System.out.println(b1);
System.out.println(b2);
}
输入内容如下:
从结果可以看出,B 注入的 A 是同一个对象,而不是期望的多例对象。
问题原因
基于前文的内容:《揭秘Spring生命周期:Bean的创建过程超详细解析》,Spring 在第一次创建单例对象 B 的时候,会进行属性的填充,此时会调用工厂获取 A 的多例对象。创建好 B 之后就将 B 缓存起来,下次获取直接从缓存中获取 B,拿到后直接返回,不会再次去工厂中获取 A 对象了,所以这就是作用域失效的根本原因。
问题解决
要想解决此问题,我们需要延迟获取多例对象。这里提供四种方案可供参考:
方案一:作用域指定代理
@Component
@Scope(value = "prototype" , proxyMode = ScopedProxyMode.TARGET_CLASS)
public class A {
}
@Component
public class B {
@Autowired
private A a;
@Override
public String toString() {
return "B{" +
"a=" + a +
'}';
}
}
这样表示每次获取 A 都是获取的代理的对象,只要在代理上调用方法,每次获取都会根据作用域重新获取。
方案二:指定懒加载
@Component
@Scope("prototype")
public class A {
}
@Component
public class B {
@Lazy
@Autowired
private A a;
@Override
public String toString() {
return "B{" +
"a=" + a +
'}';
}
}
使用懒加载一样会创建代理的对象,只要在代理上调用方法,每次获取都会根据作用域重新获取。
方案三:使用对象工厂 ObjectFactory
@Component
@Scope("prototype")
public class A {
}
@Component
public class B {
@Autowired
private ObjectFactory<A> a;
@Override
public String toString() {
return "B{" +
"a=" + a.getObject() +
'}';
}
}
方案四:从容器获取
@Component
@Scope("prototype")
public class A {
}
@Component
public class B {
@Autowired
private ApplicationContext context;
@Override
public String toString() {
return "B{" +
"a=" + context.getBean("a") +
'}';
}
}
@Component
@Scope("prototype")
public class A {
}
@Component
public class B implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public String toString() {
return "B{" +
"a=" + beanFactory.getBean("a") +
'}';
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}