SpringBoot运行源代码分析

简介: SpringBoot运行源代码分析

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


SpringApplication的拆解


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










@SpringBootApplicationpublic 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 Configureorg.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为:












# Initializersorg.springframework.context.ApplicationContextInitializer=\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener# Application Listenersorg.springframework.context.ApplicationListener=\org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listenersorg.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