Spring Boot默认使用Tomcat作为嵌入式的Servlet容器,只要引入了spring-boot-start-web依赖,则默认是用Tomcat作为Servlet容器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Servlet容器的使用
默认servlet容器
我们看看spring-boot-starter-web这个starter中有什么
核心就是引入了tomcat和SpringMvc,我们先来看tomcat
Spring Boot默认支持Tomcat,Jetty,和Undertow作为底层容器。如图:
而Spring Boot默认使用Tomcat,一旦引入spring-boot-starter-web模块,就默认使用Tomcat容器。
切换servlet容器
那如果我么想切换其他Servlet容器呢,只需如下两步:
- 将tomcat依赖移除掉
- 引入其他Servlet容器依赖
引入jetty:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<!--移除spring-boot-starter-web中的tomcat-->
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<!--引入jetty-->
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Servlet容器自动配置原理
EmbeddedServletContainerAutoConfiguration
其中 EmbeddedServletContainerAutoConfiguration 是嵌入式Servlet容器的自动配置类,该类在 spring-boot-autoconfigure.jar中的web模块 可以找到。
我们可以看到 EmbeddedServletContainerAutoConfiguration被配置在spring.factories中, 看过我前面文章的朋友应该知道SpringBoot自动配置的原理,这里将EmbeddedServletContainerAutoConfiguration配置类加入到IOC容器中,接着我们来具体看看这个配置类:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication// 在Web环境下才会起作用
@Import(BeanPostProcessorsRegistrar.class)// 会Import一个内部类BeanPostProcessorsRegistrar
public class EmbeddedServletContainerAutoConfiguration {
@Configuration
// Tomcat类和Servlet类必须在classloader中存在
// 文章开头我们已经导入了web的starter,其中包含tomcat和SpringMvc
// 那么classPath下会存在Tomcat.class和Servlet.class
@ConditionalOnClass({ Servlet.class, Tomcat.class })
// 当前Spring容器中不存在EmbeddedServletContainerFactory类型的实例
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
// 上述条件注解成立的话就会构造TomcatEmbeddedServletContainerFactory这个EmbeddedServletContainerFactory
return new TomcatEmbeddedServletContainerFactory();
}
}
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
//other code...
}
在这个自动配置类中配置了三个容器工厂的Bean,分别是:
- TomcatEmbeddedServletContainerFactory
- JettyEmbeddedServletContainerFactory
- UndertowEmbeddedServletContainerFactory
这里以大家熟悉的Tomcat为例,首先Spring Boot会判断当前环境中是否引入了Servlet和Tomcat依赖,并且当前容器中没有自定义的 EmbeddedServletContainerFactory 的情况下,则创建Tomcat容器工厂。其他Servlet容器工厂也是同样的道理。
EmbeddedServletContainerFactory
- 嵌入式Servlet容器工厂
public interface EmbeddedServletContainerFactory {
EmbeddedServletContainer getEmbeddedServletContainer ( ServletContextInitializer... initializers);
}
内部只有一个方法,用于获取嵌入式的Servlet容器。
该工厂接口主要有三个实现类,分别对应三种嵌入式Servlet容器的工厂类,如图所示:
TomcatEmbeddedServletContainerFactory
以Tomcat容器工厂TomcatEmbeddedServletContainerFactory类为例:
public class TomcatEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
//other code...
@Override
public EmbeddedServletContainer getEmbeddedServletContainer ( ServletContextInitializer... initializers) {
//创建一个Tomcat
Tomcat tomcat = new Tomcat();
//配置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);
//包装tomcat对象,返回一个嵌入式Tomcat容器,内部会启动该tomcat容器
return getTomcatEmbeddedServletContainer(tomcat);
}
}
首先会创建一个Tomcat的对象,并设置一些属性配置,最后调用 getTomcatEmbeddedServletContainer(tomcat)方法,内部会启动tomcat, 我们来看看:
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0 );
}
该函数很简单,就是来创建Tomcat容器并返回。看看TomcatEmbeddedServletContainer类:
public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
//初始化嵌入式Tomcat容器,并启动Tomcat
initialize();
}
private void initialize() throws EmbeddedServletContainerException {
TomcatEmbeddedServletContainer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
try {
final Context context = findContext();
context.addLifecycleListener(new LifecycleListener() {
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol
// binding doesn't happen when the service is
// started.
removeServiceConnectors();
}
}
});
// Start the server to trigger initialization listeners
//启动tomcat
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, getNamingToken(context),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
containerCounter.decrementAndGet();
throw ex;
}
}
catch (Exception ex) {
stopSilently();
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat", ex);
}
}
}
}
到这里就启动了嵌入式的Servlet容器,其他容器类似。
Servlet容器启动原理
SpringBoot启动过程
我们回顾一下前面讲解的SpringBoot启动过程,也就是run方法:
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);
// 第二步:根据SpringApplicationRunListeners以及参数来准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);
// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = printBanner(environment);
// 第三步:创建Spring容器
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 第四步:Spring容器前置处理
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
// 第五步:刷新容器
refreshContext(context);
// 第六步:Spring容器后置处理
afterRefresh(context, applicationArguments);
// 第七步:发出结束执行的事件
listeners.started(context);
// 第八步:执行Runners
this.callRunners(context, applicationArguments);
stopWatch.stop();
// 返回容器
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
}
我们回顾一下 第三步:创建Spring容器
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.context.embedded. AnnotationConfigEmbeddedWebApplicationContext ";
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
//根据应用环境,创建不同的IOC容器
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
创建IOC容器,如果是web应用,则创建 AnnotationConfigEmbeddedWebApplicationContext 的IOC容器;如果不是,则创建AnnotationConfigApplicationContext的IOC容器;很明显我们创建的容器是AnnotationConfigEmbeddedWebApplicationContext , 接着我们来看看 第五步,刷新容器 refreshContext(context);
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//调用容器的refresh()方法刷新容器
((AbstractApplicationContext) applicationContext).refresh();
}
容器刷新过程
调用抽象父类AbstractApplicationContext的 refresh ()方法;
AbstractApplicationContext
1 public void refresh() throws BeansException, IllegalStateException {
2 synchronized (this.startupShutdownMonitor) {
3 /**
4 * 刷新上下文环境
5 */
6 prepareRefresh();
7
8 /**
9 * 初始化BeanFactory,解析XML,相当于之前的XmlBeanFactory的操作,
10 */
11 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
12
13 /**
14 * 为上下文准备BeanFactory,即对BeanFactory的各种功能进行填充,如常用的注解@Autowired @Qualifier等
15 * 添加ApplicationContextAwareProcessor处理器
16 * 在依赖注入忽略实现*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
17 * 注册依赖,如一个bean的属性中含有ApplicationEventPublisher(beanFactory),则会将beanFactory的实例注入进去
18 */
19 prepareBeanFactory(beanFactory);
20
21 try {
22 /**
23 * 提供子类覆盖的额外处理,即子类处理自定义的BeanFactoryPostProcess
24 */
25 postProcessBeanFactory(beanFactory);
26
27 /**
28 * 激活各种BeanFactory处理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
29 * 执行对应的postProcessBeanDefinitionRegistry方法 和 postProcessBeanFactory方法
30 */
31 invokeBeanFactoryPostProcessors(beanFactory);
32
33 /**
34 * 注册拦截Bean创建的Bean处理器,即注册BeanPostProcessor,不是BeanFactoryPostProcessor,注意两者的区别
35 * 注意,这里仅仅是注册,并不会执行对应的方法,将在bean的实例化时执行对应的方法
36 */
37 registerBeanPostProcessors(beanFactory);
38
39 /**
40 * 初始化上下文中的资源文件,如国际化文件的处理等
41 */
42 initMessageSource();
43
44 /**
45 * 初始化上下文事件广播器,并放入applicatioEventMulticaster,如ApplicationEventPublisher
46 */
47 initApplicationEventMulticaster();
48
49 /**
50 * 给子类扩展初始化其他Bean
51 */
52 onRefresh();
53
54 /**
55 * 在所有bean中查找listener bean,然后注册到广播器中
56 */
57 registerListeners();
58
59 /**
60 * 设置转换器
61 * 注册一个默认的属性值解析器
62 * 冻结所有的bean定义,说明注册的bean定义将不能被修改或进一步的处理
63 * 初始化剩余的非惰性的bean,即初始化非延迟加载的bean
64 */
65 finishBeanFactoryInitialization(beanFactory);
66
67 /**
68 * 通过spring的事件发布机制发布ContextRefreshedEvent事件,以保证对应的监听器做进一步的处理
69 * 即对那种在spring启动后需要处理的一些类,这些类实现了ApplicationListener<ContextRefreshedEvent>,
70 * 这里就是要触发这些类的执行(执行onApplicationEvent方法)
71 * spring的内置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
72 * 完成初始化,通知生命周期处理器lifeCycleProcessor刷新过程,同时发出ContextRefreshEvent通知其他人
73 */
74 finishRefresh();
75 }
76
77 finally {
78
79 resetCommonCaches();
80 }
81 }
82 }
我们看第52行的方法:
protected void onRefresh() throws BeansException {
}
很明显抽象父类AbstractApplicationContext中的onRefresh是一个空方法,并且使用protected修饰,也就是其子类可以重写onRefresh方法,那我们看看其子类AnnotationConfigEmbeddedWebApplicationContext中的onRefresh方法是如何重写的,AnnotationConfigEmbeddedWebApplicationContext又继承EmbeddedWebApplicationContext,如下:
public class AnnotationConfigEmbeddedWebApplicationContext extends EmbeddedWebApplicationContext {
那我们看看其父类EmbeddedWebApplicationContext 是如何重写onRefresh方法的:
EmbeddedWebApplicationContext
@Override
protected void onRefresh() {
super.onRefresh();
try {
//核心方法:会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器
createEmbeddedServletContainer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container", ex);
}
}
在createEmbeddedServletContainer方法中会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器:
1 private void createEmbeddedServletContainer() {
2 EmbeddedServletContainer localContainer = this.embeddedServletContainer;
3 ServletContext localServletContext = getServletContext();
4 if (localContainer == null && localServletContext == null) {
5 //先获取嵌入式Servlet容器工厂
6 EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
7 //根据容器工厂来获取对应的嵌入式Servlet容器
8 this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
9 }
10 else if (localServletContext != null) {
11 try {
12 getSelfInitializer().onStartup(localServletContext);
13 }
14 catch (ServletException ex) {
15 throw new ApplicationContextException("Cannot initialize servlet context",ex);
16 }
17 }
18 initPropertySources();
19 }
关键代码在第6和第8行, 先获取Servlet容器工厂,然后 根据容器工厂来获取对应的嵌入式Servlet容器
获取Servlet容器工厂
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
//从Spring的IOC容器中获取EmbeddedServletContainerFactory.class类型的Bean
String[] beanNames = getBeanFactory().getBeanNamesForType(EmbeddedServletContainerFactory.class);
//调用getBean实例化EmbeddedServletContainerFactory.class
return getBeanFactory().getBean(beanNames[0], EmbeddedServletContainerFactory.class );
}
我们看到先从Spring的IOC容器中获取EmbeddedServletContainerFactory.class类型的Bean,然后调用getBean实例化EmbeddedServletContainerFactory.class,大家还记得我们第一节Servlet容器自动配置类EmbeddedServletContainerAutoConfiguration中注入Spring容器的对象是什么吗?当我们引入spring-boot-starter-web这个启动器后,会注入 TomcatEmbeddedServletContainerFactory 这个对象到Spring容器中,所以这里获取到的 Servlet容器工厂是 TomcatEmbeddedServletContainerFactory ,然后调用
TomcatEmbeddedServletContainerFactory的getEmbeddedServletContainer方法获取Servlet容器,并且启动Tomcat,大家可以看看文章开头的getEmbeddedServletContainer方法。
大家看一下第8行代码获取Servlet容器方法的参数getSelfInitializer(),这是个啥?我们点进去看看
private ServletContextInitializer getSelfInitializer() {
//创建一个ServletContextInitializer对象,并重写onStartup方法,很明显是一个回调方法
return new ServletContextInitializer() {
public void onStartup(ServletContext servletContext) throws ServletException {
EmbeddedWebApplicationContext.this .selfInitialize(servletContext);
}
};
}
创建一个ServletContextInitializer对象,并重写onStartup方法,很明显是一个回调方法,这里给大家留一点疑问:
- ServletContextInitializer对象创建过程是怎样的?
- onStartup是何时调用的?
- onStartup方法的作用是什么?
ServletContextInitializer
是 Servlet 容器初始化的时候,提供的初始化接口。这里涉及到Servlet、Filter实例的注册,我们留在下一篇具体讲
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。