前言
Tomcat容器最主要作用就是监听网络请求,并将请求封装成request和response对象最终交给Servlet去执行,而我们spring项目中用来供servlet调用的业务代码的入口一般就是Spring管理的Controller。而Springboot整合tomcat的话,就需要提供一个Servlet的标准实现,并将这个Servlet注册到Tomcat容器里,并且在Servlet处理请求时,能调用Spring所管理的Controller进而使用Spring容器的所有资源。所以这里最主要的两个问题是:
1、Springboot是怎么创建和启动Tomcat的?
2、Springboot怎么将DispatcherServlet注册到tomcat容器中?
在分析问题前,先看下Tomcat的架构图,有助于后面看代码:
这里可以看出一个Tomcat就是一个Server,一个Server下会有多个Service,Service只负责封装多个Connector和一个Container(Service本身不是容器,可以看做只是用来包装Connector和Container的壳,不负责具体功能),而Container(也叫engine)下又有多个Host,每个Host下对应多个Context,Context下才是我们的Servlet,Tomcat为了使整个架构灵活,所以抽象出这么多层,每层之间都可以根据不同的维度产生一对多个配置。对应到Tomcat类:
再看下主要容器的类图,这里所有的容器都继承自LifecycleBase抽象类,这个类抽象了控制生命周期的方法,比如init、start、stop、destory方法,而负责处理请求的四个容器又都继承了ContainerBase抽象类,这个抽象类最主要是抽象了pipeline(可以理解为一个调用链,一个接下一个的处理)来处理请求的逻辑,所以请求到达这四个容器后,实际是通过pipeline来处理请求,类图如下:
一、Springboot是怎么创建和启动Tomcat的?
Springboot启动的主要方法是AbstractApplicationContext
类的refresh
方法,而springboot启动Tomcat容器的逻辑在ServletWebServerApplicationContext
类的onRefresh
方法里,这里从这个方法的代码开始debug:
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 1.0
ServletWebServerFactory factory = getWebServerFactory();
// 2.0
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();
}
1.0源码分析
/**
* Returns the {@link ServletWebServerFactory} that should be used to create the
* embedded {@link WebServer}. By default this method searches for a suitable bean in
* the context itself.
* @return a {@link ServletWebServerFactory} (never {@code null})
*/
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
这部分就是获取web容器的工厂类,这个工厂类是一个接口,先看下这个ServletWebServerFactory接口的实现类图:
可以看到有tomcat、Jetty、Undertow三种,那么这里get出来的具体是哪种?这个逻辑就在org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
这个类里,这个类是springboot容器初始化时自动转载进容器的,springboot自动加载的原理在这里,在spring-boot-autoconfigure包的spring.factories里配置了所有需要自动装载进容器的类,如下图:
这个类的作用就是告诉spring容器如何加载容器,这里先看下ServletWebServerFactoryAutoConfiguration
类内容截图如下:
这三个就是对于的Tomcat、Jetty、Undertow
容器的工厂配置类,这个@Import注解就是告诉spring容器,要把这几个工厂配置类加载进spring容器里,然后我们点进Tomcat的工厂配置类ServletWebServerFactoryConfiguration.EmbeddedTomcat.class
里看下:
首先看上面的@ConditionalOnClass和@ConditionalOnMissingBean注解,@ConditionalOnClass
注解就是告诉spring容器只有这些类存在的情况下才加载EmbeddedTomcat
这个对象以及解析这个类里的@Bean
注解,@ConditionalOnMissingBean
就是告诉spring容器只有没有这些bean的条件下才加载这个类,这里就可以看见只要我们springboot项目里有引入Tomcat依赖,有Tomcat这个类,那么就会加载Tomcat容器,所以如果我们的springboot想换成jetty容器,就需要将这些依赖给干掉,spring-boot-starter-web这个依赖里会自动把tomcat相关依赖打进来:
我这个项目里由于存在tomcat的依赖,所以会加载tomcat的工厂类TomcatServletWebServerFactory。关于@Import、@Bean等注解的解析逻辑可以看这里
2.0源码分析
由于返回的是Tomcat的工厂类TomcatServletWebServerFactory
,TomcatServletWebServerFactory
的getWebServer
方法会传入ServletContextInitializer
对象,ServletContextInitializer
的onStartup
方法会在Context容器被初始化后调用,作用是给ServletContext配置servlet、filter、listener等属性,可以理解为Context容器被初始化后,靠这个方法填充一些必要的属性,这也是开头第三个问题的关键步骤,后面再分析
this.webServer = factory.getWebServer(getSelfInitializer());
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
public WebServer getWebServer(ServletContextInitializer... initializers) {
// Mbean逻辑,和主逻辑无关,这里跳过,有兴趣的可以自己搜下Mbean
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// 生成新的Tomcat对象
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 生成connector,在connector构造器里会根据出入的protocol生成对应协议处理器,下面会分析构造器代码
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
// 将connector加到Service里,这个getService会创建一个Server
tomcat.getService().addConnector(connector);
// 定义Connector,配置Connector一些默认参数包括端口、协议等
customizeConnector(connector);
tomcat.setConnector(connector);
// getHost()方法里会生成一个Engine和一个Host对象
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 生成context容器,并注册到host容器中,同时为context容器生成一个默认的servlet,包装成wrapper注册到context容器中
prepareContext(tomcat.getHost(), initializers);
// 生成TomcatWebServer并启动该Server
return getTomcatWebServer(tomcat);
}
// 默认会生成一个StandardHost对象
public Host getHost() {
Engine engine = getEngine();
if (engine.findChildren().length > 0) {
return (Host) engine.findChildren()[0];
}
Host host = new StandardHost();
host.setName(hostname);
getEngine().addChild(host);
return host;
}
// 默认会生成一个Engine对象
public Engine getEngine() {
Service service = getServer().findServices()[0];
if (service.getContainer() != null) {
return service.getContainer();
}
Engine engine = new StandardEngine();
engine.setName( "Tomcat" );
engine.setDefaultHost(hostname);
engine.setRealm(createDefaultRealm());
service.setContainer(engine);
return engine;
}
这段代码在new Connector
时,会传入所能处理的协议,这个默认就是http1.1协议,会根据这个协议创建对应的处理该协议的handle,而Connector的start方法里,就会启动这个handle,而这个handle的start方法会启动一个NioEndpoint
对象,在这个NioEndpoint
对象的启动方法里就会启动一个Acceptor
,来监听端口上的socket
的accept事件,这个Acceptor
就是Tomcat
处理请求的总入口。
connector构造方法截图如下:
结合上面的Tomcat的架构图,在这个getWebServer
方法里,生成了所有容器,并且声称一个默认的servlet,下面开始分析是怎么启动的,并且在启动过程中是怎么将spring自己的Servlet注册到Context容器中的。
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
// 初始化并启动tomcat
initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((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
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), 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) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
这里debug进tomcat.start()方法里,会发现是启动tomcat对象里的server对象:
public void start() throws LifecycleException {
getServer();
server.start();
}
这个server对象是在上面的getWebServer
方法里的,tomcat.getService()
方法里生成的StandardServer类,可以看下这个类图,继承自LifecycleBase
抽象类,tomcat里的server、service、connector、engine、host、context、wrapper容器都会继承自这个抽象类,该类的start方法抽象了事件的通知逻辑
LifecycleBase
类start方法代码如下:
public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
// 如果容器的当前状态为新建状态,则进行init方法
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
// 如果容器的当前状态为失败状态,则进行stop方法
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
// 如果容器的当前状态为失败状态,则进行stop方法
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
// 设置容器状态,并发布事件
setStateInternal(LifecycleState.STARTING_PREP, null, false);
// 调用子类实现的start方法
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
Tomcat整个start方法的启动及初始化链如下:
springboot在构建并启动tomcat过程分为两个阶段:
- 第一阶段是初始化每一级的容器,但是并不启动Connector,原因是开启了connector就会开始监听端口上的socket链接,而此时Spring的bean都没有初始化完成,无法处理请求。
- 第二阶段就是当spring的所有bean都初始化完成后,启动connector。
以下截图是AbstractApplicationContext
的refresh方法,可以看出这个逻辑:
二、Springboot怎么将DispatcherServlet注册到tomcat容器中?
Springboot用来实现Servlet的类是DispatcherServlet,在spring-boot-autoconfigure包的spring.factories里配置了自动装载类DispatcherServletAutoConfiguration,在这个类里加载了DispatcherServlet这个bean:
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
return dispatcherServlet;
}
同样在这个类里加载了DispatcherServletRegistrationBean
,这个bean负责将servlet注册到tomcat中,可以看到在构造这个bean时,会将spring生成的DispatcherServlet
通过构造器传入到DispatcherServletRegistrationBean
中
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
这个类实现了ServletContextInitializer
接口,这个接口的作用就是在context容器初始化完成后通过onStartup方法为context添加servlet、filter等逻辑,类图如下:
那么这个类的触发流程图如下:
触发入口是在context容器的start方法里,在context容器的pipeline的start方法后,开始调用所有ServletContainerInitializer
的onStartup方法,而在ServletContainerInitializer
的实现类TomcatStarter的onStartup方法里,则是调用spring容器管理的ServletContextInitializer
的onStartup方法来实现DispatcherServlet
的注册,这里有课扩展点是我们也可以扩展自己的ServletContextInitializer
类.
下面是实现了ServletContainerInitializer
接口的TomcatStarter
类的onStartup方法debug截图:
下面是负责注册DispatcherServelt的实现了ServletContextInitializer接口的lambda表达式的代码debug截图,这段lambda表达式添加到TomcatStarter里的代码在上面