SpringBoot实现ImportBeanDefinitionRegistrar动态注入

 更新时间:2024年02月19日 09:38:25   作者:清醒的人最荒唐  
在阅读Spring Boot源码时,看到Spring Boot中大量使用ImportBeanDefinitionRegistrar来实现Bean的动态注入,它是Spring中一个强大的扩展接口,本文就来详细的介绍一下如何使用,感兴趣的可以了解一下

在阅读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.edu.mapper.UserMapper

当然,这里的UserMapper并不是接口,这里的实现也并不是Mybatis中的实现形式。只是为了演示该功能的简单示例。需要注意的是文中提到了两种实现的实例,第一种是Spring Boot中的实现,第二种是我们的Mapper实例。展现了两种不同方法的注册的操作,但整个使用流程是一致的,读者注意仔细品味,并在此基础上进行拓展更复杂的功能。

到此这篇关于SpringBoot实现ImportBeanDefinitionRegistrar动态注入的文章就介绍到这了,更多相关SpringBoot 动态注入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Java编程JDialog窗体的用法及实例

    详解Java编程JDialog窗体的用法及实例

    这篇文章主要介绍了Java编程中JDialog窗体的用法及实例,描述了其特征,具有一定参考价值,需要的朋友可以了解下。
    2017-09-09
  • 使用Mybatis的Batch Insert Support 实现批量插入

    使用Mybatis的Batch Insert Support 实现批量插入

    这篇文章主要介绍了使用Mybatis的Batch Insert Support 实现批量插入。具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • nacos服务注册服务发现依赖配置详解

    nacos服务注册服务发现依赖配置详解

    这篇文章主要为大家介绍了nacos服务注册服务发现依赖配置详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • java常用工具类 Reflect反射工具类、String字符串工具类

    java常用工具类 Reflect反射工具类、String字符串工具类

    这篇文章主要为大家详细介绍了java常用工具类,包括Reflect反射工具类、String字符串工具类,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-05-05
  • mybatis Map查询结果下划线转驼峰的实例

    mybatis Map查询结果下划线转驼峰的实例

    这篇文章主要介绍了mybatis Map查询结果下划线转驼峰的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • java线程组构造方法源码解析

    java线程组构造方法源码解析

    这篇文章主要为大家介绍了java线程组构造方法源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • 浅谈spring中scope作用域

    浅谈spring中scope作用域

    这篇文章主要介绍了浅谈spring中scope作用域,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • Java实现24点小游戏

    Java实现24点小游戏

    这篇文章主要为大家详细介绍了Java实现24点小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • Spring注解@Value在controller无法获取到值的解决

    Spring注解@Value在controller无法获取到值的解决

    这篇文章主要介绍了Spring注解@Value在controller无法获取到值的解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Spring Gateway处理微服务的路由转发机制

    Spring Gateway处理微服务的路由转发机制

    我们详细地介绍了Spring Gateway,这个基于Spring 5、Spring Boot 2和Project Reactor的API网关,通过这篇文章,我们可以清晰地看到Spring Gateway的工作原理,以及它的强大之处,感兴趣的朋友一起看看吧
    2024-08-08

最新评论