SpringApplication.run(Main.class, args);//这是springboot的主入口

当我们debug进去,发现在这里主要是创建了一个SpringApplication对象,并启动了run(args)方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}

接下来我会把创建SpringApplication和run()这两件事,分别给你讲清楚

1、创建SpringApplication

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Banner.Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    //从这往上,都是固定设置。往下就是,动态设置。我主要讲动态设置部分
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();//判断并设置应用类型
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));//加载所有BootstrapRegistryInitializer 实现类
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));//加载所有 ApplicationContextInitializer 实现类
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));//加载所有 ApplicationListener 实现类
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

1.1 this.webApplicationType = WebApplicationType.deduceFromClasspath();

step into 进去,在这里会推断应用类型

ClassUtils.isPresent(String,ClassLoader)是SpringBoot提供的方法,可以判断判断某个类是否存在于当前 classpath 中。

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
        //上面判断的三个类中,如果只引入了DispatcherHandler,就判断为响应式应用,并返回
        return REACTIVE;
    } else {
        for(String className : SERVLET_INDICATOR_CLASSES) {
            //判断SERVLET_INDICATOR_CLASSES包含的两个类,是否都存在
            //都不存在,为web类型。反之,为普通类型。
            if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                return NONE;
            }
        }

        return SERVLET;
    }
}

1.2 this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

step into进入,可以发现,做了两件事SpringFactoriesLoader.forDefaultResourceLocation(this.getClassLoader())和load(type, argumentResolver)

private <T> List<T> getSpringFactoriesInstances(Class<T> type, SpringFactoriesLoader.ArgumentResolver argumentResolver) {
    return SpringFactoriesLoader.forDefaultResourceLocation(this.getClassLoader()).load(type, argumentResolver);
}

1.2.1 SpringFactoriesLoader.forDefaultResourceLocation(this.getClassLoader())

  1. 当本方法第一次被调用时,由于SpringFactoriesLoader.cache中无数据
  2. 所以会创建新的SpringFactoriesLoader对象,并放入SpringFactoriesLoader.cache中
  3. 调用loadFactoriesResource(resourceClassLoader, resourceLocation)做参传入
  4. loadFactoriesResource(resourceClassLoader, resourceLocation)会加载所有META-INF目录下spring.factories保存的配置信息
public static SpringFactoriesLoader forResourceLocation(String resourceLocation, @Nullable ClassLoader classLoader) {
    //resourceLocation的值为META-INF/spring.factories
    Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");
    ClassLoader resourceClassLoader = classLoader != null ? classLoader : SpringFactoriesLoader.class.getClassLoader();
    Map<String, SpringFactoriesLoader> loaders = (Map)cache.computeIfAbsent(resourceClassLoader, (key) -> new ConcurrentReferenceHashMap());
    return (SpringFactoriesLoader)loaders.computeIfAbsent(resourceLocation, (key) -> new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)));
    //当本方法第一次被调用时,由于SpringFactoriesLoader.cache中无数据
    //所以会创建新的SpringFactoriesLoader对象,并放入SpringFactoriesLoader.cache中
    //调用loadFactoriesResource(resourceClassLoader, resourceLocation)做参传入
    //loadFactoriesResource(resourceClassLoader, resourceLocation)会加载所有META-INF目录下spring.factories保存的配置信息
}

1.2.2 load(type, argumentResolver)

本方法主要是根据传入类型,再依据本类factories变量中存储的信息,实例化相应的类。

public <T> List<T> load(Class<T> factoryType, @Nullable SpringFactoriesLoader.ArgumentResolver argumentResolver, @Nullable SpringFactoriesLoader.FailureHandler failureHandler) {
    Assert.notNull(factoryType, "'factoryType' must not be null");
    List<String> implementationNames = this.loadFactoryNames(factoryType);
    //this.loadFactoryNames(factoryType)会将factoryType的实现类的路径都获取出来
    logger.trace(LogMessage.format("Loaded [%s] names: %s", factoryType.getName(), implementationNames));
    List<T> result = new ArrayList(implementationNames.size());
    SpringFactoriesLoader.FailureHandler failureHandlerToUse = failureHandler != null ? failureHandler : THROWING_FAILURE_HANDLER;

    for(String implementationName : implementationNames) {
        T factory = (T)this.instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse);
        //这里会根据类路径和类型来实例化对象
        if (factory != null) {
            result.add(factory);
        }
    }

    AnnotationAwareOrderComparator.sort(result);
    return result;
}

2、run(args)方法

由于run方法过长,为了方便观看,这里将catch内的操作去除了,不影响启动流程

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();//记录启动开始时间(纳秒精度)
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();//创建引导上下文,用于在应用上下文创建之前共享数据
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();//配置java.awt.headless属性,用于无头环境(如服务器)
    SpringApplicationRunListeners listeners = this.getRunListeners(args);//获取 Spring Boot 运行监听器,用于广播启动事件
    listeners.starting(bootstrapContext, this.mainApplicationClass);//触发启动开始事件

    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//封装命令行参数为ApplicationArguments对象
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);//准备并配置Environment环境对象
        Banner printedBanner = this.printBanner(environment);//打印控制台显示的springboot图标
        context = this.createApplicationContext();//创建应用上下文
        context.setApplicationStartup(this.applicationStartup);//设置应用启动性能追踪工具
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);//刷新上下文:加载 bean 定义、实例化 singleton bean、启动容器
        this.refreshContext(context);//刷新后处理:执行回调、输出日志等,web应用还要启动tomcat
        this.afterRefresh(context, applicationArguments);//刷新后处理,但此方法并无操作
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);//触发启动开始事件
        if (this.logStartupInfo) {//如果启用启动日志,则记录启动信息(版本、启动时间等)
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
        }

        listeners.started(context, timeTakenToStartup);//触发启动完成事件
        this.callRunners(context, applicationArguments);//调用所有CommandLineRunner和ApplicationRunner扩展点
    } catch (Throwable ex) {
    }

    try {
        if (context.isRunning()) {//如果上下文处于运行状态
            Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);//计算就绪总耗时
            listeners.ready(context, timeTakenToReady);//触发就绪事件,表示应用已准备好处理请求
        }

        return context;
    } catch (Throwable ex) {
    }
}

3、流程总结

  1. 首先会创建SpringApplication对象,同时会判断应用类型,并加载所有META-INF目录下spring.factories保存的配置信息
  2. 启动run()方法
  3. 记录启动开始时间
  4. 创建引导上下文
  5. 配置无头属性
  6. 获取监听器并触发starting事件
  7. 准备Environment环境
  8. 打印Banner
  9.  创建应用上下文
  10. 准备上下文,注册 bean
  11. 刷新上下文,web应用会启动Tomcat
  12. 刷新后处理
  13. 记录启动日志
  14. 触发started事件
  15. 执行自定义逻辑
  16. 计算就绪总耗时
  17. 触发ready事件,表示应用已准备好处理请求
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐