SpringBoot中根據屬性動態註冊Spring Bean

banq發表於2024-03-25

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

 

相關文章