在本教程中,我們將探討如何根據自定義屬性動態註冊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 是否已正確註冊並設定了正確的連線屬性。