1

我是春天的新手,正在做一些关于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 持有另一个原型豆

4

1 回答 1

0

介绍

考虑代码的以下部分:

public class SimpleBean {
   @Autowired
   SimplePrototypeBean prototypeBean;
}

您希望该prototypeBean领域指的是什么?

  • 它应该始终是 a 的同一个实例PrototypeBean吗?
  • 还是应该以某种方式包含原型逻辑?

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

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

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

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

    当范围为 时prototype,则:

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

也就是说,当您在 bean 上调用任何方法(包括该toString方法)时SimplePrototypeBean,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 Boot应用程序
  • RandomHolder是 IoC 容器中的原型 bean(与您声明getPrototypeBeanbean 的方式相同)
  • RandomHolder有一个我们希望是相同的字段。

当我们运行应用程序时,从getRandom方法返回的值可能会有所不同,这是一个示例输出:

random = 183673952
random = 1192775015

正如我们现在所知,therandomHolder指的是一个代理,当在其上调用方法时,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。

您的期望有点不准确,您创建的bean实例与调用任何方法的次数一样多prototype

有人可以向我解释为什么会这样

我希望,我的解释足够清楚

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

这里的问题不在原型范围内,而是在SimpleBean:它是 asingleton的范围内,所以你有相同的实例SimpleBean

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

只需在您的测试方法中添加一个断言:

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

Assertions.assertSame(simpleBean2, simpleBean1);

它不会失败。

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

于 2021-04-03T16:08:11.327 回答