Spring Boot - 图1

1 依赖管理

pom文件可知,项目先依赖于spring-boot-starter-parent
image.png
然后spring-boot-starter-parent又依赖于spring-boot-dependencies
image.png
而SpringBoot在spring-boot-dependencies中制定了各个依赖的版本。

另外,Spring Boot官方提供的部分场景依赖启动器,这些依赖启动器适用于不同的场景开发,使用时只需要在pox.xml文件中导入对应的依赖启动器即可。例如spring-boot-starter-webspring-boot-starter-quartzspring-boot-starter-amqp等等。

2 自动配置(启动流程)

  1. 启动入口**@SpringBootApplication**

点进源码可知,**@SpringBootApplication**是由多个注解组合而成

  1. @Target({ElementType.TYPE})//注解的适用范围,Type表示注解可以描述类、接口、注解或枚举
  2. @Retention(RetentionPolicy.RUNTIME)//表示注解的生命周期,Runtime运行时
  3. @Documented //表示注解可以记录在javadoc中
  4. @Inherited //表示可以被子类继承该注解
  5. @SpringBootConfiguration // 标明该类为配置类
  6. @EnableAutoConfiguration // 启动自动配置功能
  7. @ComponentScan( // 包扫描器
  8. excludeFilters = {@Filter(
  9. type = FilterType.CUSTOM,
  10. classes = {TypeExcludeFilter.class}
  11. ), @Filter(
  12. type = FilterType.CUSTOM,
  13. classes = {AutoConfigurationExcludeFilter.class}
  14. )}
  15. )
  16. public @interface SpringBootApplication {
  17. ...
  18. }

从上述源码可以看出,**@SpringBootApplication**注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:**@SpringBootConfiguration****@EnableAutoConfiguration****@ComponentScan**三个核心注解,关于这三个核心注解的相关说明具体如下:

**@EnableAutoConfiguration**

@AutoConfigurationPackage

注册BeanDefinition

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @Import({Registrar.class})
  6. public @interface AutoConfigurationPackage {
  7. String[] basePackages() default {};
  8. Class<?>[] basePackageClasses() default {};
  9. }

从上述源码可以看出,@AutoConfigurationPackage注解的功能是由@Import注解实现的,它是 spring框架的底层注解,它的作用就是给容器中导入某个组件类,例如@Import({Registrar.class}),它就是将Registrar这个组件类导入到容器中,可查看Registrar类中registerBeanDefinitions方法,这个方法就是导入组件类的具体实现 :

  1. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  2. Registrar() {
  3. }
  4. // registerBeanDefinitions
  5. public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  6. AutoConfigurationPackages.register(registry,
  7. (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
  8. }
  9. public Set<Object> determineImports(AnnotationMetadata metadata) {
  10. return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
  11. }
  12. }

从上述源码可以看出,在Registrar类中有一个registerBeanDefinitions()方法,使用Debug模式启动项目,可以看到选中的部分就是com.lagou。也就是说,**@AutoConfigurationPackage**注解的主要作 用就是将主程序类所在包及所有子包下的组件到扫描到spring容器中。 因此在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位 置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫描

@Import({AutoConfigurationImportSelector.class})

自动装配所有符合条件的组件类
向容器中引入**AutoConfigurationImportSelector**组件类,将所有符合条件的@Configuration配置,都加载到当前SpringBoot创建并使用的IoC容器中

AutoConfigurationImportSelector类:

  1. // 这个方法告诉springboot都需要导入那些组件
  2. @Override
  3. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  4. //判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
  5. if (!isEnabled(annotationMetadata)) {
  6. return NO_IMPORTS;
  7. }
  8. //1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
  9. //作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
  10. // SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
  11. // 自动配置的类全名.条件=值
  12. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
  13. AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
  14. return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  15. }

AutoConfigurationMetadataLoader
  • 定义了一个常量PATH :文件中为需要加载的配置类的类路径
  • **spring-autoconfigure-metadata.properties **文件中定义了自动配置的条件
  • 这个文件相当于条件注解:**@ConditionalOnXXXX**

    1. protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
  • 通过loadMetadata方法:

    • 读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
    • 解析urls枚举对象中的信息封装成properties对象并加载
    • 根据封装好的properties对象生成AutoConfigurationMetadata对象返回 给**getAutoConfigurationEntry**方法

image.png

getAutoConfigurationEntry方法
  1. /**
  2. * 获得 AutoConfigurationEntry 对象
  3. *
  4. * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
  5. * of the importing {@link Configuration @Configuration} class.
  6. * @param autoConfigurationMetadata the auto-configuration metadata
  7. * @param annotationMetadata the annotation metadata of the configuration class
  8. * @return the auto-configurations that should be imported
  9. */
  10. protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
  11. // 1. 判断是否开启注解。如未开启,返回空串
  12. if (!isEnabled(annotationMetadata)) {
  13. return EMPTY_ENTRY;
  14. }
  15. // 2. 获得注解的属性
  16. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  17. // 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
  18. // spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
  19. // 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
  20. // 将这些值作为自动配置类导入到容器中,自动配置类就生效了
  21. List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  22. // 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
  23. configurations = removeDuplicates(configurations);
  24. // 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
  25. // 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
  26. //找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
  27. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  28. // 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
  29. checkExcludedClasses(configurations, exclusions);
  30. // 4.2 从 configurations 中,移除所有不希望自动配置的配置类
  31. configurations.removeAll(exclusions);
  32. // 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类
  33. //@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
  34. //@ConditionalOnMissingClass : classpath中不存在该类时起效
  35. //@ConditionalOnBean : DI容器中存在该类型Bean时起效
  36. //@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
  37. //@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
  38. //@ConditionalOnExpression : SpEL表达式结果为true时
  39. //@ConditionalOnProperty : 参数设置或者值一致时起效
  40. //@ConditionalOnResource : 指定的文件存在时起效
  41. //@ConditionalOnJndi : 指定的JNDI存在时起效
  42. //@ConditionalOnJava : 指定的Java版本存在时起效
  43. //@ConditionalOnWebApplication : Web应用环境下起效
  44. //@ConditionalOnNotWebApplication : 非Web应用环境下起效
  45. //总结一下判断是否要加载某个类的两种方式:
  46. //根据spring-autoconfigure-metadata.properties进行判断。
  47. //要判断@Conditional是否满足
  48. // 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
  49. configurations = filter(configurations, autoConfigurationMetadata);
  50. // 6. 将自动配置导入事件通知监听器
  51. //当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
  52. // 并触发fireAutoConfigurationImportEvents事件。
  53. fireAutoConfigurationImportEvents(configurations, exclusions);
  54. // 7. 创建 AutoConfigurationEntry 对象
  55. return new AutoConfigurationEntry(configurations, exclusions);
  56. }
  • 通过getCandidateConfigurations(annotationMetadata, attributes)**spring.factories**获取默认支持的自动配置类名列表
  • 去除重复的类removeDuplicates(configurations)
  • 通过**EnableAutoConfiguration**excludeexcludeName属性进行配置,关闭不想要的类的自动配置
  • filter(configurations, autoConfigurationMetadata);:对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类
  • 将自动配置导入事件通知监听器
  • 创建 AutoConfigurationEntry 对象

    总结

    @SpringBootApplication

    **@SpringBootApplication**是个组合注解,由7个注解组成:
    image.png

Springboot底层实现自动配置的步骤是:

  1. Springboot应用启动;
  2. **@SpringBootConfiguration**起作用;
  3. **@EnableAutoConfiguration**
    1. **@AutoConfigurationPackage**
      1. **@Import({Registrar.class})** 它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中;
    2. **@Import({AutoConfigurationImportSelector.class})**

通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过**selectImports**方法执行的过程中,会使用内部工具类**SpringFactoriesLoader**,查找 classpath上所有jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给 SpringFactory加载器进行一系列的容器创建过程

  1. **ComponentScan** 解具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决定,在扫描过程中由前面介绍的**@AutoConfigurationPackage**注解进行解析,从而得到Spring Boot项目主程序启动类所在包的具体位置

    3 自定义Start

    3.1 自定义过程

    Starter是SpringBoot非常重要的一部分,可以理解为一个可拔插式的插件,正是这些starter使得使用 某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring Boot自动通过classpath路径下的类发现需要的Bean,并织入相应的Bean。
    SpringBoot提供的starter以 spring-boot-starter-xxx 的方式命名的。官方建议自定义的starter使用 xxx-spring-boot-starter 命名规则。

  2. 新建maven jar工程,工程名为zdy-spring-boot-starter,导入依赖:

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-autoconfigure</artifactId>
    5. <version>2.4.2</version>
    6. </dependency>
    7. </dependencies>
  3. 编写javaBean

    1. @EnableConfigurationProperties(SimpleBean.class) // 使@ConfigurationProperties起作用
    2. @ConfigurationProperties(prefix = "simplebean")
    3. public class SimpleBean {
    4. private int id;
    5. private String name;
    6. public int getId() {
    7. return id;
    8. }
    9. public void setId(int id) {
    10. this.id = id;
    11. }
    12. public String getName() {
    13. return name;
    14. }
    15. public void setName(String name) {
    16. this.name = name;
    17. }
    18. @Override
    19. public String toString() {
    20. return "SimpleBean{" +
    21. "id=" + id +
    22. ", name='" + name + '\'' +
    23. '}';
    24. }
    25. }
  4. 编写配置类 ```java @Configuration @ConditionalOnClass //当前类路径classpath下有指定类(SimpleBean.class)的情况,才会进行自动配置 public class MyAutoConfiguration {

    static {

    1. System.out.println("MyAutoConfiguration init ...");

    }

    @Bean public SimpleBean simpleBean() {

    1. return new SimpleBean();

    }

}

  1. 4. resources目录下创建/META-INF/spring.factories
  2. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22245218/1649816604717-1eeff551-d36c-42a9-878c-969762dbe284.png#clientId=u84fa3e58-4831-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=202&id=ua8bf9be1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=222&originWidth=362&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9410&status=done&style=none&taskId=u6bff7330-01f8-43fb-b4e1-79278ed65f6&title=&width=329.0909019580559)
  3. ```properties
  4. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  5. com.mxiaoy.config.MyAutoConfiguration

3.2 使用自定义Starter

  1. 在pom中引用自定义的starter

    1. <dependency>
    2. <groupId>com.mxiaoy</groupId>
    3. <artifactId>mxiaoy-spring-boot-starter</artifactId>
    4. <version>1.0-SNAPSHOT</version>
    5. </dependency>
  2. 在配置文件中定义springboot自动装配的变量

image.png
截图这里对应得就是自定义starter,第 2. 步中得注解:
**@ConfigurationProperties(prefix = "simplebean")**

  1. 注入自动装配的实例即可

image.png
此时就可以打印出配置文件中给出的值。

4 启动过程

SpringBoot的启动过程分为两步:
SpringApplication._run_(xxx.class, args);调用**run**方法然后调用重载的**run**方法;
然后重载的**run**方法如下图所示:

  1. 调用SpringApplication的构造方法;
  2. 实例化之后调用run方法

image.png

4.1 构造方法:SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)

image.png

  1. 项目启动类 SpringbootDemoApplication.class设置为属性存储起来
  2. 设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)

image.png

  1. 设置初始化器(ApplicationContextInitializer.class),

最后会调用这些初始化器(调用getSpringFactoriesInstances(xxx.class)方法)
ApplicationContextInitializer.class的作用:
在执行refresh()方法之前(容器刷新之前),去初始化一些属性。

自定义Initializer的三种方式

image.png

  1. 设置监听器(Listener),调用方法(和3一样,都是调用getSpringFactoriesInstances(xxx.class)方法)
  2. 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类

image.png

4.2 SpringApplication 的 .run(args)

  1. /**
  2. * Run the Spring application, creating and refreshing a new
  3. * {@link ApplicationContext}.
  4. * @param args the application arguments (usually passed from a Java main method)
  5. * @return a running {@link ApplicationContext}
  6. */
  7. public ConfigurableApplicationContext run(String... args) {
  8. // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
  9. StopWatch stopWatch = new StopWatch();
  10. stopWatch.start();
  11. // 初始化应用上下文和异常报告集合
  12. ConfigurableApplicationContext context = null;
  13. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  14. // 配置 headless 属性
  15. configureHeadlessProperty();
  16. // (1)获取并启动监听器
  17. SpringApplicationRunListeners listeners = getRunListeners(args);
  18. listeners.starting();
  19. try {
  20. // 创建 ApplicationArguments 对象 初始化默认应用参数类
  21. // args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
  22. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  23. //(2)项目运行环境Environment的预配置
  24. // 创建并配置当前SpringBoot应用将要使用的Environment
  25. // 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
  26. ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  27. configureIgnoreBeanInfo(environment);
  28. // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
  29. Banner printedBanner = printBanner(environment);
  30. // (3)创建Spring容器
  31. context = createApplicationContext();
  32. // 获得异常报告器 SpringBootExceptionReporter 数组
  33. //这一步的逻辑和实例化初始化器和监听器的一样,
  34. // 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
  35. exceptionReporters = getSpringFactoriesInstances(
  36. SpringBootExceptionReporter.class,
  37. new Class[] { ConfigurableApplicationContext.class }, context);
  38. // (4)Spring容器前置处理
  39. //这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
  40. prepareContext(context, environment, listeners, applicationArguments,
  41. printedBanner);
  42. // (5):刷新容器
  43. refreshContext(context);
  44. // (6):Spring容器后置处理
  45. //扩展接口,设计模式中的模板方法,默认为空实现。
  46. // 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
  47. afterRefresh(context, applicationArguments);
  48. // 停止 StopWatch 统计时长
  49. stopWatch.stop();
  50. // 打印 Spring Boot 启动的时长日志。
  51. if (this.logStartupInfo) {
  52. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  53. }
  54. // (7)发出结束执行的事件通知
  55. listeners.started(context);
  56. // (8):执行Runners
  57. //用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
  58. //Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
  59. //Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
  60. callRunners(context, applicationArguments);
  61. } catch (Throwable ex) {
  62. // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
  63. handleRunFailure(context, ex, exceptionReporters, listeners);
  64. throw new IllegalStateException(ex);
  65. }
  66. // (9)发布应用上下文就绪事件
  67. //表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
  68. // 这样整个Spring Boot项目就正式启动完成了。
  69. try {
  70. listeners.running(context);
  71. } catch (Throwable ex) {
  72. // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
  73. handleRunFailure(context, ex, exceptionReporters, null);
  74. throw new IllegalStateException(ex);
  75. }
  76. //返回容器
  77. return context;
  78. }

准备: 用StopWatch对象记录启动时间;初始化应用上下文和异常报告集合;配置headless属性
主要流程:

  1. 获取并启动监听器**SpringApplicationRunListeners**;初始化默认应用参数,创建**ApplicationArguments**
  2. 项目运行环境Environment的预配置
  3. 创建Spring容器
  4. Spring容器前置处理prepareContext

    1. private void prepareContext(ConfigurableApplicationContext context,
    2. ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
    3. ApplicationArguments applicationArguments, Banner printedBanner) {
    4. //设置容器环境,包括各种变量
    5. context.setEnvironment(environment);
    6. //设置上下文的 bean 生成器和资源加载器
    7. postProcessApplicationContext(context);
    8. //执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
    9. applyInitializers(context);
    10. //触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
    11. listeners.contextPrepared(context);
    12. //记录启动日志
    13. if (this.logStartupInfo) {
    14. logStartupInfo(context.getParent() == null);
    15. logStartupProfileInfo(context);
    16. }
    17. // Add boot specific singleton beans
    18. //注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
    19. ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    20. beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    21. if (printedBanner != null) {
    22. beanFactory.registerSingleton("springBootBanner", printedBanner);
    23. }
    24. if (beanFactory instanceof DefaultListableBeanFactory) {
    25. ((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    26. }
    27. // Load the sources
    28. // 加载所有资源
    29. Set<Object> sources = getAllSources();
    30. Assert.notEmpty(sources, "Sources must not be empty");
    31. //加载我们的启动类,将启动类注入容器,为后续开启自动化配置奠定基础
    32. load(context, sources.toArray(new Object[0]));
    33. //触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
    34. listeners.contextLoaded(context);
    35. //这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等
    36. }
  5. 刷新容器

  6. Spring容器后置处理
  7. 发出执行结束的时间通知
  8. 执行Runners

用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次
Spring Boot提供了**ApplicationRunner****CommandLineRunner**两种服务接口

  1. 发布应用上下文就绪事件