[SpringBoot] SpringApplication.run 执行流程
链接:https://www.zhihu.com/question/21346206/answer/101789659
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
基本原理其实就是通过反射解析类及其类的各种信息,包括构造器、方法及其参数,属性。然后将其封装成bean定义信息类、constructor信息类、method信息类、property信息类,最终放在一个map里,也就是所谓的container,池等等,其实就是个map。。汗。。。。当你写好配置文件,启动项目后,框架会先按照你的配置文件找到那个要scan的包,然后解析包里面的所有类,找到所有含有@bean,@service等注解的类,利用反射解析它们,包括解析构造器,方法,属性等等,然后封装成各种信息类放到一个map里。每当你需要一个bean的时候,框架就会从container找是不是有这个类的定义啊?如果找到则通过构造器new出来(这就是控制反转,不用你new,框架帮你new),再在这个类找是不是有要注入的属性或者方法,比如标有@autowired的属性,如果有则还是到container找对应的解析类,new出对象,并通过之前解析出来的信息类找到setter方法,然后用该方法注入对象(这就是依赖注入)。如果其中有一个类container里没找到,则抛出异常,比如常见的spring无法找到该类定义,无法wire的异常。还有就是嵌套bean则用了一下递归,container会放到servletcontext里面,每次reQuest从servletcontext找这个container即可,不用多次解析类定义。如果bean的scope是singleton,则会重用这个bean不再重新创建,将这个bean放到一个map里,每次用都先从这个map里面找。如果scope是session,则该bean会放到session里面。仅此而已,没必要花更多精力。建议还是多看看底层的知识。
不得不说 SpringBoot 太复杂了,我本来只想研究一下 SpringBoot 最简单的 HelloWorld 程序是如何从 main 方法一步一步跑起来的,但是这却是一个相当深的坑。你可以试着沿着调用栈代码一层一层的深入进去,如果你不打断点,你根本不知道接下来程序会往哪里流动。这个不同于我研究过去的 Go 语言、Python 语言框架,它们通常都非常直接了当,设计上清晰易懂,代码写起来简单,里面的实现同样也很简单。但是 SpringBoot 不是,它的外表轻巧简单,但是它的里面就像一只巨大的怪兽,这只怪兽有千百只脚把自己缠绕在一起,把爱研究源码的读者绕的晕头转向。但是这 Java 编程的世界 SpringBoot 就是老大哥,你却不得不服。即使你的心中有千万头草泥马在奔跑,但是它就是天下第一。如果你是一个学院派的程序员,看到这种现象你会怀疑人生,你不得不接受一个规则 —— 受市场最欢迎的未必就是设计的最好的,里面夹杂着太多其它的非理性因素。
经过了一番痛苦的折磨,我还是把 SpringBoot 的运行原理摸清楚了,这里分享给大家。
Hello World
首先我们看看 SpringBoot 简单的 Hello World 代码,就两个文件 HelloControll.java 和 Application.java,运行 Application.java 就可以跑起来一个简单的 RESTFul Web 服务器了。
1 // HelloController.java 2 package hello; 3 4 import org.springframework.web.bind.annotation.RestController; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 7 @RestController 8 public class HelloController { 9 10 @RequestMapping("/") 11 public String index() { 12 return "Greetings from Spring Boot!"; 13 } 14 15 } 16 17 // Application.java 18 package hello; 19 20 import org.springframework.boot.SpringApplication; 21 import org.springframework.boot.autoconfigure.SpringBootApplication; 22 23 @SpringBootApplication 24 public class Application { 25 26 public static void main(String[] args) { 27 SpringApplication.run(Application.class, args); 28 } 29 30 }
当我打开浏览器看到服务器正常地将输出呈现在浏览器的时候,我不禁大呼 —— SpringBoot 真他妈太简单了。
但是问题来了,在 Application 的 main 方法里我压根没有任何地方引用 HelloController 类,那么它的代码又是如何被服务器调用起来的呢?这就需要深入到 SpringApplication.run() 方法中看个究竟了。不过即使不看代码,我们也很容易有这样的猜想,SpringBoot 肯定是在某个地方扫描了当前的 package,将带有 RestController 注解的类作为 MVC 层的 Controller 自动注册进了 Tomcat Server。
还有一个让人不爽的地方是 SpringBoot 启动太慢了,一个简单的 Hello World 启动居然还需要长达 5 秒,要是再复杂一些的项目这样龟漫的启动速度那真是不好想象了。
再抱怨一下,这个简单的 HelloWorld 虽然 pom 里只配置了一个 maven 依赖,但是传递下去,它一共依赖了 36 个 jar 包,其中以 spring 开头的 jar 包有 15 个。说这是依赖地狱真一点不为过。
批评到这里就差不多了,下面就要正是进入主题了,看看 SpringBoot 的 main 方法到底是如何跑起来的。
SpringBoot 的堆栈
了解 SpringBoot 运行的最简单的方法就是看它的调用堆栈,下面这个启动调用堆栈还不是太深,我没什么可抱怨的。
public class TomcatServer {
@Override
public void start() throws WebServerException {
...
}
}
接下来再看看运行时堆栈,看看一个 HTTP 请求的调用栈有多深。不看不知道一看吓了一大跳!
我通过将 IDE 窗口全屏化,并将其它的控制台窗口源码窗口统统最小化,总算勉强一个屏幕装下了整个调用堆栈。
不过转念一想,这也不怪 SpringBoot,绝大多数都是 Tomcat 的调用堆栈,跟 SpringBoot 相关的只有不到 10 层。
探索 ClassLoader
SpringBoot 还有一个特色的地方在于打包时它使用了 FatJar 技术将所有的依赖 jar 包一起放进了最终的 jar 包中的 BOOT-INF/lib 目录中,当前项目的 class 被统一放到了 BOOT-INF/classes 目录中。
1 <build> 2 <plugins> 3 <plugin> 4 <groupId>org.springframework.boot</groupId> 5 <artifactId>spring-boot-maven-plugin</artifactId> 6 </plugin> 7 </plugins> 8 </build>
这不同于我们平时经常使用的 maven shade 插件,将所有的依赖 jar 包中的 class 文件解包出来后再密密麻麻的塞进统一的 jar 包中。下面我们将 springboot 打包的 jar 包解压出来看看它的目录结构。
├── BOOT-INF │ ├── classes │ │ └── hello │ └── lib │ ├── classmate-1.3.4.jar │ ├── hibernate-validator-6.0.12.Final.jar │ ├── jackson-annotations-2.9.0.jar │ ├── jackson-core-2.9.6.jar │ ├── jackson-databind-2.9.6.jar │ ├── jackson-datatype-jdk8-2.9.6.jar │ ├── jackson-datatype-jsr310-2.9.6.jar │ ├── jackson-module-parameter-names-2.9.6.jar │ ├── javax.annotation-api-1.3.2.jar │ ├── jboss-logging-3.3.2.Final.jar │ ├── jul-to-slf4j-1.7.25.jar │ ├── log4j-api-2.10.0.jar │ ├── log4j-to-slf4j-2.10.0.jar │ ├── logback-classic-1.2.3.jar │ ├── logback-core-1.2.3.jar │ ├── slf4j-api-1.7.25.jar │ ├── snakeyaml-1.19.jar │ ├── spring-aop-5.0.9.RELEASE.jar │ ├── spring-beans-5.0.9.RELEASE.jar │ ├── spring-boot-2.0.5.RELEASE.jar │ ├── spring-boot-autoconfigure-2.0.5.RELEASE.jar │ ├── spring-boot-starter-2.0.5.RELEASE.jar │ ├── spring-boot-starter-json-2.0.5.RELEASE.jar │ ├── spring-boot-starter-logging-2.0.5.RELEASE.jar │ ├── spring-boot-starter-tomcat-2.0.5.RELEASE.jar │ ├── spring-boot-starter-web-2.0.5.RELEASE.jar │ ├── spring-context-5.0.9.RELEASE.jar │ ├── spring-core-5.0.9.RELEASE.jar │ ├── spring-expression-5.0.9.RELEASE.jar │ ├── spring-jcl-5.0.9.RELEASE.jar │ ├── spring-web-5.0.9.RELEASE.jar │ ├── spring-webmvc-5.0.9.RELEASE.jar │ ├── tomcat-embed-core-8.5.34.jar │ ├── tomcat-embed-el-8.5.34.jar │ ├── tomcat-embed-websocket-8.5.34.jar │ └── validation-api-2.0.1.Final.jar ├── META-INF │ ├── MANIFEST.MF │ └── maven │ └── org.springframework └── org └── springframework └── boot
这种打包方式的优势在于最终的 jar 包结构很清晰,所有的依赖一目了然。如果使用 maven shade 会将所有的 class 文件混乱堆积在一起,是无法看清其中的依赖。而最终生成的 jar 包在体积上两也者几乎是相等的。
在运行机制上,使用 FatJar 技术运行程序是需要对 jar 包进行改造的,它还需要自定义自己的 ClassLoader 来加载 jar 包里面 lib 目录中嵌套的 jar 包中的类。我们可以对比一下两者的 MANIFEST 文件就可以看出明显差异
1 // Generated by Maven Shade Plugin 2 Manifest-Version: 1.0 3 Implementation-Title: gs-spring-boot 4 Implementation-Version: 0.1.0 5 Built-By: qianwp 6 Implementation-Vendor-Id: org.springframework 7 Created-By: Apache Maven 3.5.4 8 Build-Jdk: 1.8.0_191 9 Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo 10 ot-starter-parent/gs-spring-boot 11 Main-Class: hello.Application 12 13 // Generated by SpringBootLoader Plugin 14 Manifest-Version: 1.0 15 Implementation-Title: gs-spring-boot 16 Implementation-Version: 0.1.0 17 Built-By: qianwp 18 Implementation-Vendor-Id: org.springframework 19 Spring-Boot-Version: 2.0.5.RELEASE 20 Main-Class: org.springframework.boot.loader.JarLauncher 21 Start-Class: hello.Application 22 Spring-Boot-Classes: BOOT-INF/classes/ 23 Spring-Boot-Lib: BOOT-INF/lib/ 24 Created-By: Apache Maven 3.5.4 25 Build-Jdk: 1.8.0_191 26 Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo 27 ot-starter-parent/gs-spring-boot
SpringBoot 将 jar 包中的 Main-Class 进行了替换,换成了 JarLauncher。还增加了一个 Start-Class 参数,这个参数对应的类才是真正的业务 main 方法入口。我们再看看这个 JarLaucher 具体干了什么
1 public class JarLauncher{ 2 ... 3 static void main(String[] args) { 4 new JarLauncher().launch(args); 5 } 6 7 protected void launch(String[] args) { 8 try { 9 JarFile.registerUrlProtocolHandler(); 10 ClassLoader cl = createClassLoader(getClassPathArchives()); 11 launch(args, getMainClass(), cl); 12 } 13 catch (Exception ex) { 14 ex.printStackTrace(); 15 System.exit(1); 16 } 17 } 18 19 protected void launch(String[] args, String mcls, ClassLoader cl) { 20 Runnable runner = createMainMethodRunner(mcls, args, cl); 21 Thread runnerThread = new Thread(runner); 22 runnerThread.setContextClassLoader(classLoader); 23 runnerThread.setName(Thread.currentThread().getName()); 24 runnerThread.start(); 25 } 26 27 } 28 29 class MainMethodRunner { 30 @Override 31 public void run() { 32 try { 33 Thread th = Thread.currentThread(); 34 ClassLoader cl = th.getContextClassLoader(); 35 Class<?> mc = cl.loadClass(this.mainClassName); 36 Method mm = mc.getDeclaredMethod("main", String[].class); 37 if (mm == null) { 38 throw new IllegalStateException(this.mainClassName 39 + " does not have a main method"); 40 } 41 mm.invoke(null, new Object[] { this.args }); 42 } catch (Exception ex) { 43 ex.printStackTrace(); 44 System.exit(1); 45 } 46 } 47 }
从源码中可以看出 JarLaucher 创建了一个特殊的 ClassLoader,然后由这个 ClassLoader 来另启一个单独的线程来加载 MainClass 并运行。
又一个问题来了,当 JVM 遇到一个不认识的类,BOOT-INF/lib 目录里又有那么多 jar 包,它是如何知道去哪个 jar 包里加载呢?我们继续看这个特别的 ClassLoader 的源码
1 class LaunchedURLClassLoader extends URLClassLoader { 2 ... 3 private Class<?> doLoadClass(String name) { 4 if (this.rootClassLoader != null) { 5 return this.rootClassLoader.loadClass(name); 6 } 7 8 findPackage(name); 9 Class<?> cls = findClass(name); 10 return cls; 11 } 12 13 }
这里的 rootClassLoader 就是双亲委派模型里的 ExtensionClassLoader ,JVM 内置的类会优先使用它来加载。如果不是内置的就去查找这个类对应的 Package。
1 private void findPackage(final String name) { 2 int lastDot = name.lastIndexOf('.'); 3 if (lastDot != -1) { 4 String packageName = name.substring(0, lastDot); 5 if (getPackage(packageName) == null) { 6 try { 7 definePackage(name, packageName); 8 } catch (Exception ex) { 9 // Swallow and continue 10 } 11 } 12 } 13 } 14 15 private final HashMap<String, Package> packages = new HashMap<>(); 16 17 protected Package getPackage(String name) { 18 Package pkg; 19 synchronized (packages) { 20 pkg = packages.get(name); 21 } 22 if (pkg == null) { 23 if (parent != null) { 24 pkg = parent.getPackage(name); 25 } else { 26 pkg = Package.getSystemPackage(name); 27 } 28 if (pkg != null) { 29 synchronized (packages) { 30 Package pkg2 = packages.get(name); 31 if (pkg2 == null) { 32 packages.put(name, pkg); 33 } else { 34 pkg = pkg2; 35 } 36 } 37 } 38 } 39 return pkg; 40 } 41 42 private void definePackage(String name, String packageName) { 43 String path = name.replace('.', '/').concat(".class"); 44 for (URL url : getURLs()) { 45 try { 46 if (url.getContent() instanceof JarFile) { 47 JarFile jf= (JarFile) url.getContent(); 48 if (jf.getJarEntryData(path) != null && jf.getManifest() != null) { 49 definePackage(packageName, jf.getManifest(), url); 50 return null; 51 } 52 } 53 } catch (IOException ex) { 54 // Ignore 55 } 56 } 57 return null; 58 }
ClassLoader 会在本地缓存包名和 jar包路径的映射关系,如果缓存中找不到对应的包名,就必须去 jar 包中挨个遍历搜寻,这个就比较缓慢了。不过同一个包名只会搜寻一次,下一次就可以直接从缓存中得到对应的内嵌 jar 包路径。
深层 jar 包的内嵌 class 的 URL 路径长下面这样,使用感叹号 ! 分割
jar:file:/workspace/springboot-demo/target/application.jar!/BOOT-INF/lib/snakeyaml-1.19.jar!/org/yaml/snakeyaml/Yaml.class
不过这个定制的 ClassLoader 只会用于打包运行时,在 IDE 开发环境中 main 方法还是直接使用系统类加载器加载运行的。
不得不说,SpringbootLoader 的设计还是很有意思的,它本身很轻量级,代码逻辑很独立没有其它依赖,它也是 SpringBoot 值得欣赏的点之一。
HelloController 自动注册
还剩下最后一个问题,那就是 HelloController 没有被代码引用,它是如何注册到 Tomcat 服务中去的?它靠的是注解传递机制。
SpringBoot 深度依赖注解来完成配置的自动装配工作,它自己发明了几十个注解,确实严重增加了开发者的心智负担,你需要仔细阅读文档才能知道它是用来干嘛的。Java 注解的形式和功能是分离的,它不同于 Python 的装饰器是功能性的,Java 的注解就好比代码注释,本身只有属性,没有逻辑,注解相应的功能由散落在其它地方的代码来完成,需要分析被注解的类结构才可以得到相应注解的属性。
那注解是又是如何传递的呢?
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@ComponentScan
public @interface SpringBootApplication {
...
}
public @interface ComponentScan {
String[] basePackages() default {};
}
首先 main 方法可以看到的注解是 SpringBootApplication,这个注解又是由ComponentScan 注解来定义的,ComponentScan 注解会定义一个被扫描的包名称,如果没有显示定义那就是当前的包路径。SpringBoot 在遇到 ComponentScan 注解时会扫描对应包路径下面的所有 Class,根据这些 Class 上标注的其它注解继续进行后续处理。当它扫到 HelloController 类时发现它标注了 RestController 注解。
@RestController
public class HelloController {
...
}
@Controller
public @interface RestController {
}
而 RestController 注解又标注了 Controller 注解。SpringBoot 对 Controller 注解进行了特殊处理,它会将 Controller 注解的类当成 URL 处理器注册到 Servlet 的请求处理器中,在创建 Tomcat Server 时,会将请求处理器传递进去。HelloController 就是如此被自动装配进 Tomcat 的。
扫描处理注解是一个非常繁琐肮脏的活计,特别是这种用注解来注解注解(绕口)的高级使用方法,这种方法要少用慎用。SpringBoot 中有大量的注解相关代码,企图理解这些代码是乏味无趣的没有必要的,它只会把你的本来清醒的脑袋搞晕。SpringBoot 对于习惯使用的同学来说它是非常方便的,但是其内部实现代码不要轻易模仿,那绝对算不上模范 Java 代码。
用过 SpringBoot 的同学都知道,其程序的启动类是在一个main
方法中调用SpringApplication.run
方法执行的,如:
@SpringBootApplication
public class SpringApplicationBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringApplicationBootstrap.class, args);
}
}
那么,这里面到底做了什么呢?本篇文章将深入源码,带你一起探究底层实现。
SpringApplication 初始化阶段
进入到SpringApplication.run
方法,其首先会创建一个SpringApplication
对象,我们看其构造函数:
1 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { 2 this.resourceLoader = resourceLoader; 3 Assert.notNull(primarySources, "PrimarySources must not be null"); 4 // primarySources 为 run 方法传入的引导类 5 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); 6 // 推断web应用类 7 this.webApplicationType = deduceWebApplicationType(); 8 // 初始化 initializers 属性 9 setInitializers((Collection) getSpringFactoriesInstances( 10 ApplicationContextInitializer.class)); 11 // 初始化监听器 12 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 13 // 推断应用引导类 14 this.mainApplicationClass = deduceMainApplicationClass(); 15 }
我们先看推断web应用类的方法deduceWebApplicationType()
:
1 private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", 2 "org.springframework.web.context.ConfigurableWebApplicationContext" }; 3 4 private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework." 5 + "web.reactive.DispatcherHandler"; 6 7 private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework." 8 + "web.servlet.DispatcherServlet"; 9 10 private WebApplicationType deduceWebApplicationType() { 11 if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) 12 && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) { 13 return WebApplicationType.REACTIVE; 14 } 15 for (String className : WEB_ENVIRONMENT_CLASSES) { 16 if (!ClassUtils.isPresent(className, null)) { 17 return WebApplicationType.NONE; 18 } 19 } 20 return WebApplicationType.SERVLET; 21 }
根据 classpath 下是否存在某个特征类来决定是否应该创建一个为 Web 应用使用的ApplicationContext
类型。具体判断为:
如果仅存在 Reactive 的包,则为
WebApplicationType.REACTIVE
类型;
如果 Servlet 和 Reactive的包都不存在,则为WebApplicationType.NONE
类型;
其他情况都为WebApplicationType.SERVLET
类型。
接下来我们看初始化initializers
属性的过程,其通过getSpringFactoriesInstances(ApplicationContextInitializer.class)
方法获取初始化器:
1 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, 2 Class<?>[] parameterTypes, Object... args) { 3 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 4 // Use names and ensure unique to protect against duplicates 5 Set<String> names = new LinkedHashSet<>( 6 SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 7 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, 8 classLoader, args, names); 9 AnnotationAwareOrderComparator.sort(instances); 10 return instances; 11 }
该方法流程为:
- 通过
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
方法,在 META-INF/spring.factories 文件下查找ApplicationContextInitializer
类型对应的资源名称。 - 实例化上面的资源信息(初始化器)。
- 对初始化器根据
Ordered
接口或者@Order
注解进行排序。
同理,初始化listeners
监听器也是类似的,这里不再累赘。
SpringApplication 初始化阶段的最后一步是推断引导类deduceMainApplicationClass()
:
1 private Class<?> deduceMainApplicationClass() { 2 try { 3 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); 4 for (StackTraceElement stackTraceElement : stackTrace) { 5 if ("main".equals(stackTraceElement.getMethodName())) { 6 return Class.forName(stackTraceElement.getClassName()); 7 } 8 } 9 } 10 catch (ClassNotFoundException ex) { 11 // Swallow and continue 12 } 13 return null; 14 }
其将调用栈中main
方法所在的类作为引导类。
SpringApplication 运行阶段
SpringApplication 运行阶段属于核心过程,完全围绕 run(String...) 方法展开。该过程结合初始化阶段完成的状态,进一步完善运行时所需要准备的资源,随后启动 Spring 应用上下文。在此期间伴随着 Spring Boot 和 Spring 事件的触发,形成完整的 SpringApplication 生命周期。因此,下面将围绕以下三个子议题进行讨论。
- SpringApplication 准备阶段
- ApplicationContext 启动阶段
- ApplicationContext 启动后阶段
1. SpringApplication 准备阶段
本阶段属于 ApplicationContext 启动阶段的前一阶段,设计的范围从 run(String...)
方法调用开始,到refreshContext(ConfigurableApplicationContext)
调用前:
1 public ConfigurableApplicationContext run(String... args) { 2 //记录程序运行时间 3 StopWatch stopWatch = new StopWatch(); 4 stopWatch.start(); 5 //Spring 应用的上下文 6 ConfigurableApplicationContext context = null; 7 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); 8 configureHeadlessProperty(); 9 // 获取 SpringApplicationRunListeners 10 SpringApplicationRunListeners listeners = getRunListeners(args); 11 listeners.starting(); 12 try { 13 // 创建 ApplicationArguments 对象 14 ApplicationArguments applicationArguments = new DefaultApplicationArguments( 15 args); 16 // 加载属性配置 17 ConfigurableEnvironment environment = prepareEnvironment(listeners, 18 applicationArguments); 19 // 处理需要忽略的Bean 20 configureIgnoreBeanInfo(environment); 21 // 打印 banner 22 Banner printedBanner = printBanner(environment); 23 // 创建 Spring 应用上下文 24 context = createApplicationContext(); 25 // 实例化 SpringBootExceptionReporter,用来报告关于启动过程中的错误 26 exceptionReporters = getSpringFactoriesInstances( 27 SpringBootExceptionReporter.class, 28 new Class[] { ConfigurableApplicationContext.class }, context); 29 // 应用上下文的准备阶段 30 prepareContext(context, environment, listeners, applicationArguments, 31 printedBanner); 32 // 刷新应用上下文(自动装配,初始化 IOC 容器) 33 refreshContext(context); 34 ... 35 } 36 }
1 private SpringApplicationRunListeners getRunListeners(String[] args) { 2 Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; 3 return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( 4 SpringApplicationRunListener.class, types, this, args)); 5 }
该方法会通过getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)
方法,获取 META-INF/spring.factories 文件下SpringApplicationRunListener
对应的资源,并且实例化这些资源:
1 # Run Listeners 2 org.springframework.boot.SpringApplicationRunListener=\ 3 org.springframework.boot.context.event.EventPublishingRunListener
我们发现,其有且仅有一个实现类EventPublishingRunListener
:
1 public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { 2 3 private final SpringApplication application; 4 5 private final String[] args; 6 7 private final SimpleApplicationEventMulticaster initialMulticaster; 8 9 public EventPublishingRunListener(SpringApplication application, String[] args) { 10 this.application = application; 11 this.args = args; 12 this.initialMulticaster = new SimpleApplicationEventMulticaster(); 13 for (ApplicationListener<?> listener : application.getListeners()) { 14 this.initialMulticaster.addApplicationListener(listener); 15 } 16 } 17 18 @Override 19 public int getOrder() { 20 return 0; 21 } 22 23 @Override 24 public void starting() { 25 this.initialMulticaster.multicastEvent( 26 new ApplicationStartingEvent(this.application, this.args)); 27 } 28 29 @Override 30 public void environmentPrepared(ConfigurableEnvironment environment) { 31 this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( 32 this.application, this.args, environment)); 33 } 34 35 @Override 36 public void contextPrepared(ConfigurableApplicationContext context) { 37 38 } 39 40 @Override 41 public void contextLoaded(ConfigurableApplicationContext context) { 42 for (ApplicationListener<?> listener : this.application.getListeners()) { 43 if (listener instanceof ApplicationContextAware) { 44 ((ApplicationContextAware) listener).setApplicationContext(context); 45 } 46 context.addApplicationListener(listener); 47 } 48 this.initialMulticaster.multicastEvent( 49 new ApplicationPreparedEvent(this.application, this.args, context)); 50 } 51 52 @Override 53 public void started(ConfigurableApplicationContext context) { 54 context.publishEvent( 55 new ApplicationStartedEvent(this.application, this.args, context)); 56 } 57 58 @Override 59 public void running(ConfigurableApplicationContext context) { 60 context.publishEvent( 61 new ApplicationReadyEvent(this.application, this.args, context)); 62 } 63 64 @Override 65 public void failed(ConfigurableApplicationContext context, Throwable exception) { 66 ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, 67 this.args, context, exception); 68 if (context != null && context.isActive()) { 69 // Listeners have been registered to the application context so we should 70 // use it at this point if we can 71 context.publishEvent(event); 72 } 73 else { 74 // An inactive context may not have a multicaster so we use our multicaster to 75 // call all of the context's listeners instead 76 if (context instanceof AbstractApplicationContext) { 77 for (ApplicationListener<?> listener : ((AbstractApplicationContext) context) 78 .getApplicationListeners()) { 79 this.initialMulticaster.addApplicationListener(listener); 80 } 81 } 82 this.initialMulticaster.setErrorHandler(new LoggingErrorHandler()); 83 this.initialMulticaster.multicastEvent(event); 84 } 85 } 86 ... 87 88 }
在实例化EventPublishingRunListener
的过程中,会给它最重要的属性initialMulticaster
赋值,其类型是SimpleApplicationEventMulticaster
。接着遍历 SpringApplication 初始化阶段的listeners
监听器集合,将监听器存入其关联的ListenerRetriever#applicationListeners
属性中。
了解 Spring 事件监听机制的同学应该对SimpleApplicationEventMulticaster
不陌生,它是ApplicationEvent
事件的发布者。Spring Boot 的事件监听机制也是如出一辙,具体可参考我的另一篇文章 深入理解 Spring 的事件发布监听机制。
于是接下来调用listeners.starting()
方法就会通过其内部的initialMulticaster
属性发布ApplicationStartingEvent
事件。
- prepareEnvironment(listeners,applicationArguments) 方法:
加载属性配置。执行完成后,所有的environment
的属性都会加载进来,包括 application.properties 和外部的属性配置。
1 private ConfigurableEnvironment prepareEnvironment( 2 SpringApplicationRunListeners listeners, 3 ApplicationArguments applicationArguments) { 4 // 创建 ConfigurableEnvironment 对象 5 ConfigurableEnvironment environment = getOrCreateEnvironment(); 6 // 配置 ConfigurableEnvironment 7 configureEnvironment(environment, applicationArguments.getSourceArgs()); 8 // 发布 ApplicationEnvironmentPreparedEvent 事件 9 listeners.environmentPrepared(environment); 10 // 将 ConfigurableEnvironment 绑定到 SpringApplication 中 11 bindToSpringApplication(environment); 12 if (this.webApplicationType == WebApplicationType.NONE) { 13 environment = new EnvironmentConverter(getClassLoader()) 14 .convertToStandardEnvironmentIfNecessary(environment); 15 } 16 ConfigurationPropertySources.attach(environment); 17 return environment; 18 }
大致流程为:
- 创建
ConfigurableEnvironment
对象。 - 配置
environment
变量。 - 发布
ApplicationEnvironmentPreparedEvent
事件。
其对应的监听器为ConfigFileApplicationListener
,当接收到上面的事件时,加载并实例化 “META-INF/spring.factories” 文件中EnvironmentPostProcessor
类型的实现类,遍历并执行其postProcessEnvironment
方法。值得注意的是,ConfigFileApplicationListener
自身也是EnvironmentPostProcessor
的实现类,于是也会执行其postProcessEnvironment
方法:
1 public void postProcessEnvironment(ConfigurableEnvironment environment, 2 SpringApplication application) { 3 // 将配置文件信息添加到 environment 中 4 addPropertySources(environment, application.getResourceLoader()); 5 configureIgnoreBeanInfo(environment); 6 // 将 environment 绑定到 Spring 应用上下文中 7 bindToSpringApplication(environment, application); 8 }
该方法的目的是,加载配置文件信息至enviroment
,并将enviroment
绑定到 Spring 应用上下文中。
当我们需要在配置文件加载完成之后做一些事情的话,我们就可以自定义一个EnvironmentPostProcessor
的实现类,操作逻辑写在postProcessEnvironment
方法中,当然,别忘了在你的 “META-INF/spring.factories” 文件中加上配置。
- 绑定
environment
到SpringApplication
上。
- createApplicationContext 方法
该方法会根据webApplicationType
类型,创建不同的ConfigurableApplicationContext
Spring 应用上下文:
1 protected ConfigurableApplicationContext createApplicationContext() { 2 Class<?> contextClass = this.applicationContextClass; 3 if (contextClass == null) { 4 try { 5 switch (this.webApplicationType) { 6 case SERVLET: 7 contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS); 8 break; 9 case REACTIVE: 10 contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); 11 break; 12 default: 13 contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); 14 } 15 } 16 catch (ClassNotFoundException ex) { 17 throw new IllegalStateException( 18 "Unable create a default ApplicationContext, " 19 + "please specify an ApplicationContextClass", 20 ex); 21 } 22 } 23 return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); 24 }
我们以 SERVLET 类型为例,它会创建AnnotationConfigServletWebServerApplicationContext
应用上下文实例。
- 获取 Spring 异常报告器
getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context)
方法,获取 META-INF/spring.factories 文件下类型为SpringBootExceptionReporter
的资源实例:
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
其实现类有且仅有一个,即FailureAnalyzers
。
- prepareContext 方法
Spring 应用上下文启动前的准备工作:
1 private void prepareContext(ConfigurableApplicationContext context, 2 ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, 3 ApplicationArguments applicationArguments, Banner printedBanner) { 4 //设置 context 的 environment 属性 5 context.setEnvironment(environment); 6 // Spring 应用上下文的后置处理 7 postProcessApplicationContext(context); 8 // 运用 Spring 应用上下文初始化器 9 applyInitializers(context); 10 listeners.contextPrepared(context); 11 if (this.logStartupInfo) { 12 logStartupInfo(context.getParent() == null); 13 logStartupProfileInfo(context); 14 } 15 16 // Add boot specific singleton beans 17 context.getBeanFactory().registerSingleton("springApplicationArguments", 18 applicationArguments); 19 if (printedBanner != null) { 20 context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); 21 } 22 23 // Load the sources 24 Set<Object> sources = getAllSources(); 25 Assert.notEmpty(sources, "Sources must not be empty"); 26 // 加载 BeanDefinition 27 load(context, sources.toArray(new Object[0])); 28 listeners.contextLoaded(context); 29 }
大致流程为:
- 给
context
的属性做赋值,如设置环境变量,调用初始化器来初始化context
。 - 获取所有配置源信息,包括 Configuration Class、类名、包名及Spring XML 配置资源路径信息。
- 加载 Spring 应用上下文配置源。将
BeanDefinition
加载到context
中。 - 发布上下文已准备事件
ApplicationPreparedEvent
。
这里,我们要着重看第三步load(context, sources.toArray(new Object[0]))
:
1 protected void load(ApplicationContext context, Object[] sources) { 2 if (logger.isDebugEnabled()) { 3 logger.debug( 4 "Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); 5 } 6 BeanDefinitionLoader loader = createBeanDefinitionLoader( 7 getBeanDefinitionRegistry(context), sources); 8 if (this.beanNameGenerator != null) { 9 loader.setBeanNameGenerator(this.beanNameGenerator); 10 } 11 if (this.resourceLoader != null) { 12 loader.setResourceLoader(this.resourceLoader); 13 } 14 if (this.environment != null) { 15 loader.setEnvironment(this.environment); 16 } 17 loader.load(); 18 }
该方法将 Spring 应用上下文转载的任务交给了BeanDefinitionLoader
:
1 class BeanDefinitionLoader { 2 3 private final Object[] sources; 4 5 private final AnnotatedBeanDefinitionReader annotatedReader; 6 7 private final XmlBeanDefinitionReader xmlReader; 8 9 private BeanDefinitionReader groovyReader; 10 11 private final ClassPathBeanDefinitionScanner scanner; 12 13 private ResourceLoader resourceLoader; 14 ... 15 }
BeanDefinitionLoader
组合了多个属性,第一个属性为SpringApplication#getAllSources()
方法返回值,而属性annotatedReader
、xmlReader
、groovyReader
分别为注解驱动实现AnnotatedBeanDefinitionReader
、XML 配置实现XmlBeanDefinitionReader
和 Groovy 实现GroovyBeanDefinitionReader
。其中AnnotatedBeanDefinitionReader
与ClassPathBeanDefinitionScanner
配合,形成AnnotationConfigApplicationContext
扫描和注册配置类的基础,随后这些配置类被解析为 Bean 定义BeanDefinition
:
1 public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { 2 3 private final AnnotatedBeanDefinitionReader reader; 4 5 private final ClassPathBeanDefinitionScanner scanner; 6 7 8 /** 9 * Create a new AnnotationConfigApplicationContext that needs to be populated 10 * through {@link #register} calls and then manually {@linkplain #refresh refreshed}. 11 */ 12 public AnnotationConfigApplicationContext() { 13 this.reader = new AnnotatedBeanDefinitionReader(this); 14 this.scanner = new ClassPathBeanDefinitionScanner(this); 15 } 16 17 ... 18 19 public void register(Class<?>... annotatedClasses) { 20 Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified"); 21 this.reader.register(annotatedClasses); 22 } 23 24 public void scan(String... basePackages) { 25 Assert.notEmpty(basePackages, "At least one base package must be specified"); 26 this.scanner.scan(basePackages); 27 } 28 ... 29 }
不难看出,Spring Boot 中的BeanDefinitionLoader
是以上BeanDefinition
读取的综合实现。当其load()
方法调用时,这些BeanDefinitionReader
类型的属性各司其职,为 Spring 应用上下文从不同的配置源装载 Spring Bean 定义(BeanDefinition)。
装载完BeanDefinition
到 Spring 应用上下文之后,就调用listeners.contextLoaded(context)
方法,发布 Spring 应用上下文已准备ApplicationPreparedEvent
事件,以结束 SpringApplication 准备阶段。
2. ApplicationContext 启动阶段
本阶段的执行由refreshContext(ConfigurableApplicationContext)
完成,其核心方法是AbstractApplicationContext#refresh
:
1 public void refresh() throws BeansException, IllegalStateException { 2 synchronized (this.startupShutdownMonitor) { 3 // Prepare this context for refreshing. 4 prepareRefresh(); 5 6 // Tell the subclass to refresh the internal bean factory. 7 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 8 9 // Prepare the bean factory for use in this context. 10 prepareBeanFactory(beanFactory); 11 12 try { 13 // Allows post-processing of the bean factory in context subclasses. 14 postProcessBeanFactory(beanFactory); 15 16 // Invoke factory processors registered as beans in the context. 17 invokeBeanFactoryPostProcessors(beanFactory); 18 19 // Register bean processors that intercept bean creation. 20 registerBeanPostProcessors(beanFactory); 21 22 // Initialize message source for this context. 23 initMessageSource(); 24 25 // Initialize event multicaster for this context. 26 initApplicationEventMulticaster(); 27 28 // Initialize other special beans in specific context subclasses. 29 onRefresh(); 30 31 // Check for listener beans and register them. 32 registerListeners(); 33 34 // Instantiate all remaining (non-lazy-init) singletons. 35 finishBeanFactoryInitialization(beanFactory); 36 37 // Last step: publish corresponding event. 38 finishRefresh(); 39 } 40 41 catch (BeansException ex) { 42 if (logger.isWarnEnabled()) { 43 logger.warn("Exception encountered during context initialization - " + 44 "cancelling refresh attempt: " + ex); 45 } 46 47 // Destroy already created singletons to avoid dangling resources. 48 destroyBeans(); 49 50 // Reset 'active' flag. 51 cancelRefresh(ex); 52 53 // Propagate exception to caller. 54 throw ex; 55 } 56 57 finally { 58 // Reset common introspection caches in Spring's core, since we 59 // might not ever need metadata for singleton beans anymore... 60 resetCommonCaches(); 61 } 62 } 63 }
随着该方法的执行,Spring Boot 核心特性也随之启动,如组件自动装配、嵌入式容器启动。了解过 Spring Boot 自动装配机制的同学应该知道(如不清楚,请参考我的另一篇文章 Spring Boot 自动装配),在根据应用类型创建不同的 Spring 应用上下文的方法createApplicationContext()
中,会实例化AnnotatedBeanDefinitionReader
对象,该对象的构造方法中,会将ConfigurationClassPostProcessor
封装成 Spring Bean 定义(BeanDefinition),并将其注入到 Ioc 容器DefaultListableBeanFactory
(其实现了BeanDefinitionRegistry
,具有注入BeanDefinition
的功能)中。于是,在该阶段的invokeBeanFactoryPostProcessors(beanFactory)
方法中,就会取出ConfigurationClassPostProcessor
对象,随后调用其postProcessBeanFactory(beanFactory)
方法进行装配工作。
3. ApplicationContext 启动后阶段
实际上,SpringApplication#afterRefresh
方法并未给 Spring 应用上下文启动后阶段提供实现,而是将其交给开发人员自行扩展:
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}
所有,直接跳过该步,接下来调用listeners.started(context)
方法,发布 Spring 应用上下文已启动ApplicationStartedEvent
事件。
该阶段最后,调用callRunners(context, applicationArguments)
方法,来调用实现了CommandLineRunner
或者ApplicationRunner
接口的类的 run 方法,得以满足需要在 Spring 应用上下文完全准备完毕后,执行一些操作的场景。
1 private void callRunners(ApplicationContext context, ApplicationArguments args) { 2 List<Object> runners = new ArrayList<>(); 3 runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); 4 runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); 5 AnnotationAwareOrderComparator.sort(runners); 6 for (Object runner : new LinkedHashSet<>(runners)) { 7 if (runner instanceof ApplicationRunner) { 8 callRunner((ApplicationRunner) runner, args); 9 } 10 if (runner instanceof CommandLineRunner) { 11 callRunner((CommandLineRunner) runner, args); 12 } 13 } 14 }
总结
至此,SpringApplciation.run 方法的执行流程已经讲解完毕。下面我们来整理一下大体的步骤:
- 初始化 SpringApplication 实例:决定web应用类型、加载初始化器和监听器、推断 main 方法的定义类。
- 通过 SpringFactoriesLoader 加载的 SpringApplicationRunListener,调用它们的 started 方法。
- 创建并配置当前 Spring Boot 应用将要使用的 Environment,如 applocation.properties 文件和外部配置。
- 根据 Web 服务类型创建不同的 Spring 应用上下文,并将之前准备好的 Environment 设置给 Spring 应用上下文 ApplicationContext 使用。
- 遍历初始化器,对 ApplicationContext 进行初始化操作。
- 加载所有资源,如 Configuration Class、类名、包名以及 Spring XML 配置资源路径,将所有 BeanDefinition 加载至 ApplicationContext。
- 初始化上下文 refresh(),进行自动装配,初始化 Ioc 容器等操作。
-
寻找当前 ApplicationContext 中是否注册有 CommandLineRunner 或者 ApplicationRunner,如果有,则遍历执行它们。
作者:habit_learning
链接:https://www.jianshu.com/p/c2789f1548ab
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。