前言
最近一直在做项目改造,了解了一下spring boot的tomcat的运作机制,准备将Spring Boot的tomcat web迁移到现有框架中。
1. demo
pom & demo如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
</dependencies>
@SpringBootApplication
public class BootMain {
public static void main(String[] args) {
SpringApplication.run(BootMain.class, args);
}
}
Spring Boot在启动开始就会判断是servlet还是Spring normal还是reactive容器
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;
}
一般引入embed tomcat默认就是servlet
/**
* Strategy method used to create the {@link ApplicationContext}. By default this
* method will respect any explicitly set application context or application context
* class before falling back to a suitable default.
* @return the application context (not yet refreshed)
* @see #setApplicationContextClass(Class)
*/
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);
}
然后创建context,spring-boot-starter-web创建的是
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
implements AnnotationConfigRegistry {
注意继承ServletWebServerApplicationContext,这个context是创建embed tomcat的context
2. embed tomcat启动过程
在ServletWebServerApplicationContext的refresh的onRefresh方法中
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
跟踪
private void createWebServer() {
//首次创建容器 webServer与servletContext均为null
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
//使用BeanFactory创建ServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
//这里注意,非常核心
this.webServer = factory.getWebServer(getSelfInitializer());
}
//这里就是tomcat启动的spring boot启动流程
//servlet容器已经加载
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
2.1 初始化
跟踪getSelfInitializer,
这里定义了ServletContextInitializer ,这个ServletContextInitializer 是Spring Boot自己定义的初始化context,目的是添加servlet filter listener的功能,即SCI
函数式接口org.springframework.boot.web.servlet.ServletContextInitializer
package org.springframework.boot.web.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.web.SpringServletContainerInitializer;
import org.springframework.web.WebApplicationInitializer;
/**
* Interface used to configure a Servlet 3.0+ {@link ServletContext context}
* programmatically. Unlike {@link WebApplicationInitializer}, classes that implement this
* interface (and do not implement {@link WebApplicationInitializer}) will <b>not</b> be
* detected by {@link SpringServletContainerInitializer} and hence will not be
* automatically bootstrapped by the Servlet container.
* <p>
* This interface is primarily designed to allow {@link ServletContextInitializer}s to be
* managed by Spring and not the Servlet container.
* <p>
* For configuration examples see {@link WebApplicationInitializer}.
*
* @author Phillip Webb
* @since 1.4.0
* @see WebApplicationInitializer
*/
@FunctionalInterface
public interface ServletContextInitializer {
/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initialization.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;
}
/**
* Returns the {@link ServletContextInitializer} that will be used to complete the
* setup of this {@link WebApplicationContext}.
* @return the self initializer
* @see #prepareWebApplicationContext(ServletContext)
*/
//返回ServletContextInitializer对象,对象的onStartup方法引用selfInitialize方法
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);
}
}
这里::要注意,是一种函数式编程,是方法或者函数的引用,基于JDK8
ServletContextInitializer 函数式接口的
void onStartup(ServletContext servletContext) throws ServletException;
映射
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
void selfInitialize(ServletContext servletContext) throws ServletException
这个函数,是lambda表达式的体现,结果为返回ServletContextInitializer对象,对象的onStartup方法引用selfInitialize方法
跟踪this.webServer = factory.getWebServer(getSelfInitializer());
这里尤为重要,创建tomcat容器就在此处
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
//创建临时目录,比如笔者C:\Users\huahua\AppData\Local\Temp\tomcat.5826394772445150593.8080
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
//创建connector
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
//创建host,自动deploy false
tomcat.getHost().setAutoDeploy(false);
//创建引擎
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
//准备阶段,做很多事,详细分析
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
这里注意customizeConnector(Connector connector)
// Needs to be protected so it can be used by subclasses
protected void customizeConnector(Connector connector) {
int port = (getPort() >= 0) ? getPort() : 0;
connector.setPort(port);
if (StringUtils.hasText(this.getServerHeader())) {
connector.setAttribute("server", this.getServerHeader());
}
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
customizeProtocol((AbstractProtocol<?>) 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 (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(connector);
}
TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
compression.customize(connector);
for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
customizer.customize(connector);
}
}
embed的port端口就在这里设置connector
继续跟踪prepareContext(tomcat.getHost(), initializers);
十分关键,准备工作在此处,创建应用容器,初始化servlet,filter,listener
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
//创建应用容器servletContext
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);
context.setUseRelativeRedirects(false);
try {
context.setCreateUploadTargets(true);
}
catch (NoSuchMethodError ex) {
// Tomcat is < 8.5.39. Continue.
}
configureTldSkipPatterns(context);
WebappLoader loader = new WebappLoader(context.getParentClassLoader());
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);
//初始化servlet,filter,listener,相当于web.xml配置,SCI机制
configureContext(context, initializersToUse);
postProcessContext(context);
}
创建了TomcatEmbeddedContext context = new TomcatEmbeddedContext();添加一些路径,listener,jsp init classloader etc。
class TomcatEmbeddedContext extends StandardContext {
StandardContext 就是tomcat的子容器,child容器,里面有servletContext
@Override
public ServletContext getServletContext() {
if (context == null) {
context = new ApplicationContext(this);
if (altDDName != null)
context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
}
return context.getFacade();
}
跟踪ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
这里添加了一些常用的filter listener,当然最核心的是初始化的那个是DispartcherServlet,后面初始化的时候详细讲解
/**
* Utility method that can be used by subclasses wishing to combine the specified
* {@link ServletContextInitializer} parameters with those defined in this instance.
* @param initializers the initializers to merge
* @return a complete set of merged initializers (with the specified parameters
* appearing first)
*/
protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) {
List<ServletContextInitializer> mergedInitializers = new ArrayList<>();
mergedInitializers.add((servletContext) -> this.initParameters.forEach(servletContext::setInitParameter));
mergedInitializers.add(new SessionConfiguringInitializer(this.session));
mergedInitializers.addAll(Arrays.asList(initializers));
mergedInitializers.addAll(this.initializers);
return mergedInitializers.toArray(new ServletContextInitializer[0]);
}
然后host.addChild(context);十分关键,tomcat添加了一个host子容器,对应非embed tomcat增加一个host标签,webapp增加一个应用
跟踪configureContext(context, initializersToUse);
/**
* Configure the Tomcat {@link Context}.
* @param context the Tomcat context
* @param initializers initializers to apply
*/
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
//spring boot封装的SCI容器实现
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
//注意,添加了SCI,spring boot将Set设置为private static final Set<Class<?>> NO_CLASSES = Collections.emptySet();
context.addServletContainerInitializer(starter, NO_CLASSES);
for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
context.addLifecycleListener(lifecycleListener);
}
for (Valve valve : this.contextValves) {
context.getPipeline().addValve(valve);
}
for (ErrorPage errorPage : getErrorPages()) {
new TomcatErrorPage(errorPage).addToContext(context);
}
for (MimeMappings.Mapping mapping : getMimeMappings()) {
context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
}
configureSession(context);
new DisableReferenceClearingContextCustomizer().customize(context);
for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
customizer.customize(context);
}
}
TomcatStarter,tomcat ServletContainerInitializer 的spring boot实现,这里没有使用配置的方式,直接使用context.addServletContainerInitializer(starter, NO_CLASSES);来添加SCI
/**
* {@link ServletContainerInitializer} used to trigger {@link ServletContextInitializer
* ServletContextInitializers} and track startup errors.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class TomcatStarter implements ServletContainerInitializer {
private static final Log logger = LogFactory.getLog(TomcatStarter.class);
private final ServletContextInitializer[] initializers;
private volatile Exception startUpException;
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
//这里Set为空Set,因为Spring boot传的emptySet
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
//逐一启动spring boot自己定义的SCI对象
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
this.startUpException = ex;
// Prevent Tomcat from logging and re-throwing when we know we can
// deal with it in the main thread, but log for information here.
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
+ ex.getMessage());
}
}
}
public Exception getStartUpException() {
return this.startUpException;
}
}
然后
子容器添加SCI
回到org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
然后tomcat启动
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 {
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);
}
}
}
2.2 embed 启动
tomcat本身的启动,使用线程池,启动child context
启动过程中执行SCI逻辑
这个列表
首先执行
this.initParameters.forEach(servletContext::setInitParameter)
lambda表达式很难调试
然后执行SessionConfiguringInitializer
/**
* {@link ServletContextInitializer} to apply appropriate parts of the {@link Session}
* configuration.
*/
private static class SessionConfiguringInitializer implements ServletContextInitializer {
private final Session session;
SessionConfiguringInitializer(Session session) {
this.session = session;
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
if (this.session.getTrackingModes() != null) {
servletContext.setSessionTrackingModes(unwrap(this.session.getTrackingModes()));
}
configureSessionCookie(servletContext.getSessionCookieConfig());
}
private void configureSessionCookie(SessionCookieConfig config) {
Session.Cookie cookie = this.session.getCookie();
if (cookie.getName() != null) {
config.setName(cookie.getName());
}
if (cookie.getDomain() != null) {
config.setDomain(cookie.getDomain());
}
if (cookie.getPath() != null) {
config.setPath(cookie.getPath());
}
if (cookie.getComment() != null) {
config.setComment(cookie.getComment());
}
if (cookie.getHttpOnly() != null) {
config.setHttpOnly(cookie.getHttpOnly());
}
if (cookie.getSecure() != null) {
config.setSecure(cookie.getSecure());
}
if (cookie.getMaxAge() != null) {
config.setMaxAge((int) cookie.getMaxAge().getSeconds());
}
}
private Set<javax.servlet.SessionTrackingMode> unwrap(Set<Session.SessionTrackingMode> modes) {
if (modes == null) {
return null;
}
Set<javax.servlet.SessionTrackingMode> result = new LinkedHashSet<>();
for (Session.SessionTrackingMode mode : modes) {
result.add(javax.servlet.SessionTrackingMode.valueOf(mode.name()));
}
return result;
}
}
核心onStartup方法,这个方法在org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext,这样就和Spring applicationContext容器打通,即servletContext与ServletWebServerApplicationContext这个Spring容器同时可以获取到。
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);
}
}
prepareWebApplicationContext(servletContext);
protected void prepareWebApplicationContext(ServletContext servletContext) {
//rootContext是否存在
Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (rootContext != null) {
if (rootContext == this) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - "
+ "check whether you have multiple ServletContextInitializers!");
}
return;
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring embedded WebApplicationContext");
try {
//设置root context
servletContext.setAttribute
(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
//设置servletContext
setServletContext(servletContext);
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - getStartupDate();
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
最核心的方法,addServlet
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
getServletContextInitializerBeans()
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
构造函数
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializers = new LinkedMultiValueMap<>();
this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
: Collections.singletonList(ServletContextInitializer.class);
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
logMappings(this.initializers);
}
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
initializerType)) {
addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
}
}
}
可以看出getOrderedBeansOfType(beanFactory, initializerType),而initializerType是org.springframework.boot.web.servlet.ServletContextInitializer,get到DispatcherServletRegistrationBean
因为,在Spring Boot启动之初,org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration创建了
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
this.webMvcProperties.getServlet().getPath());
//DEFAULT_DISPATCHER_SERVLET_BEAN_NAME="dispatcherServlet"
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
其他filter也是autoconfigure创建的,比如:其他filter类似
@Configuration
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final HttpProperties.Encoding properties;
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
进入核心一步
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
beans.onStartup(servletContext);
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
private static final Log logger = LogFactory.getLog(RegistrationBean.class);
private int order = Ordered.LOWEST_PRECEDENCE;
private boolean enabled = true;
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
return;
}
register(description, servletContext);
}
跟踪register(description, servletContext);以DispatcherServletRegistrationBean 为例
public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean {
@Override
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(
StringUtils.capitalize(description) + " was not registered " + "(possibly already registered?)");
return;
}
configure(registration);
}
其中name是dispatcherServlet,通过SCI,servletContext.addServlet(name, this.servlet);添加了dispatcherServlet的bean
public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
//DEFAULT_DISPATCHER_SERVLET_BEAN_NAME="dispatcherServlet"
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
再看
configure(registration);
@Override
protected void configure(ServletRegistration.Dynamic registration) {
super.configure(registration);
String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
if (urlMapping.length == 0 && this.alwaysMapUrl) {
urlMapping = DEFAULT_MAPPINGS;
}
if (!ObjectUtils.isEmpty(urlMapping)) {
//addMapping,映射了servlet的url,默认/
registration.addMapping(urlMapping);
}
registration.setLoadOnStartup(this.loadOnStartup);
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
}
再看看filter如何处理
@Override
protected Dynamic addRegistration(String description, ServletContext servletContext) {
Filter filter = getFilter();
return servletContext.addFilter(getOrDeduceName(filter), filter);
}
@Override
protected void configure(FilterRegistration.Dynamic registration) {
super.configure(registration);
EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
if (dispatcherTypes == null) {
dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
}
Set<String> servletNames = new LinkedHashSet<>();
for (ServletRegistrationBean<?> servletRegistrationBean : this.servletRegistrationBeans) {
servletNames.add(servletRegistrationBean.getServletName());
}
servletNames.addAll(this.servletNames);
if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
//注册filter,默认url /*
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
}
else {
if (!servletNames.isEmpty()) {
registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
StringUtils.toStringArray(servletNames));
}
if (!this.urlPatterns.isEmpty()) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
StringUtils.toStringArray(this.urlPatterns));
}
}
}
总结
Spring Boot starter web其实就是ServletWebServerApplicationContext加embed tomcat。通过ServletWebServerApplicationContext容器onRefresh启动tomcat,然后使用this容器获取dispatcherServlet和filter,listener,加入SCI servletContext,然后启动child tomcat webapp子容器,按照tomcat架构图依次启动。