【问题标题】:Prototype bean returns multiple instances within singleton object原型 bean 在单例对象中返回多个实例
【发布时间】:2021-06-30 00:57:45
【问题描述】:

我是 Spring 新手,正在研究 proxyMode=ScopedProxyMode.TARGET_CLASS。我编写了一个简单的项目来使用单例和原型 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

见上面的输出总是显示新实例并且文本字段中的值为空。我只运行一次这个应用程序。所以我预计只有 2 个原型实例会被创建,因为我调用了 simpleBean1 和 simpleBean2。有人可以向我解释为什么会发生这种情况以及如何将其修复为只有 2 个原型对象,其中 simpleBean1 持有一个原型豆,而 simpleBean2 持有另一个原型豆

【问题讨论】:

    标签: java spring spring-boot spring5


    【解决方案1】:

    简介

    考虑以下代码部分:

    public class SimpleBean {
       @Autowired
       SimplePrototypeBean prototypeBean;
    }
    

    您希望prototypeBean 字段指的是什么?

    • 是否应该总是PrototypeBean的同一个实例?
    • 还是应该以某种方式包含原型逻辑?

    原型意味着,每次我们向 IoC 容器请求 bean 时,它都会返回一个实例

    • 当使用默认配置时(不指定proxyMode),该字段将显示为相同原型实例

    • 但是当您指定TARGET_CLASSINTERFACES 时,不会注入PrototypeBean 实例,而是注入它的代理,(参见Scoped beans as dependencies):

      也就是说,您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关作用域(例如 HTTP 请求)检索真实的目标对象,并将方法调用委托给真实的对象。

      当作用域为prototype时,则:

      共享代理上的每个方法调用都会导致创建一个目标实例,然后将调用转发到该目标实例。

    即当你在SimplePrototypeBean bean 上调用any 方法,包括toString 方法时,Spring 在下面创建SimplePrototypeBeannew 目标实例调用方法。


    另一个 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 Boot 应用程序
    • RandomHolder 是 IoC 容器中的原型 bean(与您声明 getPrototypeBean bean 的方式相同)
    • 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();
        }
    }
    

    也就是说,它能够创建RandomHolders 并在它们的新实例上调用方法。

    没有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
    

    所以我预计只会创建 2 个原型实例,因为我调用 simpleBean1 和 simpleBean2。

    你的期望有点不准确,你有 尽可能多的实例 prototype bean 创建你有任何方法被调用

    谁能解释一下为什么会这样

    希望我的解释足够清楚

    以及如何将其修复为只有 2 个原型对象,其中 simpleBean1 持有一个原型豆,而 simpleBean2 持有另一个原型豆

    这里的问题不在原型范围内,而是在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);
    

    它不会失败。

    再一次,希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 2014-09-19
      • 1970-01-01
      • 1970-01-01
      • 2011-06-14
      • 1970-01-01
      • 2015-01-14
      • 1970-01-01
      • 2014-03-01
      • 2016-07-21
      相关资源
      最近更新 更多