文章

Spring Boot 整体架构

Spring boot

如何启动Jar包的

Spring boot项目打包之后,就是一个单独的jar包,执行的时候直接运行命令java -jar application.jar,那么如此简洁的启动流程背后是如何做到的呢?

  1. 首先先看一下jar包解压后是什么样子的
    1
    2
    3
    
    drwxr-xr-x@  6 poul  staff   192B  2  4 10:04 BOOT-INF/
    drwxr-xr-x@  5 poul  staff   160B  2  4 10:04 META-INF/
    drwxr-xr-x@  3 poul  staff    96B  2  1  1980 org/
    

    java本身的机制启动一个jar包的时候,可以通过命令的方式执行一个启动类

    1
    
    java -cp myjar.jar com.example.MainClass
    

还有一种方式是直接读取jar包中的配置文件META-INF/MANIFEST.MF,获取启动类信息,我们查看springboot jar包中的配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.3.0
Build-Jdk-Spec: 21
Implementation-Title: spring-boot-test
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.peng.test.springboottest.SpringBootTestApplication
Spring-Boot-Version: 3.2.2
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx

可以看到文件中声明了启动类org.springframework.boot.loader.launch.JarLauncher 此类对应的jar包为

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-loader</artifactId>
</dependency>

这个类就在刚开始我们看到的jar包解压文件中的org/目录,确切的说是上面的整个jar包里面的类。把这个jar包的源码放到项目的pom.xml文件中,把源码下载下来。可以看到这个类的源码还是比较简单的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package org.springframework.boot.loader.launch;

/**
 * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
 * included inside a {@code /BOOT-INF/lib} directory and that application classes are
 * included inside a {@code /BOOT-INF/classes} directory.
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @author Madhura Bhave
 * @author Scott Frederick
 * @since 3.2.0
 */
public class JarLauncher extends ExecutableArchiveLauncher {

	public JarLauncher() throws Exception {
	}

	protected JarLauncher(Archive archive) throws Exception {
		super(archive);
	}

	@Override
	protected boolean isIncludedOnClassPath(Archive.Entry entry) {
		return isLibraryFileOrClassesDirectory(entry);
	}

	@Override
	protected String getEntryPathPrefix() {
		return "BOOT-INF/";
	}

	static boolean isLibraryFileOrClassesDirectory(Archive.Entry entry) {
		String name = entry.name();
		if (entry.isDirectory()) {
			return name.equals("BOOT-INF/classes/");
		}
		return name.startsWith("BOOT-INF/lib/");
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}

}

根据jar包的协议,java -jar 启动的时候,会执行声明的类Main-Class: org.springframework.boot.loader.launch.JarLaunchermain方法,然后,再根据springboot的协议,再执行声明的类Start-Class: com.peng.test.springboottest.SpringBootTestApplicationmain方法,其中目录BOOT-INF/lib/是程序依赖的jar包,目录BOOT-INF/classes/是项目代码

启动流程

  1. SpringApplication.run(Application.class, args);
    1. 创建Startup
    2. 构建SpringApplication
      1. 根据jvm已经加载的类来判断当前应用的类型 WebApplicationType,有REACTIVE,NONE,SERVLET
      2. 扫描加载所有jar包中的META-INF/spring.factories文件中的扩展点 ,SpringFactoriesLoader.load方法,如果加载的类实现了PriorityOrdered并进行排序
        1. BootstrapRegistryInitializer
          1. 具体的排序方式, 实现了PriorityOrdered接口的类排在没有实现PriorityOrdered的类前面,实现了Ordered接口的进行排序,上面提到的两个接口都没实现的排在最后面(并不能保证这部分的顺序)
        2. ApplicationContextInitializer
        3. ApplicationListener
      3. 识别主类,带main方法的类
    3. 执行run方法,参数为args
      1. 创建DefaultBootstrapContext
        1. 执行所有BootstrapRegistryInitializer类的initialize方法
      2. 从所有spring.factories中获取声明的类SpringApplicationRunListener,并传入启动类的String[] args参数
        1. spring-boot包中的类org.springframework.boot.context.event.EventPublishingRunListenerorder为0
      3. 触发SpringApplicationRunListener.starting方法
      4. 准备环境, 生成类ConfigurableEnvironment
        1. spring.factories文件中寻找ApplicationContextFactory,创建ApplicationServletEnvironment
      5. 配置环境
        1. 启动命令中的启动参数,加载到ConfigurableEnvironment的第一个PropertySource
        2. 第一个PropertySource转换成environment
        3. 触发SpringApplicationRunListener.environmentPrepared方法
        4. 移动defaultPropertiesMutablePropertySources的最后面
      6. 打印banner
      7. 创建ConfigurableApplicationContext,这个应该就是spring的bean容器
        1. 实例化类AnnotationConfigServletWebServerApplicationContext
          1. 创建对象AnnotatedBeanDefinitionReader
            1. AnnotationConfigUtils.registerAnnotationConfigProcessors方法给spring容器中注入5个bean,分别为
              1. ConfigurationClassPostProcessor
              2. AutowiredAnnotationBeanPostProcessor
              3. CommonAnnotationBeanPostProcessor
              4. EventListenerMethodProcessor
              5. DefaultEventListenerFactory
          2. 创建对象ClassPathBeanDefinitionScanner
      8. AnnotationConfigServletWebServerApplicationContext 设置 Startup
      9. prepareContext
        1. AnnotationConfigServletWebServerApplicationContext设置ConfigurableEnvironment
        2. postProcessApplicationContext
          1. AnnotationConfigServletWebServerApplicationContext设置Conversion
        3. 触发所有ApplicationContextInitializerinitialize方法
        4. 触发SpringApplicationRunListener.contextPrepared方法
        5. 启动类注入到spring容器中
      10. refreshContext 这个方法是大头,最最最重要的方法
        1. 调用方法prepareRefresh 准备环境
        2. 调用方法prepareBeanFactory
        3. 调用方法postProcessBeanFactory
        4. 调用方法invokeBeanFactoryPostProcessors, 重要方法, Invoke factory processors registered as beans in the context. 扫描整个项目把所有生命的bean注入到spring容器中
          1. 执行PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
            1. 按顺序执行BeanDefinitionRegistryPostProcessor的方法postProcessBeanDefinitionRegistry
              1. 最重要的Processor: ConfigurationClassPostProcessor
              2. org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
              3. 由于启动类添加了SpringBootApplicaton注解,解析的时候会从这个类开始,如果注解中没有制定扫描的包路径,则自动使用当前启动类的包路径
              4. org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#process 重要的解析方法,会解析除了本身项目的其它依赖jar包中的类
                1. ImportCandidates类中加载所有jar及项目中的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,并获取文件中的类,这些类都是AutoConfiguration的类,为了下一步执行自动配置。读取文件方式是通过java.lang.ClassLoader#getResources方法定位所有文件的
                2. 触发所有的类AutoConfigurationImportListener,消息事件为AutoConfigurationImportEvent
                  1. 触发方式,org.springframework.core.io.support.SpringFactoriesLoader#loadFactories 通过此方法,获取所有文件META-INF/spring.factories中声明的类AutoConfigurationImportListener并触发
                3. 处理所有的AutoConfigurationorg.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
                4. 将所有的配置类解析成ConfigurationClass
            2. 触发BeanDefinitionRegistry的方法``
        5. 调用方法onRefresh , 初始化一些特殊的bean和特殊的上下文子类
          1. 生成并启动WebServer
      11. afterRefresh

        如何实现的自动装配机制

问题

springboot的代码相当的庞大,怎么才能快速的掌握核心的逻辑呢?? 抓大放小

切分任务大小,使目标更简单的完成,快速获得完成任务的成就感。 比如:

sprinboot中的tomcat是怎么启动的

  1. 关键类
    1. WebServer,代表web服务的抽象接口,相关的实现类有:
      1. JettyWebServer
      2. NettyWebServer
      3. TomcatWebServer, tomcat是我们关注的重点
      4. UndertowWebServer
      5. UndertowServletWebServer
    2. WebServerFactory的继承接口ServletWebServerFactory,生成WebServer类的工厂接口,实现类有:
      1. JettyServletWebServerFactory
      2. TomcatServletWebServerFactory 是我们关注的重点
      3. UndertowServletWebServerFactory
    3. 综上可以看到这里用到的是工厂方法模式(factory method)
  2. 触发启动的流程
    1. 从整体的启动流程里面能看出来,是在refreshContext阶段的onRefresh方法中触发启动的tomcat

随之而来的又一个问题:

SpringBoot是怎么自动装配Bean而省去Xml的

换句话说,springboot是怎么自动注入bean的

  1. 启动类
本文由作者按照 CC BY 4.0 进行授权