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

目录
相关文章
|
26天前
|
JavaScript Java 测试技术
基于springboot+vue.js的民谣网站附带文章和源代码设计说明文档ppt
基于springboot+vue.js的民谣网站附带文章和源代码设计说明文档ppt
31 4
|
11天前
|
Java Linux Spring
在 Linux 系统中将 Spring Boot 应用作为系统服务运行
【6月更文挑战第11天】最近由于一些原因,服务器经常会重启,每次重启后需要手动启动 Spring Boot 的工程,因此我需要将其配置成开启自启动的服务。
171 1
|
26天前
|
JavaScript Java 测试技术
基于springboot+vue.js的汽车维修预约服务系统附带文章和源代码设计说明文档ppt
基于springboot+vue.js的汽车维修预约服务系统附带文章和源代码设计说明文档ppt
25 7
|
26天前
|
JavaScript Java 测试技术
基于springboot+vue.js的基于Web教师个人成果管理系统附带文章和源代码设计说明文档ppt
基于springboot+vue.js的基于Web教师个人成果管理系统附带文章和源代码设计说明文档ppt
26 7
|
26天前
|
JavaScript Java 测试技术
基于springboot+vue.js的疫情期间高校人员管理附带文章和源代码设计说明文档ppt
基于springboot+vue.js的疫情期间高校人员管理附带文章和源代码设计说明文档ppt
24 6
|
26天前
|
JavaScript Java 测试技术
基于springboot+vue.js的校园服务平台附带文章和源代码设计说明文档ppt
基于springboot+vue.js的校园服务平台附带文章和源代码设计说明文档ppt
22 6
|
26天前
|
JavaScript Java 测试技术
基于springboot+vue.js的附绿城郑州爱心公益网站带文章和源代码设计说明文档ppt
基于springboot+vue.js的附绿城郑州爱心公益网站带文章和源代码设计说明文档ppt
29 6
|
1天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue的电商数据分析的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue的电商数据分析的详细设计和实现(源码+lw+部署文档+讲解等)
6 0
|
26天前
|
JavaScript Java 测试技术
基于springboot+vue.js的美妆购物网站附带文章和源代码设计说明文档ppt
基于springboot+vue.js的美妆购物网站附带文章和源代码设计说明文档ppt
39 5
|
26天前
|
JavaScript Java 测试技术
基于springboot+vue.js的社区物业管理系统附带文章和源代码设计说明文档ppt
基于springboot+vue.js的社区物业管理系统附带文章和源代码设计说明文档ppt
32 4