1. 程式人生 > >Spring Boot通過ImportBeanDefinitionRegistrar動態注入Bean

Spring Boot通過ImportBeanDefinitionRegistrar動態注入Bean

在閱讀Spring Boot原始碼時,看到Spring Boot中大量使用ImportBeanDefinitionRegistrar來實現Bean的動態注入。它是Spring中一個強大的擴充套件介面。本篇文章來講講它相關使用。

Spring Boot中的使用

在Spring Boot 內建容器的相關自動配置中有一個ServletWebServerFactoryAutoConfiguration類。該類的部分程式碼如下:

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

    // ...
    
    /**
     * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
     * {@link ImportBeanDefinitionRegistrar} for early registration.
     */
    public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

        private ConfigurableListableBeanFactory beanFactory;

        // 實現BeanFactoryAware的方法,設定BeanFactory
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
            }
        }

        // 註冊一個WebServerFactoryCustomizerBeanPostProcessor
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                BeanDefinitionRegistry registry) {
            if (this.beanFactory == null) {
                return;
            }
            registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
                    WebServerFactoryCustomizerBeanPostProcessor.class);
            registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
                    ErrorPageRegistrarBeanPostProcessor.class);
        }

        // 檢查並註冊Bean
        private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
            // 檢查指定型別的Bean name陣列是否存在,如果不存在則建立Bean並注入到容器中
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
                RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }
        }
    }
}

在這個自動配置類中,基本上展示了ImportBeanDefinitionRegistrar最核心的用法。這裡該介面主要用來註冊BeanDefinition。

BeanPostProcessorsRegistrar實現了ImportBeanDefinitionRegistrar介面和BeanFactoryAware介面。其中BeanFactoryAware介面的實現是用來暴露Spring的ConfigurableListableBeanFactory物件。

而實現registerBeanDefinitions方法則是用來對Bean的動態注入,這裡注入了WebServerFactoryCustomizerBeanPostProcessor和ErrorPageRegistrarBeanPostProcessor。

簡單瞭解了Spring Boot中的一個使用例項,下面我們總結一下使用方法,並自己實現一個類似的功能。

ImportBeanDefinitionRegistrar使用

Spring官方通過ImportBeanDefinitionRegistrar實現了@Component、@Service等註解的動態注入機制。

很多三方框架整合Spring的時候,都會通過該介面,實現掃描指定的類,然後註冊到spring容器中。 比如Mybatis中的Mapper介面,springCloud中的FeignClient介面,都是通過該介面實現的自定義註冊邏輯。

所有實現了該介面的類的都會被ConfigurationClassPostProcessor處理,ConfigurationClassPostProcessor實現了BeanFactoryPostProcessor介面,所以ImportBeanDefinitionRegistrar中動態註冊的bean是優先於依賴其的bean初始化,也能被aop、validator等機制處理。

基本步驟:

  • 實現ImportBeanDefinitionRegistrar介面;
  • 通過registerBeanDefinitions實現具體的類初始化;
  • 在@Configuration註解的配置類上使用@Import匯入實現類;

簡單示例

這裡實現一個非常簡單的操作,自定義一個@Mapper註解(並非Mybatis中的Mapper實現),實現類似@Component的功能,添加了@Mapper註解的類會被自動載入到spring容器中。

首先建立@Mapper註解。

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface Mapper {
}

建立UserMapper類,用於使用@Mapper注。

@Mapper
public class UserMapper {
}

定義ImportBeanDefinitionRegistrar的實現類MapperAutoConfigureRegistrar。如果需要獲取Spring中的一些資料,可實現一些Aware介面,這實現了ResourceLoaderAware。

public class MapperAutoConfigureRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        MapperBeanDefinitionScanner scanner = new MapperBeanDefinitionScanner(registry, false);
        scanner.setResourceLoader(resourceLoader);
        scanner.registerFilters();
        scanner.addIncludeFilter(new AnnotationTypeFilter(Mapper.class));
        scanner.doScan("com.secbro2.learn.mapper");
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

在上面程式碼中,通過ResourceLoaderAware介面的setResourceLoader方法獲得到了ResourceLoader物件。

在registerBeanDefinitions方法中,藉助ClassPathBeanDefinitionScanner類的實現類來掃描獲取需要註冊的Bean。

MapperBeanDefinitionScanner的實現如下:

public class MapperBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

    public MapperBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        super(registry, useDefaultFilters);
    }

    protected void registerFilters() {
        addIncludeFilter(new AnnotationTypeFilter(Mapper.class));
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        return super.doScan(basePackages);
    }
}

MapperBeanDefinitionScanner繼承子ClassPathBeanDefinitionScanner,掃描被@Mapper的註解的類。

在MapperBeanDefinitionScanner中指定了addIncludeFilter方法的引數為包含Mapper的AnnotationTypeFilter。

當然也可以通過excludeFilters指定不載入的型別。這兩個方法由它們的父類ClassPathScanningCandidateComponentProvider提供的。

完成了上面的定義,則進行最後一步引入操作了。建立一個自動配置類MapperAutoConfig,並通過@Import引入自定義的Registrar。

@Configuration
@Import(MapperAutoConfigureRegistrar.class)
public class MapperAutoConfig {
}

至此,整個程式碼的功能已經編寫完成,下面寫一個單元測試。

@RunWith(SpringRunner.class)
@SpringBootTest
public class MapperAutoConfigureRegistrarTest {

    @Autowired
    UserMapper userMapper;

    @Test
    public void contextLoads() {
        System.out.println(userMapper.getClass());
    }
}

執行單元測試程式碼,會發現列印如下日誌:

class com.secbro2.learn.mapper.UserMapper

說明UserMapper已經被例項化成功,並注入Spring容器當中。

小結

當然,這裡的UserMapper並不介面,這裡的實現也並不是Mybatis中的實現形式。只是為了演示該功能的簡單示例。需要注意的是文中提到了兩種實現的例項,第一種是Spring Boot中的實現,第二種是我們的Mapper例項。展現了兩種不同方法的註冊的操作,但整個使用流程是一致的,讀者注意仔細品味,並在此基礎上進行拓展更復雜的功能。

原文連結:《Spring Boot通過ImportBeanDefinitionRegistrar動態注入Bean》


程式新視界:精彩和成長都不容錯過

相關推薦

Spring Boot通過ImportBeanDefinitionRegistrar動態注入Bean

在閱讀Spring Boot原始碼時,看到Spring Boot中大量使用ImportBeanDefinitionRegistrar來實現Bean的動態注入。它是Spring中一個強大的擴充套件介面。本篇文章來講講它相關使用。 Spring Boot中的使用 在Spring Boot 內建容器的相關自動配置中

已經解決 spring boot 攔截器中注入bean

已經解決 spring boot 攔截器中如何注入bean的問題 @Configuration public class MyWebAppConfigurer extends WebMvcConfigurerAdapter @Bean RequestInterce

spring boot 學習(三) — 依賴注入 @Bean

spring 4推薦的@Configuration 和@bean 的用法,這樣我們可以省去繁瑣的配置檔案 第一步 建一個Maven工程 第二步新增依賴  pom.xml <?xml version="1.0" encoding="UTF-8"?> <pro

記錄Spring Boot大坑一個,在bean中如果有@Test單元測試,不會注入成功

記錄Spring Boot大坑一個,在bean中如果有@Test單元測試,不會注入成功 記錄Spring Boot大坑一個,在bean中如果有@Test單元測試,不會注入成功 記錄Spring Boot大坑一個,在bean中如果有@Test單元測試,不會注入成功 org.springframework.

Spring的《XML顯式裝配bean》之通過構造器注入Bean

本文主要講解兩點: 1.怎麼樣宣告一個bean 2.通過構造器注入bean 1. 怎麼樣宣告一個bean? 1) 建立一個類: package spring.ch1.topic5; public class Song {

Spring通過註解來配置bean以及自動注入

今天看到一篇好文章,寫的很是詳細。再加上自己的理解和補充,成了這一篇文章。文後會獻上原文連結。 使用Spring經常性的需要: 通過註解配置bean   基於註解配置bean   基於註解來配置bean的屬性    ----------------------

Spring boot將配置屬性注入bean類中

一、@ConfigurationProperties註解的使用 看配置檔案,我的是yaml格式的配置: // file application.yml my: servers: - dev.bar.com - foo.bar.co

spring boot 通過session 取數據

get ets attribute ini () fig spring boot word //先把數據存入session 中  LoginInfo loginInfo = userService.login(userName, password); reque

Spring運行時動態註冊bean

factory context 創建 contex ner 註入 ref 刪除 定義 在spring運行時,動態的添加bean,dapeng框架在解析xml的字段時,使用到了動態註冊,註冊了一個實現了FactoryBean類! 定義一個沒有被Spring管理的Cont

記錄Spring Boot大坑一個,在bean中如果有@Test單元測試,不會註入成功

記錄 one except frame oot beans org init def 記錄Spring Boot大坑一個,在bean中如果有@Test單元測試,不會註入成功 記錄Spring Boot大坑一個,在bean中如果有@Test單元測試,不會註入成功 記錄Sp

spring boot去除掃描自動注入依賴方法——Spring常用註解使用方法

問題: 最近做專案的時候,需要引入其他的jar。然後還需要掃描這些jar裡的某些bean。於 是使用註解:@ComponentScan 這個註解直接指定包名就可以,它會去掃描這個包下所有的class,然後判斷是否解析: 原始碼: public @interface SpringBoo

spring-boot 屬性定義和配置bean

自定義bean屬性 1.定義bean屬性 // 通過@ConfigurationProperties載入properties檔案內的配置, // 通過prefix屬性指定properties的配置的字首,通過locations指定properties檔案的位置 @ConfigurationProperti

Spring Boot + Mybatis + Druid 動態切換多資料來源

在大型應用程式中,配置主從資料庫並使用讀寫分離是常見的設計模式。 在Spring應用程式中,要實現讀寫分離,最好不要對現有程式碼進行改動,而是在底層透明地支援。 這樣,就需要我們再一個專案中,配置兩個,乃至多個數據源。 今天,小編先來介紹一下自己配置動態多資料來源的步驟 專案簡介:   編譯器:ID

Spring Boot 系列之五:Spring Boot 通過devtools進行熱部署

前面已經分享過四篇學習文章: 1、Spring Boot 系統之一:Spring Boot 入門 2、Spring Boot 系統之二:Spring Boot 修改預設埠號和context path 3、Spring Boot 系統之三:Spring Boot 整合JdbcTemplat

spring boot 通過過濾器修改request中的body引數

1。先使用Wrapper,重寫HttpServletRequestWrapper。 package com.cepht.platform.icl.config;   import java.util.Enumeration; import java.util.Map; imp

Spring 多執行緒下注入bean問題詳解

本文介紹了Spring 多執行緒下注入bean問題詳解,分享給大家,具體如下: 問題 Spring中多執行緒注入userThreadService注不進去,顯示userThreadService為null異常 程式碼如下: public class UserThreadTask implements

spring boot通過自定義註解和AOP攔截指定的請求

本文主要通過切面類和自定註解的方式,攔截指定的介面(程式碼中已經作了詳細的說明) 目錄 一 準備工作 三 切面類 五 測試結果  一 準備工作 1.1 新增依賴 通過spr

Spring Boot通過Profiles實現多環境下配置切換

1、在yml中使用pom定義的maven屬性變數 格式:@[email protected] spring: application: name: dream-web-gateway profiles: active: '@[

Spring Boot + Mybatis 實現動態資料來源

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"

Spring Boot啟動時動態切換每個環境的配置檔案

  開發專案一般是開發環境,測試環境,和生產環境,例如:Spring Boot的application.properties配置如下   application-dev1.priperties相當於開發環境,以此類推,當你啟動Spring Boot時,切換每個環