本文是 Spring Boot 运行源码剖析内容,内容夹杂了不少的 Spring Boot 源代码片段。
有兴趣的读者,可以按照章节名称跳跃阅读。本文分为两大章节,分别是:
一、SpringBoot 工程准备
首先,花 2 分钟构建一个 SpringBoot 工程。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.live</groupId>
<artifactId>springBoot</artifactId>
<version>0.0.1</version>
<name>springBoot</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>13</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.properties
spring.profiles.active=dev
spring.devtools.add-properties=false
application.properties
spring.profiles=dev
logging.level.web=debug
MyApplication.java
package cn.live;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
启动成功日志
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.6.RELEASE)
2020-04-06 16:50:28.783 INFO 4205 --- [ restartedMain] cn.live.MyApplication : Starting MyApplication on FoamValue-MBP.local with PID 4205 (/Users/chenxinjie/workspace-2020-02/springBoot/target/classes started by chenxinjie in /Users/chenxinjie/workspace-2020-02/springBoot)
2020-04-06 16:50:28.785 INFO 4205 --- [ restartedMain] cn.live.MyApplication : The following profiles are active: dev
2020-04-06 16:50:29.768 INFO 4205 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-04-06 16:50:29.781 INFO 4205 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-04-06 16:50:29.781 INFO 4205 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.33]
2020-04-06 16:50:29.857 INFO 4205 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-04-06 16:50:29.857 DEBUG 4205 --- [ restartedMain] o.s.web.context.ContextLoader : Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT]
2020-04-06 16:50:29.857 INFO 4205 --- [ restartedMain] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1020 ms
2020-04-06 16:50:29.997 DEBUG 4205 --- [ restartedMain] o.s.b.w.s.ServletContextInitializerBeans : Mapping filters: filterRegistrationBean urls=[/*] order=-2147483647, characterEncodingFilter urls=[/*] order=-2147483648, formContentFilter urls=[/*] order=-9900, requestContextFilter urls=[/*] order=-105
2020-04-06 16:50:29.997 DEBUG 4205 --- [ restartedMain] o.s.b.w.s.ServletContextInitializerBeans : Mapping servlets: dispatcherServlet urls=[/]
2020-04-06 16:50:30.118 INFO 4205 --- [ restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-04-06 16:50:30.129 DEBUG 4205 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice
2020-04-06 16:50:30.183 DEBUG 4205 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : 2 mappings in 'requestMappingHandlerMapping'
2020-04-06 16:50:30.209 DEBUG 4205 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
2020-04-06 16:50:30.219 DEBUG 4205 --- [ restartedMain] .m.m.a.ExceptionHandlerExceptionResolver : ControllerAdvice beans: 0 @ExceptionHandler, 1 ResponseBodyAdvice
2020-04-06 16:50:30.315 INFO 4205 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2020-04-06 16:50:30.319 INFO 4205 --- [ restartedMain] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
2020-04-06 16:50:30.370 INFO 4205 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-04-06 16:50:30.374 INFO 4205 --- [ restartedMain] cn.live.MyApplication : Started MyApplication in 1.941 seconds (JVM running for 2.381)
浏览器打开效果
在浏览器中输入 `http://localhost:8080/actuator` 可以展现如下效果:
{"_links":{"self":{"href":"http://localhost:8080/actuator","templated":false},"health":{"href":"http://localhost:8080/actuator/health","templated":false},"health-path":{"href":"http://localhost:8080/actuator/health/{*path}","templated":true},"info":{"href":"http://localhost:8080/actuator/info","templated":false}}}
小结
依稀还记得 2011 年那会儿,Spring 还没有现在这么流行。那时更多的还是,手动下载一个个的 jar 包,然后放在一个 Java Web 工程下面去尝试。Spring Framework + Struts2 + Hibernate 的架构,包含很多 jar 的基础上,还有一些特定版本冲突的问题。那时候,从零构建一个 Java Web 工程,是一件非常苦痛的任务。
而现在,Spring Boot + Maven 的方式,简直不要太香了。
二、从启动类剖析
MyApplication.java 是开发人员自定义的启动类,但实际上有效的代码只有两行。分别是 `@SpringBootApplication` 和 `SpringApplication.run()`。
从 `@SpringBootApplication`、`SpringApplication.run()` 两个角度来拆分的话,可以得到下面的树状结构。
MyApplication.java
|-- @SpringBootApplication
| |-- @SpringBootConfiguration
| |-- @Configuration
| |-- @EnableAutoConfiguration
| |-- @AutoConfigurationPackage
| |-- @Import(AutoConfigurationPackages.Registrar.class)
| |-- @Import(AutoConfigurationImportSelector.class)
| |-- @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
| |-- @Repeatable(ComponentScans.class)
| |-- @ComponentScans
|--SpringApplication.run()
SpringBootApplication 注解部分
Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。
1. SpringBootApplication 注解接口类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
2. SpringBootConfiguration 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
3. Configuration 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
4. EnableAutoConfiguration 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
5. AutoConfigurationPackage 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
6. ComponentScan 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
7. ComponentScans 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
以上是 @SpringBootApplication 注解的展开内容。所有的注解加起来,只做了两件事。
SpringApplication.run() 部分
1. new SpringApplication(primarySources)
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
2. SpringApplication.run(args)
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
小结
简单翻阅了源代码,了解 `@SpringBootApplication` 都包含哪些注解标记。以及 `SpringApplication.run()` 执行的之后,都有哪些步骤。