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-starter
和 spring-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 "Connecting to " + name + " at " + 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<HashMap> properties = binder.bind("api.clients", Bindable.listOf(HashMap.class)).get();
clients = properties.stream().map(client -> new ApiClient(String.valueOf(client.get("name")),
String.valueOf(client.get("url")), String.valueOf(client.get("key")))).toList();
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
clients.forEach(client -> {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ApiClient.class);
builder.addPropertyValue("name", client.getName());
builder.addPropertyValue("url", client.getUrl());
builder.addPropertyValue("key", 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 名称的前缀。在构造函数中,使用 BinderAPI
从 Environment
对象中读取属性。然后,使用这些属性创建 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("apiClient_example");
Assertions.assertEquals("Connecting to example at https://api.example.com", exampleClient.getConnectionProperties());
ApiClient anotherExampleClient = (ApiClient) context.getBean("apiClient_anotherexample");
Assertions.assertEquals("Connecting to anotherexample at https://api.anotherexample.com", anotherExampleClient.getConnectionProperties());
}
}
如上,使用 @SpringBootTest
注解来加载 application context。然后,使用 ApplicationContext
对象,通过 getBean()
方法从 context 中获取 Bean
。getBean()
方法将唯一的 Bean 名称作为参数,并从 context 中返回 Bean。
该测试将检查 Bean 是否正确注册并设置了正确的连接属性。
5、总结 {#5总结}
本文介绍了如何通过 BeanDefinitionRegistryPostProcessor
接口根据自定义属性动态注册 Spring Bean,以及如何从上下文中检索并使用 Bean。
Ref:https://www.baeldung.com/spring-beans-dynamic-registration-properties