文章目录
- 前言
- 一、Springboot的Web场景启动器
- 二、Tomcat启动及DispatcherServlet装配过程
-
- 1.启动和装配流程图
- 2.关键各步骤说明
-
- 2.1.应用启动和自动装配导入
- 2.2.初始化上下文子类的特殊bean-ServletWebServerApplicationContext.onRefresh()
- 2.3.TomcatWebServer工厂-TomcatServletWebServerFactory.getWebServer()
- 2.4.创建TomcatWebServer实例-TomcatWebServer.initialize()
- 2.5.Tomcat启动配置-TomcatStarter.onStartup()
- 2.6.初始化并添加DispatcherServlet-DispatcherServletRegistrationBean.onStartup()
- 2.7.完成上下文的最后刷新-AbstractApplicationContext.finishRefresh();
- 2.8.生命周期处理器刷新上下文-DefaultLifecycleProcessor.onRefresh();
- 2.9.WebServer生命周期管理器启动管理的TomcatWebServer-WebServerStartStopLifecycle.start()
- 2.10.Tomcat服务启动-TomcatWebServer.start()
- 总结
前言
SpringBoot是一个框架,一种全新的编程规范,他的产生简化了框架的使用,同时也提供了很多便捷的功能,比如内置tomcat就是其中一项,他让我们省去了搭建tomcat容器,生成war,部署,启动tomcat。那么内置tomcat是如何启动以及DispatcherServlet在此过程中如何装配的?接下来我们就透过源代码来把握它的来龙去脉。以下所有内容都是基于SpringBoot-2.7.6版本来的,版本不同流程或许会有差异,在参照本文调试源码时要注意本地的SpringBoot版本。
一、Springboot的Web场景启动器
1.场景启动器
- SpringBoot的Web场景启动器为spring-boot-starter-web,该场景启动器中包含了内置tomcat的场景启动器“spring-boot-starter-tomcat”、SpringWeb及SpringMVC的依赖,maven信息如下。场景启动器引入后会启用DispatcherServletAutoConfiguration、ServletWebServerFactoryAutoConfiguration的自动配置,装配TomcatServletWebServerFactory和DispatcherServletRegistrationBean等。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.7.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.7.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.24</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.24</version>
<scope>compile</scope>
</dependency>
</dependencies>
2.关键组件
2.1.WebServer工厂-TomcatServletWebServerFactory
可用于创建TomcatWebServers。可以使用Spring的Servlet ContextInitializers或Tomcat LifecycleListeners进行初始化。
-
继承关系图
-
源码
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.embedded.tomcat;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Manager;
import org.apache.catalina.Valve;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot.ResourceSetType;
import org.apache.catalina.WebResourceSet;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.apache.catalina.util.LifecycleBase;
import org.apache.catalina.util.SessionConfig;
import org.apache.catalina.webresources.AbstractResourceSet;
import org.apache.catalina.webresources.EmptyResource;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http2.Http2Protocol;
import org.apache.tomcat.util.http.Rfc6265CookieProcessor;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.scan.StandardJarScanFilter;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.boot.web.server.Cookie.SameSite;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.MimeMappings;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.NativeDetector;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link AbstractServletWebServerFactory} that can be used to create
* {@link TomcatWebServer}s. Can be initialized using Spring's
* {@link ServletContextInitializer}s or Tomcat {@link LifecycleListener}s.
* <p>
* Unless explicitly configured otherwise this factory will create containers that listen
* for HTTP requests on port 8080.
*
* @author Phillip Webb
* @author Dave Syer
* @author Brock Mills
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Christoffer Sawicki
* @author Dawid Antecki
* @since 2.0.0
* @see #setPort(int)
* @see #setContextLifecycleListeners(Collection)
* @see TomcatWebServer
*/
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private static final Set<Class<?>> NO_CLASSES = Collections.emptySet();
/**
* The class name of default protocol used.
*/
public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
private File baseDirectory;
private List<Valve> engineValves = new ArrayList<>();
private List<Valve> contextValves = new ArrayList<>();
private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>();
private List<LifecycleListener> serverLifecycleListeners = getDefaultServerLifecycleListeners();
private Set<TomcatContextCustomizer> tomcatContextCustomizers = new LinkedHashSet<>();
private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();
private Set<TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizers = new LinkedHashSet<>();
private final List<Connector> additionalTomcatConnectors = new ArrayList<>();
private ResourceLoader resourceLoader;
private String protocol = DEFAULT_PROTOCOL;
private Set<String> tldSkipPatterns = new LinkedHashSet<>(TldPatterns.DEFAULT_SKIP);
private Set<String> tldScanPatterns = new LinkedHashSet<>(TldPatterns.DEFAULT_SCAN);
private Charset uriEncoding = DEFAULT_CHARSET;
private int backgroundProcessorDelay;
private boolean disableMBeanRegistry = true;
/**
* Create a new {@link TomcatServletWebServerFactory} instance.
*/
public TomcatServletWebServerFactory() {
}
/**
* Create a new {@link TomcatServletWebServerFactory} that listens for requests using
* the specified port.
* @param port the port to listen on
*/
public TomcatServletWebServerFactory(int port) {
super(port);
}
/**
* Create a new {@link TomcatServletWebServerFactory} with the specified context path
* and port.
* @param contextPath the root context path
* @param port the port to listen on
*/
public TomcatServletWebServerFactory(String contextPath, int port) {
super(contextPath, port);
}
private static List<LifecycleListener> getDefaultServerLifecycleListeners() {
ArrayList<LifecycleListener> lifecycleListeners = new ArrayList<>();
if (!NativeDetector.inNativeImage()) {
AprLifecycleListener aprLifecycleListener = new AprLifecycleListener();
if (AprLifecycleListener.isAprAvailable()) {
lifecycleListeners.add(aprLifecycleListener);
}
}
return lifecycleListeners;
}
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
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);
}
private void configureEngine(Engine engine) {
engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
for (Valve valve : this.engineValves) {
engine.getPipeline().addValve(valve);
}
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
context.setPath(getContextPath());
File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
: ClassUtils.getDefaultClassLoader());
resetDefaultLocaleMapping(context);
addLocaleMappings(context);
try {
context.setCreateUploadTargets(true);
}
catch (NoSuchMethodError ex) {
// Tomcat is < 8.5.39. Continue.
}
configureTldPatterns(context);
WebappLoader loader = new WebappLoader();
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true);
context.setLoader(loader);
if (isRegisterDefaultServlet()) {
addDefaultServlet(context);
}
if (shouldRegisterJspServlet()) {
addJspServlet(context);
addJasperInitializer(context);
}
context.addLifecycleListener(new StaticResourceConfigurer(context));
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
configureContext(context, initializersToUse);
postProcessContext(context);
}
/**
* Override Tomcat's default locale mappings to align with other servers. See
* {@code org.apache.catalina.util.CharsetMapperDefault.properties}.
* @param context the context to reset
*/
private void resetDefaultLocaleMapping(TomcatEmbeddedContext context) {
context.addLocaleEncodingMappingParameter(Locale.ENGLISH.toString(), DEFAULT_CHARSET.displayName());
context.addLocaleEncodingMappingParameter(Locale.FRENCH.toString(), DEFAULT_CHARSET.displayName());
context.addLocaleEncodingMappingParameter(Locale.JAPANESE.toString(), DEFAULT_CHARSET.displayName());
}
private void addLocaleMappings(TomcatEmbeddedContext context) {
getLocaleCharsetMappings().forEach(
(locale, charset) -> context.addLocaleEncodingMappingParameter(locale.toString(), charset.toString()));
}
private void configureTldPatterns(TomcatEmbeddedContext context) {
StandardJarScanFilter filter = new StandardJarScanFilter();
filter.setTldSkip(StringUtils.collectionToCommaDelimitedString(this.tldSkipPatterns));
filter.setTldScan(StringUtils.collectionToCommaDelimitedString(this.tldScanPatterns));
context.getJarScanner().setJarScanFilter(filter);
}
private void addDefaultServlet(Context context) {
Wrapper defaultServlet = context.createWrapper();
defaultServlet.setName("default");
defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
defaultServlet.addInitParameter("debug", "0");
defaultServlet.addInitParameter("listings", "false");
defaultServlet.setLoadOnStartup(1);
// Otherwise the default location of a Spring DispatcherServlet cannot be set
defaultServlet.setOverridable(true);
context.addChild(defaultServlet);
context.addServletMappingDecoded("/", "default");
}
private void addJspServlet(Context context) {
Wrapper jspServlet = context.createWrapper();
jspServlet.setName("jsp");
jspServlet.setServletClass(getJsp().getClassName());
jspServlet.addInitParameter("fork", "false");
getJsp().getInitParameters().forEach(jspServlet::addInitParameter);
jspServlet.setLoadOnStartup(3);
context.addChild(jspServlet);
context.addServletMappingDecoded("*.jsp", "jsp");
context.addServletMappingDecoded("*.jspx", "jsp");
}
private void addJasperInitializer(TomcatEmbeddedContext context) {
try {
ServletContainerInitializer initializer = (ServletContainerInitializer) ClassUtils
.forName("org.apache.jasper.servlet.JasperInitializer", null).getDeclaredConstructor()
.newInstance();
context.addServletContainerInitializer(initializer, null);
}
catch (Exception ex) {
// Probably not Tomcat 8
}
}
// Needs to be protected so it can be used by subclasses
protected void customizeConnector(Connector connector) {
int port = Math.max(getPort(), 0);
connector.setPort(port);
if (StringUtils.hasText(getServerHeader())) {
connector.setProperty("server", getServerHeader());
}
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
}
invokeProtocolHandlerCustomizers(connector.getProtocolHandler());
if (getUriEncoding() != null) {
connector.setURIEncoding(getUriEncoding().name());
}
// Don't bind to the socket prematurely if ApplicationContext is slow to start
connector.setProperty("bindOnInit", "false");
if (getHttp2() != null && getHttp2().isEnabled()) {
connector.addUpgradeProtocol(new Http2Protocol());
}
if (getSsl() != null && getSsl