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启动时都做了些什么,后面我们会逐步讲到整个的运作原理。

目录
相关文章
|
4月前
|
Java Spring
Spring boot 运行服务jar外配置配置文件方式总结
Spring boot 运行服务jar外配置配置文件方式总结
943 0
|
3月前
|
Java 应用服务中间件 Spring
为什么SpringBoot的 jar 可以直接运行?
SpringBoot的 jar 可以直接运行的原因
388 2
|
15天前
|
Dubbo Java 应用服务中间件
深入探讨了“dubbo+nacos+springboot3的native打包成功后运行出现异常”的原因及解决方案
本文深入探讨了“dubbo+nacos+springboot3的native打包成功后运行出现异常”的原因及解决方案。通过检查GraalVM版本兼容性、配置反射列表、使用代理类、检查配置文件、禁用不支持的功能、查看日志文件、使用GraalVM诊断工具和调整GraalVM配置等步骤,帮助开发者快速定位并解决问题,确保服务的正常运行。
30 1
|
1月前
|
Java BI API
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
这篇文章介绍了如何在Spring Boot项目中整合iTextPDF库来导出PDF文件,包括写入大文本和HTML代码,并分析了几种常用的Java PDF导出工具。
439 0
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
|
1月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
179 2
|
3月前
|
网络协议 Java 物联网
MQTT(EMQX) - SpringBoot 整合MQTT 连接池 Demo - 附源代码 + 在线客服聊天架构图
MQTT(EMQX) - SpringBoot 整合MQTT 连接池 Demo - 附源代码 + 在线客服聊天架构图
807 2
|
3月前
|
SQL 前端开发 Java
在IDEA中使用Maven将SpringBoot项目打成jar包、同时运行打成的jar包(前后端项目分离)
这篇文章介绍了如何在IntelliJ IDEA中使用Maven将Spring Boot项目打包成可运行的jar包,并提供了运行jar包的方法。同时,还讨论了如何解决jar包冲突问题,并提供了在IDEA中同时启动Vue前端项目和Spring Boot后端项目的步骤。
在IDEA中使用Maven将SpringBoot项目打成jar包、同时运行打成的jar包(前后端项目分离)
|
4月前
|
Java Spring 容器
Spring Boot 启动源码解析结合Spring Bean生命周期分析
Spring Boot 启动源码解析结合Spring Bean生命周期分析
104 11
|
4月前
|
Java 持续交付 Maven
Spring Boot程序的打包与运行:构建高效部署流程
构建高效的Spring Boot部署流程对于保障应用的快速、稳定上线至关重要。通过采用上述策略,您可以确保部署过程的自动化、可靠性和高效性,从而将专注点放在开发上面。无论是通过Maven的生命周期命令进行打包,还是通过容器技术对部署过程进行优化,选择正确的工具与实践是成功实现这一目标的关键。
201 2
|
3月前
|
Dubbo Java Nacos
【实战攻略】破解Dubbo+Nacos+Spring Boot 3 Native打包后运行异常的终极秘籍——从零开始彻底攻克那些让你头疼不已的技术难题!
【8月更文挑战第15天】Nacos作为微服务注册与配置中心受到欢迎,但使用Dubbo+Nacos+Spring Boot 3进行GraalVM native打包后常遇运行异常。本文剖析此问题及其解决策略:确认GraalVM版本兼容性;配置反射列表以支持必要类和方法;采用静态代理替代动态代理;检查并调整配置文件;禁用不支持的功能;利用日志和GraalVM诊断工具定位问题;根据诊断结果调整GraalVM配置。通过系统排查方法,能有效解决此类问题,确保服务稳定运行。
100 0

热门文章

最新文章

下一篇
无影云桌面