【问题标题】:How to create multiple beans (same type) in one Spring Boot java config class (@Configuration)?如何在一个 Spring Boot java config 类(@Configuration)中创建多个 bean(相同类型)?
【发布时间】:2019-07-20 06:29:45
【问题描述】:

在我的 yaml 文件中,我的配置值如下:

    myapp:
      rest-clients:
        rest-templates:
        - id: myService1
          username: chris
          password: li
          base-url: http://localhost:3000/service1
          read-timeout: 2s
          connect-timeout: 1s
        - id: myService2
          username: chris
          password: li
          base-url: http://localhost:3000/service1
          read-timeout: 2s
          connect-timeout: 1s

我希望 Spring Boot 2 应用为每个配置项注册一个 RestTemplate。

我的配置是bean如下:

@Configuration
@AllArgsConstructor
public class MyAppRestClientsConfiguration {

    private MyAppRestClientsProperties properties;

    private GenericApplicationContext applicationContext;

    private RestTemplateBuilder restTemplateBuilder;

    @PostConstruct
    public void init() {
        properties.getRestTemplates().forEach(this::registerRestTemplate);
    }

    private void registerRestTemplate(MyAppRestTemplateConfig config) {
       // do some work
       applicationContext.registerBean(config.getId(), RestTemplate.class, () -> restTemplate) 
    }
}

问题是当我通过@Autowire 注入我注册的RestTemplate 时,这个配置bean 还没有完成初始化。所以没有可以注入 RestTemplate bean。

    @Autowired
    @Qualifier("myService1")
    private RestTemplate client1;
The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)
    - @org.springframework.beans.factory.annotation.Qualifier(value=myService1)

有没有正确的方法来实现这个要求?

【问题讨论】:

  • 你在哪里注入你的休息模板。
  • 你应该试试@DependsOn注解。如需更清晰,请参阅docs.spring.io/spring-framework/docs/current/javadoc-api/org/…
  • @Thomas 我在一些控制器中注入了我的 resttemplate。
  • @P singh 我正在为我的团队实现一个库。所以不知道其他开发人员在哪里注入了 resttmplate。我希望他们拥有 conf yaml,然后可以将 resttempalte 注入任何 bean,就像 spring boot 为我们所做的那样。我不想强迫他们在需要休息模板时使用 @DependsOn 注释

标签: spring spring-boot


【解决方案1】:

@PostConstruct 注释方法中注册新bean 的问题是Spring 已经过了Spring 生命周期中的那个特定点(more info on the Spring life cycle)。有时,@DependsOn(已经提到)、@Order@Lazy 等注释可能会有所帮助。但是,正如您提到的,您不希望在使用您的库的项目上强制执行(spring)实现细节,我编写了一个BeanFactoryPostProcessor,它注册了一个RestTemplate bean:

@Component
public class DemoBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) {
        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setBeanClass(RestTemplate.class);

        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setReadTimeout(Integer.valueOf(configurableListableBeanFactory.resolveEmbeddedValue("${rest-templates[0].read-timeout}")));
        factory.setConnectTimeout(Integer.valueOf(configurableListableBeanFactory.resolveEmbeddedValue("${rest-templates[0].connect-timeout}")));
        // etc

        ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
        constructorArgumentValues.addGenericArgumentValue(factory);

        genericBeanDefinition.setConstructorArgumentValues(constructorArgumentValues);

        String beanId = configurableListableBeanFactory.resolveEmbeddedValue("${rest-templates[0].id}");
        ((DefaultListableBeanFactory) configurableListableBeanFactory).registerBeanDefinition(beanId, genericBeanDefinition);
    }
}

application.yml:

rest-templates:
  - id: myService1
    username: chris
    password: li
    base-url: http://localhost:3000/service1
    read-timeout: 2000
    connect-timeout: 1000
  - id: myService2
    username: chris
    password: li
    base-url: http://localhost:3000/service1
    read-timeout: 2000
    connect-timeout: 1000

伴随测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

    @Autowired
    @Qualifier("myService1")
    private RestTemplate restTemplate;

    @Test
    public void demoBeanFactoryPostProcessor_shouldRegisterBean() {
        String stackOverflow =
                restTemplate.getForObject("https://stackoverflow.com/questions/57122343/how-to-create-multiple-beans-same-type-in-one-spring-boot-java-config-class", String.class);

        Assertions.assertThat(stackOverflow).contains("How to create multiple beans (same type) in one Spring Boot java config class (@Configuration)?");
    }

}

由于在完全设置应用程序上下文之前调用了BeanFactoryPostProcessor,因此我必须找到一种不同的方法来检索应用程序属性。我使用方法ConfigurableListableBeanFactory#resolveEmbeddedValue 来检索占位符值,而不是通过@Value 注释或environment#getProperty 注入它们。此外,我将属性值 2s 重写为 2000,因为 HttpComponentsClientHttpRequestFactory 需要一个 int 值。

【讨论】:

  • 感谢您的回答。这个解决方案的问题是我有一个MyAppRestClientsProperties,它由@ConfigurationProperties(prefix = "myapp.rest-clients") 注释。我还是想使用 Spring Boot 提供的 ConfigurationProperties 功能。
  • 你不能。将环境中的值绑定到由@ConfigurationProperties 注释的bean 由this BeanPostProcesor 完成。 Bean 后处理器在 bean factory 后处理器之后工作。
猜你喜欢
  • 2017-09-02
  • 2014-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-08
  • 2021-01-18
  • 2017-01-13
  • 1970-01-01
相关资源
最近更新 更多