如何在一个Spring Boot Java配置类(@Configuration)中创建多个bean(相同类型)?

时间:2019-07-20 06:29:45

标签: spring spring-boot

在我的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)

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

1 个答案:

答案 0 :(得分:0)

在带有注释的方法@PostConstruct中注册新bean的问题在于,Spring已经超过了Spring生命周期(more info on the Spring life cycle)中的特定时间点。有时,诸如@DependsOn(已经提到),@Order@Lazy之类的注释可能会有所帮助。但是,正如您提到的那样,您不希望在使用您的库的项目上强加(春季)实现细节,我编写了一个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需要一个int值,因此我将属性值2000重写为HttpComponentsClientHttpRequestFactory