Spring Boot經過ImportBeanDefinitionRegistrar動態注入Bean

在閱讀Spring Boot源碼時,看到Spring Boot中大量使用ImportBeanDefinitionRegistrar來實現Bean的動態注入。它是Spring中一個強大的擴展接口。本篇文章來說講它相關使用。web

Spring Boot中的使用

在Spring Boot 內置容器的相關自動配置中有一個ServletWebServerFactoryAutoConfiguration類。該類的部分代碼以下:spring

@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。app

簡單瞭解了Spring Boot中的一個使用實例,下面咱們總結一下使用方法,並本身實現一個相似的功能。框架

ImportBeanDefinitionRegistrar使用

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

不少三方框架集成Spring的時候,都會經過該接口,實現掃描指定的類,而後註冊到spring容器中。 好比Mybatis中的Mapper接口,springCloud中的FeignClient接口,都是經過該接口實現的自定義註冊邏輯。spring-boot

全部實現了該接口的類的都會被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


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

程序新視界-微信公衆號

相關文章
相關標籤/搜索