SpringBoot运行源代码分析

简介: SpringBoot运行源代码分析

我们知道SpringBoot是基于“约定优于配置”,也知道可以根据starters自动加载和配置相应的服务,那么SpringBoot底层是怎么实现这些操作呢?这篇文章带大家通过源码分析,了解SpringBoot运行原理。


SpringApplication的拆解

通常创建SpringBoot项目之后,默认的启动代码只有一行,通过默认的配置基本上可以完成大多数的功能,但如果需要对启动流程的扩展,就需要对SpringBoot的启动方法进行拆解。其实这个操作在《SpringBoot基础之banner玩法解析》这篇文章中已经实践过。通过扩展来设置banner相关操作。我们还可以通过其他set操作来设置其他扩展。


@SpringBootApplication
public class SpringLearnApplication {
  public static void main(String[] args) {
    SpringApplication app = new SpringApplication(SpringLearnApplication.class);
    app.setXXX(...);// 自定义扩展
    app.run(args);
  }
}

SpringBoot实例化

先来看看SpringBoot实例化时都做了些什么:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 1.判断应用的类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 2.加载扫描到的Initializer
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    // 3.设置**ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 4.推断并设置main方法的定义类
    this.mainApplicationClass = deduceMainApplicationClass();
  }

通过代码中的注释,可以了解到SpringApplication初始化可分为四部:


判断应用的类型;

加载扫描到的Initializer;

加载扫描到的Listener;

推断并设置main方法的定义类。

下面针对四个步骤再看一下相应的源代码实现。


判断应用的类型

此功能在枚举类WebApplicationType中实现,根据INDICATOR_CLASSES的类型来判断当前web应用是什么类型的,分别有:REACTIVE(reactive web应用)、SERVLET(基于servlet的web应用)、NONE(不启动web应用)。


static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
        && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
        && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
        return WebApplicationType.NONE;
      }
    }
    return WebApplicationType.SERVLET;
  }

加载扫描到的Initializer

使用SpringFactoriesLoader扫描加载classpath下的META-INF/spring.factories文件中所有可用的Initializer。

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;
                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

其中spring.factories默认在spring-boot-autoconfigure包下,文件的内容格式为:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
# 省略其他的

加载扫描到的ApplicationListener

同样是使用SpringFactoriesLoader扫描加载classpath下的META-INF/spring.factories文件中所有可用的Listener。对应文件类的Listener为:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

推断并设置main方法的定义类

private Class<?> deduceMainApplicationClass() {
    try {
      StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
      for (StackTraceElement stackTraceElement : stackTrace) {
        if ("main".equals(stackTraceElement.getMethodName())) {
          return Class.forName(stackTraceElement.getClassName());
        }
      }
    }
    catch (ClassNotFoundException ex) {
      // Swallow and continue
    }
    return null;
  }

run方法的基本操作

当实例化对象之后,会调用run方法。下面通过源代码来了解一下run方法都做了些什么。

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 创建SpringApplicationRunListener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 遍历并调用SpringApplicationRunListener的starting方法
    listeners.starting();
    try {
        // 创建参数配置
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
          args);
      // 环境参数准备
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
          applicationArguments);
      configureIgnoreBeanInfo(environment);
      // 打印banner
      Banner printedBanner = printBanner(environment);
      // 创建ApplicationContext
      context = createApplicationContext();
      // 获得SpringFactories实例
      exceptionReporters = getSpringFactoriesInstances(
          SpringBootExceptionReporter.class,
          new Class[] { ConfigurableApplicationContext.class }, context);
      // 设置Environment,加载相关配置
      prepareContext(context, environment, listeners, applicationArguments,
          printedBanner);
      // 刷新ApplicationContext
      refreshContext(context);
      // 刷新context之后调用此方法
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
        new StartupInfoLogger(this.mainApplicationClass)
            .logStarted(getApplicationLog(), stopWatch);
      }
      // 刷新context,启动application
      listeners.started(context);
      // 启动程序
      callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
    }
    try {
        // 发出running消息,告知程序已启动
      listeners.running(context);
    }
    catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
    }
    return context;
  }

如果查看当前自动配置

如果想查看当前启动了哪些自动配置有以下几种方式。


(1)运行jar时增加–debug参数。


java -jar xx.jar --debug


(2)在application.properties进行配置。


debug=true


(3)在IDE运行程序中添加VM参数。


-Ddebug


控制台会输出对应日志。已启动的自动配置日志如下:


Positive matches:
-----------------
   CodecsAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition)
   CodecsAutoConfiguration.JacksonCodecConfiguration matched:
      - @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)

未启动的自动配置日志为:

Negative matches:
-----------------
   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
   AopAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)

小结

本篇文章我们简单介绍了springboot启动时都做了些什么,后面我们会逐步讲到整个的运作原理。

目录
相关文章
|
6月前
|
Java Spring
Spring boot 运行服务jar外配置配置文件方式总结
Spring boot 运行服务jar外配置配置文件方式总结
995 0
|
4天前
|
Java 应用服务中间件 API
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
本文介绍了 Spring Boot 的核心概念和使用场景,并通过一个实战项目演示了如何构建一个简单的 RESTful API。
22 5
|
8天前
|
负载均衡 IDE Java
SpringBoot整合XXL-JOB【04】- 以GLUE模式运行与执行器负载均衡策略
在本节中,我们将介绍XXL-JOB的GLUE模式和集群模式下的路由策略。GLUE模式允许直接在线上改造方法为定时任务,无需重新部署。通过一个测试方法,展示了如何在调度中心配置并使用GLUE模式执行定时任务。接着,我们探讨了多实例环境下的负载均衡策略,确保任务不会重复执行,并可通过修改路由策略(如轮训)实现任务在多个实例间的均衡分配。最后,总结了GLUE模式和负载均衡策略的应用,帮助读者更深入理解XXL-JOB的使用。
24 9
|
29天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
5月前
|
Java 应用服务中间件 Spring
为什么SpringBoot的 jar 可以直接运行?
SpringBoot的 jar 可以直接运行的原因
482 2
|
2月前
|
Dubbo Java 应用服务中间件
深入探讨了“dubbo+nacos+springboot3的native打包成功后运行出现异常”的原因及解决方案
本文深入探讨了“dubbo+nacos+springboot3的native打包成功后运行出现异常”的原因及解决方案。通过检查GraalVM版本兼容性、配置反射列表、使用代理类、检查配置文件、禁用不支持的功能、查看日志文件、使用GraalVM诊断工具和调整GraalVM配置等步骤,帮助开发者快速定位并解决问题,确保服务的正常运行。
72 1
|
2月前
|
安全 Java 应用服务中间件
如何将Spring Boot应用程序运行到自定义端口
如何将Spring Boot应用程序运行到自定义端口
88 0
|
3月前
|
Java BI API
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
这篇文章介绍了如何在Spring Boot项目中整合iTextPDF库来导出PDF文件,包括写入大文本和HTML代码,并分析了几种常用的Java PDF导出工具。
819 0
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
|
3月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
297 2
|
5月前
|
网络协议 Java 物联网
MQTT(EMQX) - SpringBoot 整合MQTT 连接池 Demo - 附源代码 + 在线客服聊天架构图
MQTT(EMQX) - SpringBoot 整合MQTT 连接池 Demo - 附源代码 + 在线客服聊天架构图
1171 3