在本教程中,我们将探讨如何根据自定义属性动态注册Bean 。我们将探讨BeanDefinitionRegistryPostProcessor 接口以及如何使用它向应用程序上下文添加 bean。
让我们首先创建一个简单的 Spring Boot 应用程序。
首先,我们将定义一个要动态注册的 bean。然后,我们将提供一个属性来决定如何注册 beans。最后,我们将定义一个配置类,它将根据我们的自定义属性注册 bean。
添加 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 依赖项。接下来,我们定义一个要根据自定义应用程序属性注册的 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 创建类定义。相反,我们希望为每个 API 动态定义属性并注册 bean。我们不应该使用@Component 或 @Service注释ApiClient 类 ,因为我们不想使用组件扫描将其注册为 bean。
让我们添加一个属性来确定 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 时使用这些属性。动态注册Bean
Spring 提供了一种使用BeanDefinitionRegistryPostProcessor接口动态注册 bean 的方法 。 该接口允许我们在注册带注释的 bean 定义后添加或修改 bean 定义。 由于它发生在 bean 实例化之前,因此 bean 在应用程序上下文完全初始化之前注册。
BeanDefinitionRegistry后处理器
定义一个配置类,它将根据自定义属性注册 ApiClient beans :
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 从环境对象中读取属性。然后,我们使用这些属性创建ApiClient 对象。
在实现postProcessBeanDefinitionRegistry()方法时,我们迭代属性并 使用 BeanDefinitionRegistry对象注册ApiClient beans 。
我们使用 BeanDefinitionBuilder 创建 bean 。它要求我们定义bean类。然后它让我们使用字段名称一一设置 bean 属性。
请注意,我们使用唯一的名称注册每个 bean – API_CLIENT_BEAN_NAME + client.getName()。当我们想要从上下文中读取我们选择的 bean 时,这将很有帮助。
最后,我们需要定义主应用程序类并使用 @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类中的属性 。现在 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 是否已正确注册并设置了正确的连接属性。