@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})}
)
public @interface SpringBootApplication{}
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@Configuration:代表当前是一个配置类
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@AutoConfigurationPackage:自动配置包
@Import:给容器中导入一个组件(Registrar.class),利用Registrar给容器中导入一系列组件;
registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)方法中metadata代表注解的元信息,我们这个这个注解是
@AutoConfigurationPackage
;registerBeanDefinitions方法体的内部有一行代码getPackageNames()
代表获取元注解的包信息,所以我们拿到的包信息就是Springboot的main方法的包路径;
public class AutoConfigurationPackages{
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
}
所以,@Import 注解最终实现的结果就是将启动类路径下的包和子包,全部扫描进容器中;(全部扫描不意味着全部加载,能够被加载需要满足加载所需的条件
)
step1
:
public class AutoConfigurationImportSelector{
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
}
step2
:
利用 this.getAutoConfigurationEntry(annotationMetadata);
给容器中批量导入一些组件
public class AutoConfigurationImportSelector{
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
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);
}
}
step3
:
getCandidateConfigurations(annotationMetadata, attributes);
获取所有导入到容器中的配置类,(全部导入不意味着全部加载,能够被加载需要满足加载所需的条件
)
public class AutoConfigurationImportSelector{
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
}
step4
:
SpringFactoriesLoader.loadFactoryNames()
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
getOrDefault()
public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>{}
public interface MultiValueMap<K, V> extends Map<K, List<V>> {}
public class Map{
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
}
step5
:
loadSpringFactories()
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
//.........
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
step6
:加载到全部需加载的配置后,回到step2
,对配置进行条件判断,判断是否能够成功加载;
@ComponentScan:指定扫描的包路径
官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/web.html#web.servlet.spring-mvc.static-content
默认情况下,Spring Boot 从类路径中名为
/static
(或/public
或/resources
或/META-INF/resources
)的目录或从ServletContext
. 它使用ResourceHttpRequestHandler
来自 Spring MVC 的方法,因此您可以通过添加自己的方法WebMvcConfigurer
并覆盖该addResourceHandlers
方法来修改该行为。在独立的 Web 应用程序中,容器中的默认 servlet 也被启用并充当后备,
ServletContext
如果 Spring 决定不处理它,则从根目录提供内容。大多数情况下,这不会发生(除非你修改了默认的 MVC 配置),因为 Spring 总是可以通过DispatcherServlet
.默认情况下,资源映射在 上
/**
,但您可以使用该spring.mvc.static-path-pattern
属性对其进行调整。例如,将所有资源重新定位到/resources/**
可以实现如下:spring.mvc.static-path-pattern=/resources/**
您还可以使用该
spring.web.resources.static-locations
属性自定义静态资源位置(将默认值替换为目录位置列表)。根 servlet 上下文路径"/"
也会自动添加为位置。
Q:
静态资源的访问和Controller层的调用顺序是怎么样的?(当Controller层有和静态资源同名的路径,会访问Controller 还是静态资源?)先访问Controller,然后再访问静态资源;
Q:
如何修改默认的静态资源访问路径?spring.resources.static-locations=classpath:/wddong
spring.mvc.static-path-pattern=/resources/** # 访问方式为 当前项目+ `/resources` + 静态资源名 = 静态资源
类的包路径:\repository\org\springframework\boot\spring-boot-autoconfigure\2.2.13.RELEASE\spring-boot-autoconfigure-2.2.13.RELEASE-sources.jar!\org\springframework\boot\autoconfigure\web\servlet\WebMvcAutoConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
}
WebMvcProperties
与配置文件中的spring.mvc
进行了绑定;
ResourceProperties
与配置文件中的spring.resources
进行了绑定;
spring:
mvc:
static-path-pattern: /wddong/**
resources:
static-locations: classpath:/wddong
cache:
period: 100
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
private String staticPathPattern = "/**";
}
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
}
WebMvcAutoConfiguration
中的静态内部类WebMvcAutoConfigurationAdapter
只有一个有参构造器;并且为
@Configuration 被Spring管理,意味着方法的形参值都是从Spring中获取;
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
private final WebMvcProperties mvcProperties;
private final ListableBeanFactory beanFactory;
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
public WebMvcAutoConfigurationAdapter(WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
ObjectProvider<HttpMessageConverters> messageConvertersProvider) {
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
}
}
WebMvcAutoConfiguration
中的静态内部类EnableWebMvcConfiguration
只有一个有参构造器;同理如上;
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
//有参构造方法
public EnableWebMvcConfiguration(ResourceProperties resourceProperties,
ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ListableBeanFactory beanFactory) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.beanFactory = beanFactory;
}
//资源处理的方法
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(),
this.resourceProperties.getStaticLocations());
}
}
public static class EnableWebMvcConfiguration {
private final ResourceProperties resourceProperties;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//调用父类方法,添加资源处理器
super.addResourceHandlers(registry);
//resourceProperties为上文中,提到的映射 配置文件中的spring.resources属性
//判断配置文件中的isAddMappings,当配置文件中配置isAddMappings=false;下面的代码不执行
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//webjars的相关规则
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
//mvcProperties为上文中,提到的映射 配置文件中的spring.mvc属性
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(),
this.resourceProperties.getStaticLocations());
//resourceProperties为上文中,提到的映射 配置文件中的spring.resources属性
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
if (registry.hasMappingForPattern(pattern)) {
return;
}
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
registration.addResourceLocations(locations);
//设置缓存时间
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
customizeResourceHandlerRegistration(registration);
this.autoConfiguredResourceHandlers.add(pattern);
}
}
addResourceHandlers()
解析:
step1
:调用父类方法,添加资源处理器
step2
:resourceProperties对应为ResourceProperties类,ResourceProperties从配置文件spring.resources
中获取属性;判断配置文件中的isAddMappings,当配置文件中配置isAddMappings=false;下面的代码不执行
step3
:配置webjars的相关规则;
step4
:获取mvcProperties的相关配置;mvcProperties对应为WebMvcProperties类,WebMvcProperties从配置文件spring.mvc
中获取属性getStaticLocations()
;当配置文件中没有配置static-locations的相关信息时,加载的即为系统默认的配置,如下图;
step5
:当配置文件中配置有static-locations的相关信息时,加载的配置的文件信息,替换系统默认的配置;
SpringBoot如何自动配置欢迎页为index.html
public static class EnableWebMvcConfiguration{
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
}
final class WelcomePageHandlerMapping{
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
if (welcomePage != null && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage);
setRootViewName("forward:index.html");
}
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
}
同一种类型的.yml的配置文件加载顺序优先于.properties的配置文件
bootstrap (.yml 或者 .properties),application (.yml 或者 .properties);
在 Spring Boot 中有两种上下文,一种是 bootstrap, 另外一种是 application, bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。
加载位置和加载顺序均为数字编号,先被加载的配置会被后续加载的同名配置所覆盖
SpringBoot
内嵌Tomcat的流程SpringBoot与Tomcat的启动(内嵌Tomcat)
通过代码详细分析上图的流程
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
//根据run方法得到以下
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
//再次跟进run方法得到以下
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
分为了两部分part1
:(new SpringApplication(primarySources))
``part2:
.run(args),首先看
part1`
//part1部分
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
//本方法内部推断了当前Web应用类型以及启动类
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
....
//推断当前web容器类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
....
// 推断当前工程主启动类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
//可以看到SpringApplication构造方法通过 WebApplicationType.deduceFromClasspath 方法确定Web容器的类型webApplicationType(NONE,SERVLET,REACTIVE;最终会得到这三种类型其中一种)
接下来看part2
:.run(args)
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
.....
try {
...
//创建ApplicationContext容器
context = this.createApplicationContext();
...
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新容器
this.refreshContext(context);
....
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
.....
}
注意看refreshContext
(刷新容器的方法)
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
里面调用的refresh()
,如果对Spring的启动流程有了解的话,应该知道Spring启动过程中,最重要的就是AbstractApplicationContext#refresh()
过程,在这个方法中Spring执行了BeanFactory
的初始化,Bean的实例化、属性填充、初始化操作等等重要的操作
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
....
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
....
// Initialize other special beans in specific context subclasses.
onRefresh();
....
} catch (BeansException ex) {
...
} finally {
...
}
}
}
}
//两个方法在spring中均是空实现。
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
protected void onRefresh() throws BeansException {
}
如上spring的refresh
方法,其他的模板方法已省略,保留了两个spring
交给子类个性化定制的方法。
在step2
中。构造SpringApplicaton
时,已经推断出当前Web
工程类型,当开始执行#run
方法时,会根据不同类型的Web项目创建不同类型的ApplicationContext
。然后接着会执行 this.refreshContext(context);
刷新容器的方法,所以上面模板方法,会调用子类中个性化定制的方法。所以确定好子类,找到子类的实现方法就好了。
//part2中创建ApplicationContext容器
// context = this.createApplicationContext();
// 根据deduceWebApplicationType推断出来的webApplicationType
// 选择不同类型的ApplicationContext创建
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
//根据前面得到的webApplicationType创建ApplicationContext类型、
switch (this.webApplicationType) {
case SERVLET:
// 本文将会根据此行代码创建容器对象
// 类型:AnnotationConfigServletWebServerApplicationContext
// 继承自:ServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_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) {
// ... 省略异常处理
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
接着执行refreshContext
方法,来到前面根据webApplicationType
创建的容器类ServletWebServerApplicationContext
。找到对应的实现。
public class ServletWebServerApplicationContext
extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
// 重点是这里,它重写了AbstractApplicationContext的onRefresh
// 并且在这类创建了一个web服务器
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
} catch (Throwable ex) {
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 通过Spring.factories配置自动装配类
// ServletWebServerFactoryAutoConfiguration
// 该类通过Import引入ServletWebServerFactoryConfiguration#EmbeddedTomcat
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
} else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
} catch (ServletException ex) {
}
}
initPropertySources();
}
}
进入到TomcatServletWebServerFactory
,可以看到如下启动内置Tomcat的过程
public class TomcatServletWebServerFactory
extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
// 启动Tomcat
return new TomcatWebServer(tomcat, getPort() >= 0);
}
}
public class TomcatWebServer implements WebServer {
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
private void initialize() throws WebServerException {
synchronized (this.monitor) {
try {
...
// Start the server to trigger initialization listeners
this.tomcat.start(); // 启动Tomcat
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
....
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
// 阻塞当前Tomcat线程,否则Tomcat就直接退出了
startDaemonAwaitThread();
} catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
}
至此,Tomcat继承Spring的AbstractApplicationContext类,覆盖它的模板方法onRefresh,SpringBoot在自身启动的过程中,启动了内置的Tomcat服务器