51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

根据属性(Properties)动态注册 Spring Bean

1、概览 {#1概览}

本文将带你了解如何根据自定义属性动态注册 Bean。

主要是学习 BeanDefinitionRegistryPostProcessor 接口,以及如何使用它将 Bean 添加到 Application Context 中。

2、设置 {#2设置}

创建一个简单的 Spring Boot 应用。

首先,定义一个要动态注册的 Bean。然后,提供一个属性来决定如何注册 Bean。最后,定义一个配置类,它将根据自定义属性注册 Bean。

2.1、依赖 {#21依赖}

添加 Maven 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>3.2.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>3.2.3</version>
    <scope>test</scope>
</dependency>

添加 spring-boot-starterspring-boot-starter-test 依赖。

2.2、Bean 类 {#22bean-类}

接下来,根据自定义 application properties 定义要注册的 ApiClient

public class ApiClient {
    private String name;
    private String url;
    private String key;
    // Getter、Setter 和构造函数
public String getConnectionProperties() {
    return &quot;Connecting to &quot; + name + &quot; at &quot; + url;     
}

}

假设我们希望根据提供的属性使用这个 Bean 连接到不同的 API。我们不想为每个 API 创建类定义,而是希望动态地为每个 API 定义属性并注册该 Bean。

这里不应该用 @Component@Service 来注解 ApiClient 类,避免组件扫描直接将其注册为 bean。

2.3、Properties {#23properties}

application.yml 文件中添加一个属性来确定应该为哪些 API 注册该 Bean

api:
  clients:
    - name: example  
      url: https://api.example.com
      key: 12345
    - name: anotherexample
      url: https://api.anotherexample.com
      key: 67890

如上,定义了两个客户端及其各自的属性。我们将在注册 Bean 时使用这些属性。

3、动态注册 Bean {#3动态注册-bean}

Spring 提供了一种通过 BeanDefinitionRegistryPostProcessor 接口动态注册 Bean 的方式。这个接口允许我们在注解的 Bean 定义注册后添加或修改 Bean 定义。由于它在 Bean 实例化之前发生,因此这些 Bean 在 application context 完全初始化之前就被注册了。

3.1、BeanDefinitionRegistryPostProcessor {#31beandefinitionregistrypostprocessor}

定义一个配置类,它将 根据自定义属性注册 ApiClient Bean:

public class ApiClientConfiguration implements BeanDefinitionRegistryPostProcessor {
    private static final String API_CLIENT_BEAN_NAME = "apiClient_";
    List<ApiClient> clients;
public ApiClientConfiguration(Environment environment) {
    Binder binder = Binder.get(environment);
    List&lt;HashMap&gt; properties = binder.bind(&quot;api.clients&quot;, Bindable.listOf(HashMap.class)).get();
    clients = properties.stream().map(client -&gt; new ApiClient(String.valueOf(client.get(&quot;name&quot;)),
            String.valueOf(client.get(&quot;url&quot;)), String.valueOf(client.get(&quot;key&quot;)))).toList();
}    

@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

clients.forEach(client -&amp;gt; {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ApiClient.class);
    builder.addPropertyValue(&amp;quot;name&amp;quot;, client.getName());
    builder.addPropertyValue(&amp;quot;url&amp;quot;, client.getUrl());
    builder.addPropertyValue(&amp;quot;key&amp;quot;, client.getkey());
    registry.registerBeanDefinition(API_CLIENT_BEAN_NAME + client.getName(), builder.getBeanDefinition());
});

}

@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { }

}

如上,实现了 BeanDefinitionRegistryPostProcessor 接口。我们覆写 postProcessBeanDefinitionRegistry 方法,该方法负责根据自定义属性注册 Bean。

首先,定义了一个常量 API_CLIENT_BEAN_NAME,它用作 Bean 名称的前缀。在构造函数中,使用 BinderAPIEnvironment 对象中读取属性。然后,使用这些属性创建 ApiClient 对象。

在执行 postProcessBeanDefinitionRegistry() 方法时,我们会遍历属性,并使用 BeanDefinitionRegistry 对象注册 ApiClient Bean。

使用 BeanDefinitionBuilder 创建 Bean,指定 Bean 类。然后,可以使用字段名逐一设置 Bean 属性。

注意,这里为每个 Bean 注册了一个唯一的名称: API_CLIENT_BEAN_NAME + client.getName()。这有助于我们从 Context 中读取我们选择的 Bean。

3.2、Application 类 {#32application-类}

最后,定义 main 类,并用 @SpringBootApplication 对其进行注解:

@SpringBootApplication
public class RegistryPostProcessorApplication {
public static void main(String[] args) {
    SpringApplication.run(RegistryPostProcessorApplication.class, args);
}

@Bean public ApiClientConfiguration apiClientConfiguration(ConfigurableEnvironment environment) { return new ApiClientConfiguration(environment); }

}

如上,定义 ApiClientConfiguration Bean,并将 ConfigurableEnvironment 对象传递给构造函数。用于读取 ApiClientConfiguration 类中的属性。

4、测试 {#4测试}

现在,Bean 已经注册完成,编写一个简单的测试类进行验证:

@SpringBootTest
class ApiClientConfigurationTest {
    @Autowired
    private ApplicationContext context;
@Test
void givenBeansRegistered_whenConnect_thenConnected() {
    ApiClient exampleClient = (ApiClient) context.getBean(&quot;apiClient_example&quot;);
    Assertions.assertEquals(&quot;Connecting to example at https://api.example.com&quot;, exampleClient.getConnectionProperties());
ApiClient anotherExampleClient = (ApiClient) context.getBean(&amp;quot;apiClient_anotherexample&amp;quot;);
Assertions.assertEquals(&amp;quot;Connecting to anotherexample at https://api.anotherexample.com&amp;quot;, anotherExampleClient.getConnectionProperties());

}

}

如上,使用 @SpringBootTest 注解来加载 application context。然后,使用 ApplicationContext 对象,通过 getBean() 方法从 context 中获取 BeangetBean() 方法将唯一的 Bean 名称作为参数,并从 context 中返回 Bean。

该测试将检查 Bean 是否正确注册并设置了正确的连接属性。

5、总结 {#5总结}

本文介绍了如何通过 BeanDefinitionRegistryPostProcessor 接口根据自定义属性动态注册 Spring Bean,以及如何从上下文中检索并使用 Bean。


Ref:https://www.baeldung.com/spring-beans-dynamic-registration-properties

赞(11)
未经允许不得转载:工具盒子 » 根据属性(Properties)动态注册 Spring Bean