【小家Spring】Spring向容器注册Bean的高级应用:@Import、DeferredImportSelector、ImportBeanDefinitionRegistrar的使用(下)

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家Spring】Spring向容器注册Bean的高级应用:@Import、DeferredImportSelector、ImportBeanDefinitionRegistrar的使用(下)

ImportBeanDefinitionRegistrar


该接口功能非常强大,能够实现快速的、批量的、扫描式的注册。比如我们熟悉的ServletComponentScanRegistrar就是去解析注解@ServletComponentScan实现批量注册Bean定义

MapperScannerRegistrar就是MyBatis用来解析@MapperScan注解,来扫描的 等等还有很多类似的设计方式


先看一个最简单的效果吧:


@Configuration
@Import({Parent.class, AntPathMatcher.class, MyImportSelector.class, MyDeferredImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class RootConfig {
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    // 同样的,这种注入都是不好使的(相同的,那些感知接口是可以实现的,从而注入对应组件)
    @Autowired
    private HelloService helloService;
    /**
     * 实现了该接口让我们的这个类成为了拥有注册bean的能力
     * 也可以让我们实现动态注入(根据条件、逻辑进行动态注入)
     *
     * @param importingClassMetadata 注解信息和本类信息
     * @param registry               注册器,我们可以向容器里面注册[Bean定义信息]
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        System.out.println("this MyImportBeanDefinitionRegistrar");
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(GenericBean.class);
        registry.registerBeanDefinition("genericBean", beanDefinition);
    }
}


输出:(genericBean成功被注入容器了)


rootConfig
helloServiceImpl
com.fsx.bean.Parent
org.springframework.util.AntPathMatcher
com.fsx.bean.Child
genericBean


从另外一个日志的打印来看:MyImportBeanDefinitionRegistrar是更加滞后执行的。那么下面我们就要看看它到底啥时候执行的呢?


this MyImportSelector...
this MyDeferredImportSelector...
this MyImportBeanDefinitionRegistrar


有了上面的源码分析我们知道了,实现了ImportBeanDefinitionRegistrar接口的,最后是这么一句话给缓存下来了(还木有执行):


// registrar是已经被实例化了的当前类
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());


下面重点看看,该接口到底什么时候执行的呢?根据断点,跟踪到它的加载时机是这句代码里:this.reader.loadBeanDefinitions(configClasses);


  public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    // 依次解析每一个配置类,这里面注意了configClass 比如我们的RootConfig这个对象里有个字段
    //importBeanDefinitionRegistrars是记录着了我们前面add进去的ImportBeanDefinitionRegistrar的,因此它会在此处开始执行了
    for (ConfigurationClass configClass : configurationModel) {
      loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
  }
  // 这个方法很重要:它处理了多种方式(@Bean、实现接口类注册等等)完成向容器里注册Bean定义信息
  private void loadBeanDefinitionsForConfigurationClass(
      ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    // 如果这咯Config不需要被解析,做一些清理、移除的操作~~~~
    if (trackedConditionEvaluator.shouldSkip(configClass)) {
      String beanName = configClass.getBeanName();
      if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
        this.registry.removeBeanDefinition(beanName);
      }
      this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
      return;
    }
///稍微注意一下Spring处理这些Bean定义的顺序,在某些判断逻辑中或许能用到///
    // 如果是被单纯@Import进来的,这个值是true的,默认值是false哦
    if (configClass.isImported()) {
      // 这个处理源码这里不分析了,比较简单。支持@Scope、@Lazy、@Primary、@DependsOn、@Role、@Description等等一些通用的基本属性
      registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    //处理方法上的@Bean 解析@Bean上面各种属性值。也处理上面提到的那些通用注解@Lazy等等吧
    //这里面只说一个内部比较重要的方法isOverriddenByExistingDefinition(beanMethod, beanName)  
    // 该方法目的主要是去重。其实如果是@Configuration里面Bean重名了,IDEA类似工具发现,但是无法判断xml是否也存在(注意,发现归发现,但并不是编译报错哦~~~)
    // 它的处理策略为:若来自同一个@Configuration配置类,那就保留之前的。若来自不同配置类,那就覆盖
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
      loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    //处理@ImportResource,里面解析xml就是上面说到的解析xml的XmlBeanDefinitionReader
    //所以,咱们@Configuration和xml是可以并行使用的
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    // 最后,这里就是咱们今天的主菜了:解析咱们的ImportBeanDefinitionRegistrars
    // configClass.getImportBeanDefinitionRegistrars():就是我们上面异步add进去的那些注册器们
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
  }


loadBeanDefinitionsFromRegistrars完整解析:


// 没什么多余的代码  所有的注册逻辑(哪些Bean需要注册,哪些不需要之类的,全部交给子类去实现)
// 用处:上面有提到,比如@MapperScan这种批量扫描的===
  private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
    registrars.forEach((registrar, metadata) ->
        registrar.registerBeanDefinitions(metadata, this.registry));
  }

应用场景分析

根据各个接口的特点,有各自的应用场景。因为直接@Import普通类的场景相对较少,这里主要说说实现接口的方式的场景:


ImportSelector接口应用场景

AdviceModeImportSelector:它是个抽象类。(实现类有出名的AsyncConfigurationSelector、CachingConfigurationSelector等,因为都是基于代理来做的,所以都继承了此抽象)。

它拿到泛型类型(比如@EnableAsync或者@EnableCaching),然后解析注解为AnnotationAttributes,最后由子类去实现select逻辑(具体要向容器注入的Class全类名),比如注入ProxyAsyncConfiguration,或者其它的。。。

总之,向这种还不能决定去注入哪个处理器(如果你能决定,那就直接@Import那个类好了,没必要实现接口了嘛),然后可以实现此接口,写出一些判断逻辑,不同的配置情况注入不同的处理类。


DeferredImportSelector接口应用场景

它和上面只是执行的时机不同。在Spring内部没有应用,但是在Spring Boot中却有大量的应用,比如:

AutoConfigurationImportSelector、EnableCircuitBreakerImportSelector等等(在SpirngBoot章节中还会重点分析)


实现这个接口的基本思想是:默认处理(以用户配置的为准,若用户没管,那就执行我的默认配置呗)。执行生效的一个先后顺序的简单控制


ImportBeanDefinitionRegistrar接口应用场景


它的应用场景特别的有用,因此也是最常使用的。因为她直接可以向工厂里注册Bean的定义信息(当然也可以拿出来Bean定义信息,做出对应的修改)~


下面两个实现,都喝@EnableAspectJAutoProxy注解相关:


  • AspectJAutoProxyRegistrar:它能解析注解的时候,从BeanFactory拿出指定的Bean,设置一些参数值等等
  • AutoProxyRegistrar:自动代理的注册器。它和上面的区别在于它和代理的类型无关(它可以指定mode类型),而上面是表示就是用AspectJ来做切面代理。


实现它的基本思想是:当自己需要操作BeanFactory里面的Bean的时候,那就必须只有它才能做到了。而且它还有个方便的地方,那就是做包扫描的时候,比如@MapperScan类似这种的时候,用它处理更为方便(因为扫描到了直接注册即可)


备注:@Mapper的扫描依赖于ClassPathMapperScanner,它由mybatis-spring提供。它继承与ClassPathBeanDefinitionScanner由Spring底层提供


总结


注意,完成了所有的这些注解、配置文件的解析,Bean们都还只是被加载了,还没有加入到Bean的定义信息里面,更别谈实例化了。要加入到Bean的定义信息里面存储好,还得这一步:


//configClasses就是parser.parse(candidates);上面解析完成了的配置类
//根据这些已经解析好了的配置类,由这个ConfigurationClassBeanDefinitionReader去加载Bean定义信息
this.reader.loadBeanDefinitions(configClasses);


然后,上面已经从源码上分析了Spring处理的一个流程,希望各位小伙伴能更宏观的明晰这个执行的时序图(流程图),最好是能画出来(我这里就不画了哈,若哪位小伙伴有心,欢迎留言贡献一份,不胜感激~)

相关文章
|
11天前
|
Kubernetes 开发者 容器
"Kubernetes的生死抉择:揭秘Pod容器重启策略如何决定应用命运的惊天大戏"
【8月更文挑战第20天】Kubernetes (k8s) 是一个强大的容器编排平台,其中Pod是最小的运行单元。Pod的重启策略确保服务连续性,主要有Always(总是重启)、OnFailure(失败时重启)和Never(从不重启)。默认策略为Always。根据不同场景,如Web服务、批处理作业或一次性任务,可以选择合适的策略。K8s还支持健康检查等高级机制来控制容器重启。合理配置这些策略对维护应用稳定性至关重要。
37 4
|
15天前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
15天前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
15天前
|
XML Java 数据格式
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
这篇文章是Spring5框架的入门教程,详细讲解了IOC容器中Bean的自动装配机制,包括手动装配、`byName`和`byType`两种自动装配方式,并通过XML配置文件和Java代码示例展示了如何在Spring中实现自动装配。
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
|
1天前
|
Cloud Native 持续交付 Docker
云原生入门指南:构建你的首个容器化应用
【8月更文挑战第30天】云原生技术,作为现代软件开发的风向标,正在改变我们构建、部署和管理应用程序的方式。本篇文章将引导你了解云原生的核心概念,并通过一个简单的代码示例,展示如何将传统应用转变为容器化的云原生应用。无论你是新手开发者还是希望扩展知识的IT专业人士,这篇文章都将是你探索云原生世界的起点。
|
4天前
|
运维 开发者 Docker
Docker容器化技术在运维中的应用实践
【8月更文挑战第27天】本文旨在探讨Docker容器化技术如何在现代运维工作中发挥核心作用,通过深入浅出的方式介绍Docker的基本概念、优势以及实际应用场景。文章将结合具体案例,展示如何利用Docker简化部署流程、提高资源利用率和加强应用的可移植性。读者将获得对Docker容器技术在实际运维中应用的全面认识,并能够理解其在提升运维效率与质量方面的重要性。
|
9天前
|
存储 监控 Linux
在Linux中,如何进行容器技术的应用?
在Linux中,如何进行容器技术的应用?
|
11天前
|
Kubernetes Shell 测试技术
在Docker中,可以在一个容器中同时运行多个应用进程吗?
在Docker中,可以在一个容器中同时运行多个应用进程吗?
|
11天前
|
存储 Kubernetes 监控
在Docker中,很多应用容器都是默认后台运行的,怎么查看它们的输出和日志信息?
在Docker中,很多应用容器都是默认后台运行的,怎么查看它们的输出和日志信息?
|
15天前
|
XML Java 数据格式
Spring5入门到实战------8、IOC容器-Bean管理注解方式
这篇文章详细介绍了Spring5框架中使用注解进行Bean管理的方法,包括创建Bean的注解、自动装配和属性注入的注解,以及如何用配置类替代XML配置文件实现完全注解开发。
Spring5入门到实战------8、IOC容器-Bean管理注解方式
下一篇
云函数