【小家Spring】Spring容器加载Bean定义信息的两员大将:AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner(下)

简介: 【小家Spring】Spring容器加载Bean定义信息的两员大将:AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner(下)

ClassPathScanningCandidateComponentProvider#findCandidateComponents

  public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    // 上面说过了CandidateComponentsIndex是Spring5提供的优化扫描的功能
    // 显然这里编译器我们没有写META-INF/spring.components索引文件,所以此处不会执行Spring5 的扫描方式,所以我暂时不看了(超大型项目才会使用Spring5的方式)
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
      return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    } else {
      // Spring 5之前的方式(绝大多数情况下,都是此方式)
      return scanCandidateComponents(basePackage);
    }
  }


scanCandidateComponents:根据basePackage扫描候选的组件们(非常重要)


  private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
      // 1.根据指定包名 生成包搜索路径
      //通过观察resolveBasePackage()方法的实现, 我们可以在设置basePackage时, 使用形如${}的占位符, Spring会在这里进行替换 只要在Enviroment里面就行
      // 本次值为:classpath*:com/fsx/config/**/*.class
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
          resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      //2. 资源加载器 加载搜索路径下的 所有class 转换为 Resource[]
      // 拿着上面的路径,就可以getResources获取出所有的.class类,这个很强大~~~
      // 真正干事的为:PathMatchingResourcePatternResolver#getResources方法
      // 此处能扫描到两个类AppConfig(普通类,没任何注解标注)和RootConfig。所以接下里就是要解析类上的注解,以及过滤掉不是候选的类(比如AppConfig)
      // 注意:这里会拿到类路径下(不包含jar包内的)的所有的.class文件 可能有上百个,然后后面再交给后面进行筛选~~~~~~~~~~~~~~~~(这个方法,其实我们也可以使用)
      // 当然和getResourcePatternResolver和这个模版有关
      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
      // 记录日志(下面我把打印日志地方都删除)
      boolean traceEnabled = logger.isTraceEnabled();
      boolean debugEnabled = logger.isDebugEnabled();
      // 接下来的这个for循环:就是把一个个的resource组装成
      for (Resource resource : resources) {
        //文件必须可读 否则直接返回空了
        if (resource.isReadable()) {
          try {
            //读取类的 注解信息 和 类信息 ,两大信息储存到  MetadataReader
            MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
            // 根据TypeFilter过滤排除组件。因为AppConfig没有标准@Component或者子注解,所以肯定不属于候选组件  返回false
            // 注意:这里一般(默认处理的情况下)标注了默认注解的才会true,什么叫默认注解呢?就是@Component或者派生注解。还有javax....的,这里省略啦
            if (isCandidateComponent(metadataReader)) {
              //把符合条件的 类转换成 BeanDefinition
              ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
              sbd.setResource(resource);
              sbd.setSource(resource);
              // 再次判断 如果是实体类 返回true,如果是抽象类,但是抽象方法 被 @Lookup 注解注释返回true (注意 这个和上面那个是重载的方法) 
              // 这和上面是个重载方法  个人觉得旨在处理循环引用以及@Lookup上
              if (isCandidateComponent(sbd)) {
                candidates.add(sbd);
              }
            }
          } 
        }
      }
    }
    return candidates;
  }
// 备注:此时ComponentScan这个注解还并没有解析


ClassPathBeanDefinitionScanner#postProcessBeanDefinition


  protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
    // 位Bean定义 执行些默认的信息
    // BeanDefinitionDefaults是个标准的javaBean,有一些默认值
    beanDefinition.applyDefaults(this.beanDefinitionDefaults);
    // 自动依赖注入 匹配路径(此处为null,不进来)
    if (this.autowireCandidatePatterns != null) {
      beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
    }
  }
AbstractBeanDefinition#applyDefaults:
  public void applyDefaults(BeanDefinitionDefaults defaults) {
    setLazyInit(defaults.isLazyInit());
    setAutowireMode(defaults.getAutowireMode());
    setDependencyCheck(defaults.getDependencyCheck());
    setInitMethodName(defaults.getInitMethodName());
    setEnforceInitMethod(false);
    setDestroyMethodName(defaults.getDestroyMethodName());
    setEnforceDestroyMethod(false);
  }


就这样,扫描的Bean就全部注册完成了(若你的Config比较多的情况下,用扫描的方式更加简单些~)


总结


观看Spring源码这么久,最大的感受就是:组件繁多,但管理有序。它完美的实现了单一职责的设计原则,谁的事情谁去干,这点体现得非常好。只有遵循了这个原则,才能做到无状态和组件化,达到随意装配的效果~

另外,还有一种设计思想就是预加载。什么组件在什么时候加载,什么时候可以预加载进去,都是设计的精妙之处


理解ClassPathBeanDefinitionScanner的工作原理,可以帮助理解Spring IOC 容器的初始化过程。

同时对理解MyBatis 的 Mapper 扫描 也是有很大的帮助。

因为 MyBatis 的MapperScannerConfigurer的底层实现也是一个ClassPathBeanDefinitionScanner的子类。就像我们自定义扫描器那样,自定定义了 过滤器的过滤规则。


这两名大将还提供了比如扫描包的核心方法,这个在讲解具体IOC容器流程的时候再具体分析~~~

相关文章
|
9月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
9月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
772 2
|
XML Java 数据格式
Spring IoC容器的设计与实现
Spring 是一个功能强大且模块化的 Java 开发框架,其核心架构围绕 IoC 容器、AOP、数据访问与集成、Web 层支持等展开。其中,`BeanFactory` 和 `ApplicationContext` 是 Spring 容器的核心组件,分别定位为基础容器和高级容器,前者提供轻量级的 Bean 管理,后者扩展了事件发布、国际化等功能。
392 18
|
XML Java 数据格式
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
|
XML Java 数据格式
Spring容器的本质
本文主要讨论Spring容器最核心的机制,用最少的代码讲清楚Spring容器的本质。
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——定义返回的统一 json 结构
本课主要讲解Spring Boot中的全局异常处理方法。在项目开发中,各层操作难免会遇到各种异常,若逐一处理将导致代码耦合度高、维护困难。因此,需将异常处理从业务逻辑中分离,实现统一管理与友好反馈。本文通过定义一个简化的JsonResult类(含状态码code和消息msg),结合全局异常拦截器,展示如何封装并返回标准化的JSON响应,从而提升代码质量和用户体验。
388 0
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
585 6
|
监控 IDE Java
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
1665 8
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
459 1
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。