在閱讀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》