Spring Boot是一个基于Spring Framework的快速开发应用程序的框架。它提供了一种简单粗暴的方式来构建应用程序,开发人员只需要少量的配置即可快速的构建可运行的应用程序。然而,即使是Spring Boot的核心框架,也有很多可以让我们深入了解它如何工作的知识点。在这篇文章中,我们将深入研究Spring Boot的启动流程,并提供运行过程中的详细解释。
概述
在Spring Boot应用程序的启动过程中,一个类被认为是Spring Boot的入口点:org.springframework.boot.loader.Launcher
。(准确的说是PropertiesLauncher
类extends了Launcher
类,最先进入的是PropertiesLauncher
类)。Launcher
这个类的主要任务是设置ClassLoader
和加载应用程序的主类。在执行PropertiesLauncher
的main
方法之前,Spring Boot会执行一些准备工作,例如创建ClassLoader
和加载一些必需的类。接下来,我们将逐步了解Spring Boot在启动过程中的每个步骤。
创建ClassLoader
为了加载应用程序的类和资源,Spring Boot使用了一个自定义的ClassLoader:org.springframework.boot.loader.LaunchedURLClassLoader
。Spring Boot使用LaunchedURLClassLoader
替代了JDK提供的ClassLoader,这个ClassLoader可以加载来自多个JAR包和目录的类和资源。在Spring Boot启动流程的第一步中,它会执行以下代码创建LaunchedURLClassLoader
:
// PropertiesLauncher 的main方法,调用launch类launch方法
public static void main(String[] args) throws Exception {
PropertiesLauncher launcher = new PropertiesLauncher();
args = launcher.getArgs(args);
launcher.launch(args);
}
...
// Launcher类的launch方法
protected void launch(String[] args) throws Exception {
if (!isExploded()) {
JarFile.registerUrlProtocolHandler();
}
ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
String jarMode = System.getProperty("jarmode");
String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
launch(args, launchClass, classLoader);
}
...
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
return getClassPathArchives().iterator();
}
...
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
}
...
// Launcher类的createClassLoader方法
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(50);
while (archives.hasNext()) {
urls.add(archives.next().getUrl());
}
return createClassLoader(urls.toArray(new URL[0]));
}
...
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
}
...
protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(launchClass, args, classLoader).run();
}
getClassLoader()
方法用于获取Launcher
类的ClassLoader
,并将其设置为当前线程的ContextClassLoader
。这个步骤非常重要,因为Spring Boot应用程序可能需要与其他应用程序或库共享ClassLoader
。接下来,Spring Boot将使用getClassPathArchivesIterator()
方法从应用程序的JAR包或目录中获取所有的类文件。然后,Spring Boot使用createClassLoader()
方法创建LaunchedURLClassLoader
。
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(50);
while (archives.hasNext()) {
urls.add(archives.next().getUrl());
}
return createClassLoader(urls.toArray(new URL[0]));
}
getClassPathArchivesIterator()
方法用于获取每个JAR包或目录的URL,并将其添加到一个List
中。最终,该方法返回一个表示所有URL的数组。然后,Spring Boot使用urls
参数和parent
参数创建LaunchedURLClassLoader
。
LaunchedURLClassLoader
可以加载来自以下位置的类和资源:
- 从应用程序的JAR包或目录中加载类和资源。
- 从JRE和JDK提供的系统类或资源中加载类和资源。
- 从其他位于
Djava.ext.dirs
或java.ext.dirs
环境变量指定目录中的类或资源中加载类或资源。
LaunchedURLClassLoader
确保只能从JAR包和目录中加载应用程序的类和资源,不会影响系统类或资源,这也是Spring Boot应用程序不会与其他应用程序或库发生冲突的原因之一。
使用LaunchedURLClassLoader加载应用程序的主类
在启动流程的下一步中,Spring Boot将使用LaunchedURLClassLoader
加载应用程序的主类。应用程序的主类通常是一个类的入口点,其中包含Spring的@SpringBootApplication
注解,该注解为Spring提供了一些配置信息。
在Launcher
类的main
方法之前,Spring Boot会执行以下代码加载应用程序的主类:
private Class<?> getMainClass() throws Exception {
String mainClassName = this.manifest.getMainAttributes().getValue(MAIN_CLASS_ATTRIBUTE);
if (mainClassName == null) {
throw new IllegalStateException("No '" + MAIN_CLASS_ATTRIBUTE + "' attribute specified in manifest");
}
try {
return Class.forName(mainClassName, false, getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Unable to load main class: " + mainClassName, ex);
}
}
在这里,Spring Boot从应用程序的MANIFEST.MF
文件中获取应用程序的主类名称,并使用获取的名称使用ClassLoader
加载该类。一旦找到应用程序的主类,Spring Boot就会使用Java Reflection API执行该类的main
方法。
加载Spring Boot默认配置
Spring Boot提供了大量的自动配置,这些自动配置确定了应用程序的基本行为。例如,自动配置可以启用Web功能和限制Web的端口,自动配置并启用Spring Data JPA或MongoDB,以及自动配置并启用Spring Security等。在启动流程中的下一步,Spring Boot会尝试读取一些默认的配置文件作为自动配置。这些默认的配置文件包括:
application.properties
或application.yml
:位于应用程序的classpath
根目录下,可以包含所有Spring Boot配置属性的值。application-{profile}.properties
或application-{profile}.yml
:根据特定的应用程序部署配置文件包括在激活的profile中。application-local.properties
或application-local.yml
:相当于application-{profile}.properties
或application-{profile}.yml
,但只在部署到本地机器时才有效。
在确定了默认配置文件的位置之后,Spring Boot将使用PropertiesLoaderUtils
或YamlPropertiesFactoryBean
加载所有的配置文件,并将它们合并成一个Properties
或YamlProperties
对象。
private ConfigurableEnvironment createEnvironment() {
StandardEnvironment environment = new StandardEnvironment();
if (this.useLegacyProcessing) {
environment.getPropertySources().addLast(new SimpleCommandLinePropertySource(this.args));
}
else {
environment.getPropertySources().addFirst(new SimpleCommandLinePropertySource(this.args));
}
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
}
...
new SpringFactoriesLoader().loadFactories(EnvironmentPostProcessor.class, classLoader)
.forEach((processor) -> processor.postProcessEnvironment(environment, this.application));
return environment;
}
在这里,createEnvironment()
方法用于创建一个应用程序的环境,并通过将所有的配置文件加载到环境中来启用自动配置。在环境创建完成后,Spring Boot将检查所有被标记为@Configuration
和@Component
的类,以确定它们是否是自动配置类。一旦自动配置类被确定,Spring Boot将尝试向应用程序中添加适当的Bean,以便为应用程序提供所需的功能。
执行Spring Boot应用程序
在前面的步骤中,Spring Boot创建了一个环境,并加载了必要的配置文件和自动配置类。在启动流程的最后一步中,Spring Boot将执行应用程序的主类,并开始运行应用程序。
private void launch(String[] args) throws Exception {
...
evaluateSystemProperties();
...
for (SpringApplicationRunListener listener : getRunListeners(args)) {
listener.starting();
}
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableApplicationContext context = createApplicationContext();
...
context.publishEvent(new EnvironmentPreparedEvent(context, applicationArguments, this.environment));
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog());
}
..
context.refresh();
...
propagateApplicationReadyEvent(listener, context);
...
context.start
在context.start()
方法被调用后,Spring Boot将开始运行应用程序,这时应用程序已准备好处理来自客户端的请求。
关于org.springframework.boot.loader.Launcher
org.springframework.boot.loader.Launcher
是 Spring Boot 内置的一个类,用于启动和运行可执行的 Spring Boot Jar 文件或 War 文件。
它被用来创建类加载器、加载和启动应用程序上下文,并启动应用程序。注意,这个类通常不需要显式使用,因为 SpringApplication
或 SpringBootServletInitializer
实现了这个类的所有功能。
在 Spring Boot 的内部机制中,Launcher
主要被用来给类路径中所有的 jar 文件创建一个简单的类加载器,并组装这些 jar 文件内的类。然后,它会为应用程序创建一个新的 classpath,包含 Spring Boot 运行时需要的 jar 文件和所有的类路径,然后启动应用程序。
在一般情况下,开发者不需要使用 Launcher
类。它的作用已经被 Spring Boot 的启动器和 Maven 或 Gradle 插件所抽象封装,我们只需要使用 SpringApplication.run()
或 web 应用程序版本的 SpringBootServletInitializer
来启动我们的应用程序即可。
结论
在本文中,我们对Spring Boot应用程序启动过程做了一个总体介绍,并深入了解了每个步骤的细节。了解Spring Boot启动流程是非常重要的,因为它可以使我们更好地理解应用程序的工作原理,并帮助我们在出现问题时更好地排除故障。通过对Spring Boot启动流程的深入学习,我们可以更好地维护和优化自己的应用程序。
在本文中,我们了解了以下内容:
- Spring Boot创建
LaunchedURLClassLoader
,以加载应用程序的类和资源。 - Spring Boot加载应用程序的主类,并使用Reflection API执行主方法。
- Spring Boot加载配置文件,以准备自动配置。
- Spring Boot执行应用程序,并开始运行。
了解Spring Boot启动流程是我们取得成功的关键之一。感谢您阅读本文。如果您有任何疑问或反馈,请在下面的评论区留言。