SpringBoot启动原理分析


1. SpringBoot如何通过jar包启动

1.1. java -jar做了什么?

https://docs.oracle.com/javase/tutorial/deployment/jar/run.html

To indicate which class is the application's entry point, you must add a Main-Class header to the JAR file's manifest. The header takes the form:
Main-Class: classname

Java没有提供任何标准的方式来加载嵌套的jar文件(即,它们本身包含在jar中的jar文件)。

Jar包的打包插件及核心方法
Spring Boot项目的pom.xml文件中使用如下插件进行打包:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

执行maven clean package之后,会生成两个文件:

spring-boot-start-demo-1.0-SNAPSHOT.jar
spring-boot-start-demo-1.0-SNAPSHOT.jar.original

spring-boot-maven-plugin默认有5个goals:

repackage、run、start、stop、build-info

在打包的时候默认使用的是repackage。

1.2. jar包目录结构

spring-boot-start-demo-1.0-SNAPSHOT

├── META-INF
│   └── MANIFEST.MF
├── BOOT-INF
│   ├── classes
│   │   └── 应用程序类
│   └── lib
│       └── 第三方依赖jar
└── org
    └── springframework
        └── boot
            └── loader
                └── springboot启动程序

META-INF内容:
在上述目录结构中,MANIFEST.MF记录了相关jar包的基础信息,包括入口程序等:

Manifest-Version: 1.0
Implementation-Title: spring-boot-start-demo
Implementation-Version: 1.0-SNAPSHOT
Start-Class: com.isvmspace.demo.SpringBootDemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.17.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher

Main-Class是org.springframework.boot.loader.JarLauncher ,这个是jar启动的Main函数。
Start-Class是com.isvmspace.demo.SpringBootDemoApplication,这个是我们应用自己的Main函数。

1.3. JarLauncher源码解析

引入JarLauncher的依赖,查看源码

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-loader</artifactId>
</dependency>

JarLauncher的依赖关系:

class JarLauncher extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher extends Launcher

JarLauncher的源码分析

public class JarLauncher extends ExecutableArchiveLauncher {
    public JarLauncher() {}
    public static void main(String[] args) throws Exception {
        // 新建了JarLauncher并调用父类Launcher中的launch方法启动程序
        new JarLauncher().launch(args);
    }
}

在创建JarLauncher时,父类ExecutableArchiveLauncher找到自己所在的jar,并创建archive。

public abstract class ExecutableArchiveLauncher extends Launcher {
    private final Archive archive;
    public ExecutableArchiveLauncher() {
        try {
            // 找到自己所在的jar,并调用父类Launcher方法创建Archive
            this.archive = createArchive();
        }
        catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }
}

Launcher的核心执行方法launch

public abstract class Launcher {
    /**
     * Launch the application. This method is the initial entry point that should be
     * called by a subclass {@code public static void main(String[] args)} method.
     */
    protected void launch(String[] args) throws Exception {
        JarFile.registerUrlProtocolHandler();
        // 获取archives,并且得ClassLoader
        ClassLoader classLoader = createClassLoader(getClassPathArchives());
        // 获取启动类,并且通过反射运行主类的main()
        launch(args, getMainClass(), classLoader);
    }
}

2. SpringBoot如何启动Spring容器

2.1. 自定义springboot应用启动类

public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}

2.2. 创建SpringApplication并启动

org.springframework.boot.SpringApplication#run

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}
2.2.1. SpringApplication构造函数分析
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   // 将启动类放入primarySources 
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   // 根据classpath 下的类,推算当前web应用类型(webFlux, servlet)
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   // 去spring.factories 中去获取所有key:org.springframework.context.ApplicationContextInitializer
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   //去spring.factories 中去获取所有key: org.springframework.context.ApplicationListener
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   // 根据main方法推算出mainApplicationClass 
   this.mainApplicationClass = deduceMainApplicationClass();
}
2.2.2. SpringApplication#run核心源码解析

org.springframework.boot.SpringApplication#run(java.lang.String...)

    /**
     * 运行Spring application, 创建并且更新ApplicationContext.
     * @param args 命令行参数
     * @return ApplicationContext
     */
public ConfigurableApplicationContext run(String... args) {
    // 用来记录当前springboot启动耗时
   StopWatch stopWatch = new StopWatch();
   // 记录启动开始时间
   stopWatch.start();
   // spring上下文的接口, 接收ApplicationContext实现
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   configureHeadlessProperty();
   // 通过spring.factroies中读取了SpringApplicationRunListener 的组件,用来发布事件或者运行监听器
   SpringApplicationRunListeners listeners = getRunListeners(args);
   // 触发ApplicationStartingEvent的监听者执行onApplicationEvent
   listeners.starting();
   try {
       // 根据命令行参数 实例化一个ApplicationArguments 
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 预初始化环境: 读取环境变量,读取配置文件信息(基于监听器)
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      // 忽略beaninfo的bean
      configureIgnoreBeanInfo(environment);
      // 打印Banner
      Banner printedBanner = printBanner(environment);
      // 根据webApplicationType创建Spring上下文  
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      // 预初始化spring上下文
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 加载spring ioc 容器   **相当重要   由于是使用AnnotationConfigServletWebServerApplicationContext 启动的spring容器所以springboot对它做了扩展:
      //  加载自动配置类:invokeBeanFactoryPostProcessors ,  创建servlet容器onRefresh
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      // 发布ApplicationStartedEvent事件
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      // 发布ApplicationReadyEvent事件
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

org.springframework.boot.SpringApplication#prepareEnvironment

    /**
     * 预初始化环境
     *
     * @param listeners            监听器
     * @param applicationArguments 命令行参数
     * @return 环境信息
     */
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // 根据webApplicationType 创建Environment  创建就会读取: java环境变量和系统环境变量
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   // 将命令行参数读取环境变量中
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   // 将configurationProperties的配置信息 放在第一位
   ConfigurationPropertySources.attach(environment);
   // 发布ApplicationEnvironmentPreparedEvent的监听器
   // ConfigFileApplicationListener读取了全局配置文件
   listeners.environmentPrepared(environment);
   // 将所有spring.main 开头的配置信息绑定SpringApplication
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   //更新PropertySources
   ConfigurationPropertySources.attach(environment);
   return environment;
}

org.springframework.boot.SpringApplication#prepareContext

    /**
     * 预初始化spring上下文
     *
     * @param context              上下文
     * @param environment          环境
     * @param listeners            监听器
     * @param applicationArguments 命令行参数
     * @param printedBanner        banner
     */
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   // 拿到之前读取到所有ApplicationContextInitializer的组件, 循环调用initialize方法
   applyInitializers(context);
   // 发布了ApplicationContextInitializedEvent
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // 获取当前spring上下文beanFactory (负责创建bean)
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   // 在SpringBoot 在这里设置了不允许覆盖, 当出现2个重名的bean 会抛出异常
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   // 在Spring下 如果出现2个重名的bean, 则后读取到的会覆盖前面
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   // Load the sources
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   // 读取主启动类
   load(context, sources.toArray(new Object[0]));
   // 读取完配置类后发送ApplicationPreparedEvent
   listeners.contextLoaded(context);
}

声明:微默网|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - SpringBoot启动原理分析


不以物喜,不以己悲! 不忘初心,方得始终!