1. 概述
在这个教程中,我们将探讨如何根据自定义属性动态注册bean。我们将研究BeanDefinitionRegistryPostProcessor
接口,以及如何利用它将bean添加到应用上下文。
2. 代码设置
首先,我们创建一个简单的Spring Boot应用。
2.1. 依赖项
让我们从添加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. 类类
接下来,我们定义一个根据自定义应用属性想要动态注册的API客户端:
public class ApiClient {
private String name;
private String url;
private String key;
// standard getters, setters and constructors
public String getConnectionProperties() {
return "Connecting to " + name + " at " + url;
}
}
假设我们想根据提供的属性使用这个bean连接不同的API。我们不想为每个API创建类定义。相反,我们希望定义属性并根据需要动态注册bean。
我们不应该在ApiClient
类上使用@Component
或@Service
注解,因为我们不希望通过组件扫描来注册它。
2.3. 属性
让我们添加一个属性,决定bean应该注册哪些API。我们在application.yml
文件中定义这个属性:
api:
clients:
- name: example
url: https://api.example.com
key: 12345
- name: anotherexample
url: https://api.anotherexample.com
key: 67890
这里定义了两个客户端及其各自的属性。我们在注册bean时会使用这些属性。
3. 动态注册bean
Spring提供了通过BeanDefinitionRegistryPostProcessor
接口动态注册bean的方法。这个接口允许我们在注解的bean定义注册后添加或修改bean定义。由于它发生在bean实例化之前,因此在应用上下文完全初始化之前就注册了bean。
3.1. BeanDefinitionRegistryPostProcessor
让我们定义一个配置类,根据自定义属性注册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名称的前缀。在构造函数中,我们使用Binder
API从Environment
对象读取属性。然后,我们使用属性创建ApiClient
对象。
在实现postProcessBeanDefinitionRegistry()
方法时,我们遍历属性,并使用BeanDefinitionRegistry
对象注册ApiClient
bean。
我们使用BeanDefinitionBuilder
创建bean,需要定义bean类。然后,我们可以逐一设置bean属性,使用字段名。
注意,我们为每个bean注册一个唯一的名称——API_CLIENT_BEAN_NAME + client.getName()
。这将帮助我们在需要时从上下文中读取所需的bean。
3.2. 主应用类
最后,我们需要定义主应用类,并用@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. 测试
现在bean已经注册,让我们测试它们是否具有正确的属性来连接API。为了测试这一点,我们将编写一个简单的测试类:
@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
注解加载应用上下文。然后,我们使用ApplicationContext
对象使用getBean()
方法从上下文中获取bean。getBean()
方法接受唯一的bean名称作为参数,并从上下文中返回bean。
测试检查bean是否正确注册并且设置了正确的连接属性。
5. 总结
在这个教程中,我们探讨了如何使用BeanDefinitionRegistryPostProcessor
接口根据自定义属性动态注册Spring bean。我们还编写了一个简单的测试案例,展示了如何从上下文中检索bean并使用它们。
如往常一样,示例代码可在GitHub上找到此处。