Spring Boot 的初步使用: 【从零开始学Spring Boot】-1.第一个Spring Boot应用
1.Spring Boot 启动流程是怎样的?
从 main 方法开始
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
SpringApplication.run -> ConfigurableApplicationContext
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 设置 “java.awt.headless” 属性
configureHeadlessProperty();
// 使用 SpringFactoryLoader 获取 SpringApplicationRunListener 实例的 listeners
SpringApplicationRunListeners listeners = getRunListeners(args);
// 逐个启动 SpringApplicationRunListener,应用开始启动事件
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 获取 Environment,根据 webType 获取不同类型;并配置 propertySource 和 profiles
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印 banner
Banner printedBanner = printBanner(environment);
// 创建 spring 应用上下文,类型和 webType 有关
context = createApplicationContext();
// 使用 SpringFactoryLoader 获取 SpringBootExceptionReporter 实例的 exceptionReporters
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 上下文预处理,spring boot
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文,spring context
refreshContext(context);
// 上下文后置处理,暂为空
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// listeners 启动完成事件
listeners.started(context);
// 触发 ApplicationRunner 和 CommandLineRunner
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;
}
2.Spring Boot 如何加载 Tomcat?
SpringApplication.run -> ConfigurableApplicationContext ,跟踪 tomcat 的创建过程,主要看 createApplicationContext() 和 refreshContext() 方法
public ConfigurableApplicationContext run(String... args) {
// ...
try {
// ...
context = createApplicationContext();
// ...
refreshContext(context);
// ...
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
// ...
return context;
}
createApplicationContext() 创建 spring 应用
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
最终创建的应用类型和 webApplicationType 有关,webApplicationType 在 SpringApplication 的构造函数中进行实例化
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();
}
默认类型为 WebApplicationType.SERVLET
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
在 refreshContext 中调用了 refresh(context) 方法,这里的 applicationContext 为 AnnotationConfigServletWebServerApplicationContext
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
然后调用 AbstractApplicationContext.refresh() 方法
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
// ...
try {
// ...
this.onRefresh();
// ...
} catch (BeansException var9) {
// ...
} finally {
this.resetCommonCaches();
}
}
}
onRefresh() 是一个钩子方法,根据上面分析,这里使用的是 servlet,所以会调用到
ServletWebServerApplicationContext.onRefresh()
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
在 ServletWebServerApplicationContext 中调用 createWebServer() 创建 web 服务
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
这里的 factory 通过 ServletWebServerFactory 来实例化,所以创建 ServletWebServer
最终在 TomcatServletWebServerFactory 中创建了 Tomcat、Connector、Engine、Host 等,这里可以结合 apache-tomcat 的配置文件 server.xml 来分析几者之间的层级关系
3.Spring Boot 默认容器为何是 tomcat?
先看下 spring-boot-starter-web 的依赖结构
spring-boot-starter-web 依赖了 spring-boot-starter-tomcat,又依赖了 tomcat-embed-core。但是只凭这个,并不能说明默认容器为 tomca 的原因。
要弄清这个问题,就要涉及到 Spring Boot 的自动装配,以及 WebServer 的装配。
@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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
// ...
}
其中 @EnableAutoConfiguration 和自动装配相关,@EnableAutoConfiguration 声明如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// ...
}
@Import 导入一个 AutoConfigurationImportSelector,这个 Selector 中 selectImports() 实现如下
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
其中 getAutoConfigurationEntry 获取自动装配类型,它的实现如下
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// 是否开启自动装配,默认开启,可通过 spring.boot.enableautoconfiguration 进行配置
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取元注解中的属性,它是一个 LinkedHashMap
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取要自动装配类的类名
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// 要排除的装配类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 过滤
configurations = filter(configurations, autoConfigurationMetadata);
// 触发自动装配导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
在 getCandidateConfigurations 获取所有自动装配类,这个方法通过 SpringFactoriesLoader 加载 META-INF/spring.factories 中的内容,在 spring-boot-autoconfigure 的 spring.factories 中有如下内容
其中有一条配置为 EmbeddedWebServerFactoryCustomizerAutoConfiguration,这个即为内容 webServer 的自动装配类
它的实现如下
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
public static class JettyWebServerFactoryCustomizerConfiguration {
@Bean
public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new JettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
public static class UndertowWebServerFactoryCustomizerConfiguration {
@Bean
public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
}
}
/**
* Nested configuration if Netty is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpServer.class)
public static class NettyWebServerFactoryCustomizerConfiguration {
@Bean
public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new NettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
}
结合上面 spring-boot-starter-web 中引入了 tomcat-embed-core 依赖,可以发现,默认装配的类型即为
TomcatWebServerFactoryCustomizerConfiguration