从*Application.java解读SpringBoot

时间:2022-07-28
本文章向大家介绍从*Application.java解读SpringBoot,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

之前的经历让我发现了,其实很多东西还需要学习。

不仅仅是我所谓的知识体系。

接下来我会慢慢的重塑自己的知识体系,并一边弥补自己的面试缺憾。



由于是解读SpringBoot,为了去除其他的干扰,从Spring的官网生成(https://start.spring.io/)了一个最简单的SpringBoot项目。

我们来看看*Application.java文件的代码:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

从上到下解读,首先是包名目录,然后导入的指令。

从line6解读,这里是springboot专有的注解,我们点进去看看。

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.repository.Repository;

@Target(ElementType.TYPE) // Java的元注解,说明这是一个接口或类或枚举
@Retention(RetentionPolicy.RUNTIME) // 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Documented // Documented注解表明这个注释是由 javadoc记录的,在默认情况下也有类似的记录工具。如果一个类型声明被注释了文档化,它的注释成为公共API的一部分
@Inherited // 使用此注解声明出来的自定义注解,在使用此自定义注解时,如果注解在类上面时,自动继承此注解,否则的子会话,子类不会继承此注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication { // @interface 并不是说明这是一个接口

  @AliasFor(annotation = EnableAutoConfiguration.class)
  Class<?>[] exclude() default {};

  @AliasFor(annotation = EnableAutoConfiguration.class)
  String[] excludeName() default {};

  @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  String[] scanBasePackages() default {};

  @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
  Class<?>[] scanBasePackageClasses() default {};

  @AliasFor(annotation = Configuration.class)
  boolean proxyBeanMethods() default true;
}

为了节省篇幅,

@Target(ElementType.TYPE)--声明类型

@Retention(RetentionPolicy.RUNTIME)--注解进入jvm仍然存在

@Documented--javadoc,公共api说明

@Inherited--让使用这个注解的类继承这个注解的所有注解(敲黑板)

的使用已经在代码中注释。

接下来重点讲解一下这个类最主要的三个注解:

  1. @SpringBootConfiguration 继承@Configuration,功能一致,标注当前类是配置类,并会将类内声明的标注了@Bean的方法加载到IOC容器中,实例名为方法名。
  2. @EnableAutoConfiguration 借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器
  3. @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

@ComponentScan是自动扫描并加载符合条件的组件或者bean,最终将这些bean放入IOC容器中,我们可以通过basePackages属性来定义扫描范围,如果不指定默认是从注解所在类的package开始扫描。

excludeFilters--排除过滤规则

@Filter(type = FilterType.CUSTOM--按照自定义来进行过滤和筛选,后面的classes就是自定义的匹配方法

下面的方法定义

@AliasFor标识别名的意思。

exclude —— 根据Class对象来排除特定的类,不加入到Spring容器中,传入Class数组。 excludeName —— 根据类名来排除特定的类,不加入到Spring容器中,传入参数是类的全限定名数组。 scanBasePackages —— 指定自动扫描的包,参数是字符串数组。 scanBasePackageClasses —— 指定自动扫描的包,参数是Class对象数组。

proxyBeanMethods默认是true,如果为false配置类就不会被代理,不代理可以减少springboot的启动时间。

回到第一段代码line9,点进去看源码

// SpringApplication.run(DemoApplication.class, args); 调用下面的代码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}
// 上面的代码调用下面这段重载代码
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);

// 也就是 new SpringApplication(Class数组).run(输入字符串),其中class数组只有一个内容DemoApplication.class

我们先看创建方法,解释完再看run方法就会相对轻松一些

@SuppressWarnings({ "unchecked", "rawtypes" })
  public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 将DemoApplication.class赋值给resourceLoader
    this.resourceLoader = resourceLoader; 
    // 用断言判断Class是否为空,这里...是多参数兼容,用数组接收
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 将class数组转换成List然后全部放进LinkedHashSet中,初始化并去重
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); 
    // 查看web应用类型,如果能够实例化DispatcherHandler,则返回REACTIVE,如果无法获得相应的类加载器,则返回NONE,否则返回SERVLET
    this.webApplicationType = WebApplicationType.deduceFromClasspath(); 
    // 设置应用上线文初始化器,从"META-INF/spring.factories"读取ApplicationContextInitializer类的实例名称集合并去重,并进行set去重。
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); 
    // 设置监听器,从"META-INF/spring.factories"读取ApplicationListener类的实例名称集合并去重,并进行set去重。
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 
    // 获取当前调用线程的栈,遍历获取栈中main方法所在类名,通过类名getClass并赋值给mainApplicationClass
        this.mainApplicationClass = deduceMainApplicationClass();
  }

从上面不难看出,其实只是做了一些初始化的操作,对变量的赋值和启动初始化器和监听器。

接下来进入run方法

    // 创建spring的计时器
    StopWatch stopWatch = new StopWatch();
    // 调用启动计时器,如果已经启动了就会抛异常IllegalStateException,否则启动,并记录当前系统时间纳秒
    stopWatch.start();
    // 初始化应用上下文
    ConfigurableApplicationContext context = null;
    // 初始化异常报告集合
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 设置系统属性java.awt.headless,默认为"true",用于运行headless服务器,进行简单的图像处理
    configureHeadlessProperty();
    // 创建所有spring运行监听器并发布应用启动事件,即获取SpringApplicationRunListener类型实例(EventPublishingRunListener),放入SpringApplicationRunListeners中,然后返回
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 启动返回的所有listener
    listeners.starting();
    try {
      // 初始化默认应用参数类
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 根据运行监听器和参数来准备环境
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      // 如果系统属性里没有spring.beaninfo.ignore,就根据环境属性获取,默认是"true" -- 打开忽略bean
      configureIgnoreBeanInfo(environment);
      // 获取当前输出类型,off/console/log
      Banner printedBanner = printBanner(environment);
      // 创建应用上下文(一个容器)
      context = createApplicationContext();
      // 获取异常报告器,用于报告启动异常
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
          new Class[] { ConfigurableApplicationContext.class }, context);
      // 准备应用上下文,该步骤包含一个非常关键的操作,将启动类注入容器,为后续开启自动化提供基础
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 刷新应用上下文
      refreshContext(context);
      // 刷新后的操作,默认为空
      afterRefresh(context, applicationArguments);
      // 计时器停止计时 
      stopWatch.stop();
      if (this.logStartupInfo) { // this.logStartupInfo默认为true
        // 打印启动日志
        new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      // 应用上下文启动监听事件
      listeners.started(context);
      // 从上下文中获取实现了ApplicationRunner或CommandLineRunner接口的任务并调度--可以用来执行业务初始化,只要实现这两个其中一个接口
      callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
    }

    try {
      // 运行监听器,启动事件
      listeners.running(context);
    }
    catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
    }
    // 返回上下文
    return context;

在这个过程中,有一些源码一直追溯到最底层,如果在这里展开篇幅过长。这里就不作详细介绍,不过一路点下去看底层的代码还是很有趣的,能发现spring的实现功能大多是用的反射。

其中涉及到的一个配置文件读取的路径是spring-boot-autoconfigure-2.2.2.RELEASE.jar中META-INF文件下的spring.factories。

附上启动流程图