prototype bean在单例对象中返回多个示例

mspsb9vt  于 2021-07-13  发布在  Java
关注(0)|答案(1)|浏览(392)

我刚到Spring,正在研究 proxyMode=ScopedProxyMode.TARGET_CLASS . 我编写了一个简单的项目,用singleton和prototype bean来测试这一点。但是当我打印对象时,它会打印一个新的原型bean示例。

public class SimplePrototypeBean {
    private String name;

    //getter setters.       
}

单粒豆

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();
   }    
}

配置类。

@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

请参阅上面的输出始终显示新示例,并且文本字段中的值为null。我只运行一次这个应用程序。所以我希望在调用simplebean1和simplebean2时只创建2个原型示例。有人能给我解释一下为什么会发生这种情况,以及如何将其修复为只有两个原型对象,其中simplebean1持有一个prototypebean,simplebean2持有另一个prototypebean

vlju58qv

vlju58qv1#

简介

请考虑代码的以下部分:

public class SimpleBean {
   @Autowired
   SimplePrototypeBean prototypeBean;
}

你对未来有什么期待 prototypeBean 要引用的字段?
它应该始终是同一个示例吗 PrototypeBean ?
或者它应该以某种方式包含原型逻辑?
原型意味着,每次我们向ioc容器请求bean时,它都会返回一个新示例
使用默认配置时(不指定 proxyMode ),字段将显示为相同的原型示例
但当你指定 TARGET_CLASS 或者 INTERFACES 那就不是 PrototypeBean 示例将被注入,但其代理(请将作用域bean视为依赖项):
也就是说,您需要注入一个代理对象,该代理对象公开与作用域对象相同的公共接口,但也可以从相关作用域(如http请求)检索真实的目标对象,并将方法调用委托给真实对象。
当范围 prototype ,然后:
对共享代理的每个方法调用都会导致创建一个新的目标示例,然后将调用转发到该示例。
即调用任何方法,包括 toString 方法,在 SimplePrototypeBean bean,spring创建 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;
    }
}

我们的班级 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引导应用程序 RandomHolder 是ioc容器中的一个原型bean(与您声明 getPrototypeBean 豆子)
这个 RandomHolder 有一个我们期望相同的领域。
当我们运行应用程序时,从 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 并在新示例上调用方法。

不带proxymode=scopedproxymode.target\u类

当我们放下武器 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

所以我希望在调用simplebean1和simplebean2时只创建2个原型示例。
你的期望有点不准确,你有很多这样的例子 prototype 创建bean的次数与调用任何方法的次数相同。
有人能解释一下为什么会这样吗
我希望,我的解释足够清楚
以及如何将其修复为只有两个原型对象,其中simplebean1持有一个prototypebean,simplebean2持有另一个prototypebean
这里的问题不在原型范围内,而是在 SimpleBean :这是一个 singleton ,所以您有相同的 SimpleBean 当您这样做时:

SimpleBean simpleBean1 = (SimpleBean) ctx.getBean("getTheBean");

只需在测试方法中添加一个Assert:

SimpleBean simpleBean1 = (SimpleBean) ctx.getBean("getTheBean");
SimpleBean simpleBean2 = (SimpleBean) ctx.getBean("getTheBean");

Assertions.assertSame(simpleBean2, simpleBean1);

不会失败的。
再一次,希望这有帮助。

相关问题