以单例注入多例为例
@Scope("prototype")
@Component
public class F1 {
}
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class F2 {
}
@Scope("prototype")
@Component
public class F3 {
}
@Scope("prototype")
@Component
public class F4 {
}
有一个单例对象 E
@Component
public class E {
private static final Logger log = LoggerFactory.getLogger(E.class);
private F f;
public E() {
log.info("E()");
}
@Autowired
public void setF(F f) {
this.f = f;
log.info("setF(F f) {}", f.getClass());
}
public F getF() {
return f;
}
}
测试
E e = context.getBean(E.class);
F f1 = e.getF();
F f2 = e.getF();
System.out.println(f1);
System.out.println(f2);
输出
com.dhy.demo.cycle.F@6622fc65
com.dhy.demo.cycle.F@6622fc65
发现它们是同一个对象,而不是期望的多例对象
对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F
@Component
public class E {
@Autowired
@Lazy
public void setF(F f) {
this.f = f;
log.info("setF(F f) {}", f.getClass());
}
// ...
}
注意
输出
E: setF(F f) class com.itheima.demo.cycle.F$$EnhancerBySpringCGLIB$$8b54f2bc
F: F()
com.dhy.demo.cycle.F@3a6f2de3
F: F()
com.dhy.demo.cycle.F@56303b57
从输出日志可以看到调用 setF 方法时,f 对象的类型是代理类型
4种解决方法
如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED,否则spring可能会反射调用jdk内部方法,造成访问权限异常被拒
@Component
public class E {
@Lazy
@Autowired
private F1 f1;
@Autowired
private F2 f2;
@Autowired
private ObjectFactory<F3> f3;
@Autowired
private ApplicationContext context;
public F1 getF1() {
return f1;
}
public F2 getF2() {
return f2;
}
public F3 getF3() {
return f3.getObject();
}
public F4 getF4() {
return context.getBean(F4.class);
}
}
对于f2的设置如下:
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class F2 {
}
本质也是生成一个代理对象,每次使用代理对象的任意方法时,由代理创建新的 F2 对象
剩余两种方法,都是从IOC容器中获取bean对象
出现上面scope失效的原因在于在依赖注入阶段就去获取了对应scope的值,然后后续所有使用到该scope类型的对象都是直接返回一开始注入好的值,而不是每次都向容器去获取一下。
因此想要解决这个办法,就必须要推迟socpe bean的获取,方法有上面说的四种