Spring Annotation 启动流程

Stella981
• 阅读 471

Spring Annotation 启动流程

本文将对 Spring 注解方式的启动流程进行分析 author: huifer

实例

  • 在开始分析启动流程之前我们需要先编写一个例子,这个例子是我们分析源码的根.

下面是基本用例

package org.source.hot.spring.overview.ioc.bean.annotation;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 *
 *
 * @author huifer
 */
public class AnnotationContextDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context
                = new AnnotationConfigApplicationContext(AnnotationContextDemo.class);
        Us bean = context.getBean(Us.class);


        context.close();

    }

    @Bean
    public Us us() {
        Us us = new Us();
        us.setName("a");
        return us;
    }
}

class Us {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 对于这个类我们首先需要关注的是 AnnotationConfigApplicationContext 构造函数

    AnnotationConfigApplicationContext context
            = new AnnotationConfigApplicationContext(AnnotationContextDemo.class);
    
  • 其次我们需要对 @Bean 注解进行分析

AnnotationConfigApplicationContext 构造函数分析-参数是Class列表

  • 首先我们来对AnnotationConfigApplicationContext构造函数进行分析

  • 下面代码是我们在AnnotationContextDemo中编写的构造函数的详细代码.

    public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); refresh(); }

三行代码分别做了什么呢?

  1. this()

    • this() 的完整代码 👇

      public AnnotationConfigApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); }

    赋值成员变量readerscanner

    /**
     * 注解版本的beanDefinition阅读器
     */
    private final AnnotatedBeanDefinitionReader reader;
    
    
    /**
    * 扫描器
    */
    private final ClassPathBeanDefinitionScanner scanner;
    
  2. register(componentClasses)

    注册组件

    注册逐渐依靠的是成员变量reader中的register方法

  3. refresh()

    刷新上下文

    方法提供者: AbstractApplicationContext(本文不做分析)

  • 在构造函数中我们可以明确需要分析的方法是register,即org.springframework.context.annotation.AnnotatedBeanDefinitionReader#register

AnnotatedBeanDefinitionReader#register

  • 注解模式下bean定义的注册方法

入口方法代码如下

public void register(Class<?>... componentClasses) {
   for (Class<?> componentClass : componentClasses) {
      registerBean(componentClass);
   }
}

这里可以看出参数是多个组件类列表, 根据前文实例我们的参数是: org.source.hot.spring.overview.ioc.bean.annotation.AnnotationContextDemo

整体代码逻辑就是循环注册组件

真正需要关注的方法在doRegisterBean

首先我们来看参数

  1. Class<T> beanClass: bean 类型
  2. String name: bean 名称
  3. @Nullable Class<? extends Annotation>[] qualifiers: 限定注解
  4. @Nullable Supplier<T> supplier: bean 实例提供者
  5. @Nullable BeanDefinitionCustomizer[] customizers: 自定义处理Bean定义的实现类列表
  • 看完参数我们来整理执行流程

    1. 创建AnnotatedGenericBeanDefinition(带有注解的泛型bean定义)

      AnnotatedGenericBeanDefinition 中存储了关于 Bean的相关信息

      这里举几个例子

      1. beanClass
      2. lazyInit
      3. primary
      4. ....

      有兴趣的可以查阅下面类图中的AnnotatedGenericBeanDefinitionGenericBeanDefinitionAbstractBeanDefinition所包含的成员变量

      Spring Annotation 启动流程

    2. 设置实例提供者

      设置实例提供者就是将参数@Nullable Supplier<T> supplier设置到 AnnotatedGenericBeanDefinition 成员变量中

    3. 解析Scope属性,并设置

      解析Scope属性依靠 ScopeMetadataResolver 接口, 在注解环境下一般是 AnnotationScopeMetadataResolver 实现类

    4. beanName 处理

      beanName的处理其实也是根据参数String name 进行的

      当参数name 存在的情况下就用参数的name 作为 beanName

      当参数name不存在的情况下会依靠 **BeanNameGenerator 接口 ** 生成名称, 在注解环境下一般是 AnnotationBeanNameGenerator 提供具体的实现方法

      beanName 的可能:

      1. value 属性中获取. value 属性会在 @Bean@Service@Component等中出现
      2. 短类名,首字母小写
    5. 通用注解处理

      在这一步会对通用注解进行处理, 即设置AnnotatedGenericBeanDefinition中部分成员变量的属性

      通用注解:

      1. @Lazy
      2. @DependsOn
      3. @Role
      4. @Description
      • 方法提供者: AnnotationConfigUtils.processCommonDefinitionAnnotations(abd)
    6. 参数@Nullable BeanDefinitionCustomizer[] customizers的处理, 即对bean定义的自定义处理

      这一段就是一个循环调用 BeanDefinitionCustomizer#customize

    7. 注册bean定义

  • 完整代码如下

    private void doRegisterBean(Class beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier supplier, @Nullable BeanDefinitionCustomizer[] customizers) { // 带有注解的泛型bean定义 AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); // 和条件注解相关的函数 if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; }

    // 设置实例提供者 abd.setInstanceSupplier(supplier); // 解析 注解的 beanDefinition 的作用域元数据 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); // 设置 作用域元数据 abd.setScope(scopeMetadata.getScopeName()); // beanName 处理 String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

    // 通用注解的处理 AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); if (qualifiers != null) { for (Class<? extends Annotation> qualifier : qualifiers) { if (Primary.class == qualifier) { abd.setPrimary(true); } else if (Lazy.class == qualifier) { abd.setLazyInit(true); } else { abd.addQualifier(new AutowireCandidateQualifier(qualifier)); } } } // 自定义的beanDefinition处理 if (customizers != null) { for (BeanDefinitionCustomizer customizer : customizers) { customizer.customize(abd); } }

    // 创建 beanDefinition Holder 后进行注册 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); // 应用作用域代理 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); }

  • 关于 refresh 的分析各位可以在这个项目中找到👉Spring-Analysis

  • 这是我们关于参数是多个class的构造函数分析, 在AnnotationConfigApplicationContext还提供了字符串形式的扫描.这也是后续SpringBoot中关于扫描的核心. 下面我们来看看扫描的方法

  • 构造函数如下

    public AnnotationConfigApplicationContext(String... basePackages) { this(); scan(basePackages); refresh(); }

  • 真正关注的方法应该时 scan

    @Override public void scan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); this.scanner.scan(basePackages); }

  • scan 方法依靠ClassPathBeanDefinitionScanner类所提供的scan方法, 找到了目标方法下面开始对其进行分析。

在 scan 方法中的执行流程如下

  1. 获取未进行注册前的bean数量
  2. 扫描包路径,进行注册
  3. 注册 注解的配置处理器
  4. 得到注册的bean数量

在这段流程中核心方法是第二步(doScan) ,先回顾一下scan方法的实现代码

public int scan(String... basePackages) {
   // 在执行扫描方法前beanDefinition的数量
   int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

   // 真正的扫描方法
   doScan(basePackages);

   // 是否需要注册 注解的配置处理器
   // Register annotation config processors, if necessary.
   if (this.includeAnnotationConfig) {
      // 注册注解后置处理器
      AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
   }

   //  当前 BeanDefinition 数量 - 历史 B
   return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
  • 下面将展开doScan方法的分析

处理流程

  1. 循环每个包路径

  2. 在指定包路径中找到可能的组件(处理方法: findCandidateComponents)

    1. 什么是可能的组件?

      在Spring中对可能组件的判断代码如下

      protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
         // 从 注解的bean定义中获取注解元信息
         AnnotationMetadata metadata = beanDefinition.getMetadata();
         // 1. 是否独立
         // 2. 是否可以创建
         // 3. 是否 abstract 修饰
         // 4. 是否有 Lookup 注解
         return (metadata.isIndependent() && (metadata.isConcrete() ||
               (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
      }
      
    2. 扫描方法各位请查看

      addCandidateComponentsFromIndex
      scanCandidateComponents
      
  3. 在得到可能的组件列表后,注意这里可能的组件列表就是 BeanDefinition , 后续的操作就是围绕 BeanDefinition 进行

    1. 设置作用域

    2. beanName处理

    3. bean定义的后置处理

      处理内容如下

      1. 设置默认值, 默认值从BeanDefinitionDefaults中获取
      2. 设置 autowireCandidate
    4. 通用注解处理

    5. beanName和候选对象的匹配检测

      1. beanName 是否存在

      2. 容器中BeanName对应的实例和参数传递的BeanDefinition是否兼容

        Spring 中对于兼容的判断

        protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {
           // 1. 是否是 ScannedGenericBeanDefinition 类型
           // 2. source 是否相同
           // 3. 参数是否相同
           return (!(existingDefinition instanceof ScannedGenericBeanDefinition) ||  // explicitly registered overriding bean
                 (newDefinition.getSource() != null && newDefinition.getSource().equals(existingDefinition.getSource())) ||  // scanned same file twice
                 newDefinition.equals(existingDefinition));  // scanned equivalent class twice
        }
        
      3. 如果通过了匹配检测则加入到容器

  • findCandidateComponents方法中没有展开addCandidateComponentsFromIndexscanCandidateComponents 方法 只是将判断可能组件的方式提了出来, 更多细节各位读者还需要自行查看

  • 下面是Spring中关于doScan的完整代码

    protected Set doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); // bean 定义持有器列表 Set beanDefinitions = new LinkedHashSet<>(); // 循环包路径进行扫描 for (String basePackage : basePackages) { // 搜索可能的组件. 得到 组件的BeanDefinition Set candidates = findCandidateComponents(basePackage); // 循环候选bean定义 for (BeanDefinition candidate : candidates) { // 获取 作用域元数据 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); // 设置作用域 candidate.setScope(scopeMetadata.getScopeName()); // beanName 生成 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); // 类型判断 AbstractBeanDefinition if (candidate instanceof AbstractBeanDefinition) { // bean 定义的后置处理 postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } // 类型判断 AnnotatedBeanDefinition if (candidate instanceof AnnotatedBeanDefinition) { // 通用注解的处理 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // 候选检测 if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); // 作用于属性应用 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); // 注册 bean定义 registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }

  • 到这关于Spring注解模式的启动方式全部分析完成. 感谢各位的阅读

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这