Prototype bean returns 单例对象中的多个实例
Prototype bean returns multiple instances within singleton object
我是 spring 的新手,正在研究 proxyMode=ScopedProxyMode.TARGET_CLASS
。我写了一个简单的项目来测试这个单例和原型 bean。但是当我打印对象时它会打印一个新的原型 bean 实例。
public class SimplePrototypeBean {
private String name;
//getter setters.
}
单例 bean
public class SimpleBean {
@Autowired
SimplePrototypeBean prototypeBean;
public void setTextToPrototypeBean(String name) {
System.out.println("before set > " + prototypeBean);
prototypeBean.setText(name);
System.out.println("after set > " + prototypeBean);
}
public String getTextFromPrototypeBean() {
return prototypeBean.getText();
}
}
配置class.
@Configuration
public class AppConfig {
@Bean
SimpleBean getTheBean() {
return new SimpleBean();
}
@Bean
@Scope(value = "prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
public SimplePrototypeBean getPrototypeBean(){
return new SimplePrototypeBean();
}
}
单元测试
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class SimpleConfigTest {
@Test
public void simpleTestAppConfig() {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(AppConfig.class);
for (String beanName : ctx.getBeanDefinitionNames()) {
System.out.println("Bean " + beanName);
}
SimpleBean simpleBean1 = (SimpleBean) ctx.getBean("getTheBean");
SimpleBean simpleBean2 = (SimpleBean) ctx.getBean("getTheBean");
simpleBean1.setTextToPrototypeBean("XXXX");
simpleBean2.setTextToPrototypeBean("YYYY");
System.out.println(simpleBean1.getTextFromPrototypeBean());
System.out.println(simpleBean2.getTextFromPrototypeBean());
System.out.println(simpleBean2.getPrototypeBean());
}
}
输出
Bean org.springframework.context.annotation.internalAutowiredAnnotationProcessor
Bean org.springframework.context.annotation.internalCommonAnnotationProcessor
Bean org.springframework.context.event.internalEventListenerProcessor
Bean org.springframework.context.event.internalEventListenerFactory
Bean appConfig
Bean getTheBean
Bean scopedTarget.getPrototypeBean
Bean getPrototypeBean
springCertification.com.DTO.SimpleBean@762ef0ea
springCertification.com.DTO.SimpleBean@762ef0ea
before set > springCertification.com.DTO.SimplePrototypeBean@2f465398
after set > springCertification.com.DTO.SimplePrototypeBean@610f7aa
before set > springCertification.com.DTO.SimplePrototypeBean@6a03bcb1
after set > springCertification.com.DTO.SimplePrototypeBean@21b2e768
null
null
springCertification.com.DTO.SimplePrototypeBean@17a7f733
看到上面的输出总是显示新实例并且文本字段中的值为空。我 运行 只有一次这个应用程序。所以我期望在调用 simpleBean1 和 simpleBean2 时只会创建 2 个原型实例。有人可以向我解释为什么会发生这种情况以及如何将其修复为只有 2 个原型对象,其中 simpleBean1 持有一个 prototypeBean 而 simpleBean2 持有另一个 prototypeBean
简介
考虑您的代码的以下部分:
public class SimpleBean {
@Autowired
SimplePrototypeBean prototypeBean;
}
您认为 prototypeBean
字段指的是什么?
- 是否总是是
PrototypeBean
的同一个实例?
- 还是应该以某种方式包含原型逻辑?
Prototype 意味着,每次我们向 IoC 容器请求一个 bean 时,它都会 return 一个 new 实例
当使用默认配置时(未指定proxyMode
),该字段将对我们显示为相同原型实例
但是当您指定 TARGET_CLASS
或 INTERFACES
时,将不会注入 PrototypeBean
实例,而是它的 proxy,(参见 Scoped beans as dependencies):
That is, you need to inject a proxy object that exposes the same public interface as the scoped object but that can also retrieve the real target object from the relevant scope (such as an HTTP request) and delegate method calls onto the real object.
当作用域为prototype
时,则:
every method call on the shared proxy leads to the creation of a new target instance to which the call is then being forwarded.
也就是说,当您调用 any 方法时,包括 toString
方法,在 SimplePrototypeBean
bean 上,Spring 创建一个 new 下面 SimplePrototypeBean
的目标实例调用方法。
另一个mcve
你可以试试下面的MCVE来加深理解:
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RandomHolder {
private final int random = ThreadLocalRandom.current().nextInt();
public int getRandom() {
return random;
}
}
和 class 与 main
:
@SpringBootApplication
@AllArgsConstructor
public class SoApplication implements ApplicationRunner {
private final RandomHolder randomHolder;
public static void main(String[] args) {
SpringApplication.run(SoApplication.class, args);
}
@Override
public void run(ApplicationArguments args) {
System.out.println("random = " + randomHolder.getRandom());
System.out.println("random = " + randomHolder.getRandom());
}
}
- 这是一个Spring Boot应用程序
RandomHolder
是 IoC 容器中的原型 bean(与您声明 getPrototypeBean
bean 的方式相同)
RandomHolder
有一个我们希望相同的字段。
当我们 运行 应用程序 returned 来自 getRandom
方法的值可能不同,这里是示例输出:
random = 183673952
random = 1192775015
我们 现在 知道,randomHolder
指的是一个代理,当在其上调用方法时,RandomHolder
的新目标实例已创建并对其调用方法。
你可以想象代理是这样的:
public class RandomHolderProxy extends RandomHolder {
private final Supplier<RandomHolder> supplier = RandomHolder::new;
@Override
public int getRandom() {
return supplier.get().getRandom();
}
}
也就是说,它能够创建 RandomHolder
s 并在它们的新实例上调用方法。
没有proxyMode = ScopedProxyMode.TARGET_CLASS
当我们删除 proxyMode
参数时:
- 输出是一样的
random = 2628323
random = 2628323
- Spring 不会创建代理,但会在每次请求时创建 一个新实例
如果我们添加另一个组件:
@AllArgsConstructor
@Component
public class ApplicationRunner2 implements ApplicationRunner {
private final RandomHolder randomHolder;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner2: " + randomHolder.getRandom());
System.out.println("ApplicationRunner2: " + randomHolder.getRandom());
}
}
那么输出可能是:
random = -1884463062
random = -1884463062
ApplicationRunner2: 1972043512
ApplicationRunner2: 1972043512
So I'm expecting only 2 prototype instances will be created as I call simpleBean1 and simpleBean2.
您的期望有点不准确,您创建了 个实例 个 prototype
bean 个调用任何方法的次数在.
Can someone explain to me why this is happening
希望我的解释足够清楚
and how to fix it to have only 2 prototype objects where simpleBean1 holds one prototypeBean and simpleBean2 holds another prototypeBean
这里的问题不在原型作用域,而是在SimpleBean
的作用域:是一个singleton
,所以你有相同的SimpleBean
的实例,当你这样做时:
SimpleBean simpleBean1 = (SimpleBean) ctx.getBean("getTheBean");
只需在您的测试方法中添加一个断言:
SimpleBean simpleBean1 = (SimpleBean) ctx.getBean("getTheBean");
SimpleBean simpleBean2 = (SimpleBean) ctx.getBean("getTheBean");
Assertions.assertSame(simpleBean2, simpleBean1);
不会失败。
再次希望对您有所帮助。
我是 spring 的新手,正在研究 proxyMode=ScopedProxyMode.TARGET_CLASS
。我写了一个简单的项目来测试这个单例和原型 bean。但是当我打印对象时它会打印一个新的原型 bean 实例。
public class SimplePrototypeBean {
private String name;
//getter setters.
}
单例 bean
public class SimpleBean {
@Autowired
SimplePrototypeBean prototypeBean;
public void setTextToPrototypeBean(String name) {
System.out.println("before set > " + prototypeBean);
prototypeBean.setText(name);
System.out.println("after set > " + prototypeBean);
}
public String getTextFromPrototypeBean() {
return prototypeBean.getText();
}
}
配置class.
@Configuration
public class AppConfig {
@Bean
SimpleBean getTheBean() {
return new SimpleBean();
}
@Bean
@Scope(value = "prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
public SimplePrototypeBean getPrototypeBean(){
return new SimplePrototypeBean();
}
}
单元测试
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class SimpleConfigTest {
@Test
public void simpleTestAppConfig() {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(AppConfig.class);
for (String beanName : ctx.getBeanDefinitionNames()) {
System.out.println("Bean " + beanName);
}
SimpleBean simpleBean1 = (SimpleBean) ctx.getBean("getTheBean");
SimpleBean simpleBean2 = (SimpleBean) ctx.getBean("getTheBean");
simpleBean1.setTextToPrototypeBean("XXXX");
simpleBean2.setTextToPrototypeBean("YYYY");
System.out.println(simpleBean1.getTextFromPrototypeBean());
System.out.println(simpleBean2.getTextFromPrototypeBean());
System.out.println(simpleBean2.getPrototypeBean());
}
}
输出
Bean org.springframework.context.annotation.internalAutowiredAnnotationProcessor
Bean org.springframework.context.annotation.internalCommonAnnotationProcessor
Bean org.springframework.context.event.internalEventListenerProcessor
Bean org.springframework.context.event.internalEventListenerFactory
Bean appConfig
Bean getTheBean
Bean scopedTarget.getPrototypeBean
Bean getPrototypeBean
springCertification.com.DTO.SimpleBean@762ef0ea
springCertification.com.DTO.SimpleBean@762ef0ea
before set > springCertification.com.DTO.SimplePrototypeBean@2f465398
after set > springCertification.com.DTO.SimplePrototypeBean@610f7aa
before set > springCertification.com.DTO.SimplePrototypeBean@6a03bcb1
after set > springCertification.com.DTO.SimplePrototypeBean@21b2e768
null
null
springCertification.com.DTO.SimplePrototypeBean@17a7f733
看到上面的输出总是显示新实例并且文本字段中的值为空。我 运行 只有一次这个应用程序。所以我期望在调用 simpleBean1 和 simpleBean2 时只会创建 2 个原型实例。有人可以向我解释为什么会发生这种情况以及如何将其修复为只有 2 个原型对象,其中 simpleBean1 持有一个 prototypeBean 而 simpleBean2 持有另一个 prototypeBean
简介
考虑您的代码的以下部分:
public class SimpleBean {
@Autowired
SimplePrototypeBean prototypeBean;
}
您认为 prototypeBean
字段指的是什么?
- 是否总是是
PrototypeBean
的同一个实例? - 还是应该以某种方式包含原型逻辑?
Prototype 意味着,每次我们向 IoC 容器请求一个 bean 时,它都会 return 一个 new 实例
当使用默认配置时(未指定
proxyMode
),该字段将对我们显示为相同原型实例但是当您指定
TARGET_CLASS
或INTERFACES
时,将不会注入PrototypeBean
实例,而是它的 proxy,(参见 Scoped beans as dependencies):That is, you need to inject a proxy object that exposes the same public interface as the scoped object but that can also retrieve the real target object from the relevant scope (such as an HTTP request) and delegate method calls onto the real object.
当作用域为
prototype
时,则:every method call on the shared proxy leads to the creation of a new target instance to which the call is then being forwarded.
也就是说,当您调用 any 方法时,包括 toString
方法,在 SimplePrototypeBean
bean 上,Spring 创建一个 new 下面 SimplePrototypeBean
的目标实例调用方法。
另一个mcve
你可以试试下面的MCVE来加深理解:
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RandomHolder {
private final int random = ThreadLocalRandom.current().nextInt();
public int getRandom() {
return random;
}
}
和 class 与 main
:
@SpringBootApplication
@AllArgsConstructor
public class SoApplication implements ApplicationRunner {
private final RandomHolder randomHolder;
public static void main(String[] args) {
SpringApplication.run(SoApplication.class, args);
}
@Override
public void run(ApplicationArguments args) {
System.out.println("random = " + randomHolder.getRandom());
System.out.println("random = " + randomHolder.getRandom());
}
}
- 这是一个Spring Boot应用程序
RandomHolder
是 IoC 容器中的原型 bean(与您声明getPrototypeBean
bean 的方式相同)RandomHolder
有一个我们希望相同的字段。
当我们 运行 应用程序 returned 来自 getRandom
方法的值可能不同,这里是示例输出:
random = 183673952
random = 1192775015
我们 现在 知道,randomHolder
指的是一个代理,当在其上调用方法时,RandomHolder
的新目标实例已创建并对其调用方法。
你可以想象代理是这样的:
public class RandomHolderProxy extends RandomHolder {
private final Supplier<RandomHolder> supplier = RandomHolder::new;
@Override
public int getRandom() {
return supplier.get().getRandom();
}
}
也就是说,它能够创建 RandomHolder
s 并在它们的新实例上调用方法。
没有proxyMode = ScopedProxyMode.TARGET_CLASS
当我们删除 proxyMode
参数时:
- 输出是一样的
random = 2628323 random = 2628323
- Spring 不会创建代理,但会在每次请求时创建 一个新实例
如果我们添加另一个组件:
@AllArgsConstructor
@Component
public class ApplicationRunner2 implements ApplicationRunner {
private final RandomHolder randomHolder;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner2: " + randomHolder.getRandom());
System.out.println("ApplicationRunner2: " + randomHolder.getRandom());
}
}
那么输出可能是:
random = -1884463062
random = -1884463062
ApplicationRunner2: 1972043512
ApplicationRunner2: 1972043512
So I'm expecting only 2 prototype instances will be created as I call simpleBean1 and simpleBean2.
您的期望有点不准确,您创建了 个实例 个 prototype
bean 个调用任何方法的次数在.
Can someone explain to me why this is happening
希望我的解释足够清楚
and how to fix it to have only 2 prototype objects where simpleBean1 holds one prototypeBean and simpleBean2 holds another prototypeBean
这里的问题不在原型作用域,而是在SimpleBean
的作用域:是一个singleton
,所以你有相同的SimpleBean
的实例,当你这样做时:
SimpleBean simpleBean1 = (SimpleBean) ctx.getBean("getTheBean");
只需在您的测试方法中添加一个断言:
SimpleBean simpleBean1 = (SimpleBean) ctx.getBean("getTheBean");
SimpleBean simpleBean2 = (SimpleBean) ctx.getBean("getTheBean");
Assertions.assertSame(simpleBean2, simpleBean1);
不会失败。
再次希望对您有所帮助。