Springboot内置了web的starter,开箱即用。web容器除了Tomcat还可以是Jetty等,因此SpringBoot内部肯定进行了封装处理,本文将从Tomcat作为入口,分析一下SpringBoot是如何整合Tomcat。
1. Spring 使用Tomcat
Spring上如果要使用Tomcat,类似一下例子,在Tomcat中增加DispatcherServlet,Servlet在执行的时候会调用init的方法。DispatcherServlet内部关联了ApplicationContext,所以可以在init的方法内做一下Spring IOC的一些整合逻辑。
java
public class WebApplicationStarter {
private Tomcat tomcat;
public static void main(String[] args) throws LifecycleException {
WebApplicationStarter starter = new WebApplicationStarter();
starter.start();
}
public WebApplicationStarter(){
tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.getConnector();
Context context = tomcat.addContext("", null);
try {
DispatcherServlet dispatcherServlet = new DispatcherServlet(createContext(context.getServletContext()));
Wrapper wrapper = Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet);
wrapper.setLoadOnStartup(1);
wrapper.addMapping("/*");
}catch (Exception e) {
}
}
public void start() throws LifecycleException {
if (this.tomcat != null) {
this.tomcat.start();
}
}
public WebApplicationContext createContext(ServletContext context){
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(WebApplicationConfig.class);
applicationContext.setServletContext(context);
applicationContext.refresh();
applicationContext.registerShutdownHook();
return applicationContext;
}
}
java
@Configuration
@ComponentScan(basePackageClasses = WebApplicationConfig.class)
@EnableWebMvc
public class WebApplicationConfig implements WebMvcConfigurer {
@Autowired(required = false)
private List<HandlerInterceptor> handlerInterceptors;
@Override
public void addInterceptors(InterceptorRegistry registry) {
if (handlerInterceptors != null) {
for (HandlerInterceptor interceptor : handlerInterceptors) {
registry.addInterceptor(interceptor);
}
}
}
}
2. Spring Boot内嵌Tomcat
SpringBoot会根据web类型是Servlet类型,从而创建的ApplicationContext是AnnotationConfigServletWebServerApplicationContext,实际主要的处理逻辑在父类ServletWebServerApplicationContext上。
在onRefresh会先调用父类的方法,然后调用createWebServer创建WebServer。WebServer是web容器的抽象,具体实现有TomcatWebServer,NettyWebServer等。
onRefresh来自AbstractApplicationContext启动流程中的一部分,可以查看本人的另外一篇文章:浅析Spring启动流程
java
//ServletWebServerApplicationContext#onRefresh
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
当WebServer和ServletContext为空时(默认情况下为空),则会通过ServletWebServerFactory进行创建WebServer。以Tomcat为例,ServletWebServerFactory的实现为TomcatServletWebServerFactory。
java
//ServletWebServerApplicationContext#createWebServer
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
}
接下来就分析一下TomcatServletWebServerFactory主要的逻辑,内部会调用Tomcat的api来实现Tomcat的整合,关注核心部分。
Tomcat整体架构,可以查看本人的另外一篇文章:Tomcat整体架构
主要有两大逻辑:构建Connector;构建Context。至于getTomcatWebServer逻辑则很简单,就是new TomcatWebServer进行返回,后文再分析TomcatWebServer。
java
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());
//创建一个Connector
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
//定制Connector
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
//准备Context
prepareContext(tomcat.getHost(), initializers);
//创建TomcatWebServer
return getTomcatWebServer(tomcat);
}
2.1 构建Connector
首先会创建一个Connector对象,默认是org.apache.coyote.http11.Http11NioProtocol
,Tomcat内部会直接使用这个类,作为Tomcat连接器的处理,支持Http/1.1协议,采用NIO模型。
紧接着就会对Connector进行定制。会设置端口号、属性值、URIEncoding、SSL以及UpgradeProtocol等处理。然后还会对ProtocolHandler和Connector进行定制。
- 可以通过TomcatProtocolHandlerCustomizer对ProtocolHandler进行定制,如设置连接相关的参数:maxKeepAliveRequests等。
- 可以通过TomcatConnectorCustomizer对Connector进行定制。
java
//TomcatServletWebServerFactory#customizeConnector
protected void customizeConnector(Connector connector) {
int port = Math.max(getPort(), 0);
connector.setPort(port);
if (StringUtils.hasText(this.getServerHeader())) {
connector.setAttribute("server", this.getServerHeader());
}
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
}
//对ProtocolHandler进行定制
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 (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(connector);
}
TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
compression.customize(connector);
//对Connector进行定制
for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
customizer.customize(connector);
}
}
除此之外,还可以额外设置其他Connector。
java
//TomcatServletWebServerFactory#getWebServer
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
2.2 构建Context
首先会创建TomcatEmbeddedContext对象,该对象继承StandardContext实现了Context接口(Tomcat提供),然后会做一些系列属性设置。同时这个Context也会加入到Host中,然后会对Context进行配置。
java
//TomcatServletWebServerFactory#prepareContext
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());
//...
context.addLifecycleListener(new StaticResourceConfigurer(context));
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
// 添加到Host
host.addChild(context);
//配置Context
configureContext(context, initializersToUse);
postProcessContext(context);
}
会添加Starter到Context中,而这里的Starter内部实际持有ServletContextInitializer接口(有onStartup方法,参数对象为ServletContext),这是一种扩展机制,可以通过定制ServletContextInitializer,实现对应的功能,比如,将DispatcherServlet通过ServletContext加入到Tomcat中(将Servlet加入到Tomcat中),后文将会分析。
此处,可以通过TomcatContextCustomizer接口,对Context进行定制处理。
java
//TomcatServletWebServerFactory#configureContext
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
//将TomcatStarter加入到context中。
context.addServletContainerInitializer(starter, NO_CLASSES);
//...
//配置Session
configureSession(context);
new DisableReferenceClearingContextCustomizer().customize(context);
//定制TomcatContextCustomizer
for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
customizer.customize(context);
}
}
前面我们提到会把TomcatStarter(实现了ServletContainerInitializer接口)加入到Context。
在Context启动的时候进行,会调用ServletContainerInitializer。
java
//StandardContext#startInternal
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
而在SpringBoot中的TomcatStarter类的onStartup方法中,实际上会循环调用ServletContextInitializer(注意,这两个接口不一样)
java
//TomcatStarter#onStartup
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
而在SpringBoot中,通过RegistrationBean作为基类实现ServletContextInitializer接口,对应的子类有ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean等。以Dispactcher为例,查看如何被添加到Context中。
首先基类实现了onStartup方法,然后提供了抽象方法register
java
//RegistrationBean#onStartup
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);
}
子类DynamicRegistrationBean提供了addRegistration抽象方法
java
//DynamicRegistrationBean#register
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);
}
子类ServletRegistrationBean会调用ServletContext的addServlet把servlet加入到Context中。DispatcherServletRegistrationBean类具化了servlet为DispatcherServlet
java
//ServletRegistrationBean#addRegistration
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
2.3 创建TomcatWebServer
创建TomcatWebServer的时候,会调用初始化方法
java
//TomcatWebServer
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
初始化方法中,会调用Tomcat的start方法,在Context启动的时候,会先移除Connector(Context容器的启动在Connector之前),这样是避免Spring容器启动阶段,就开始处理连接相关内容。最后会启动一个非守护线程,避免应用启动后就直接退出了。
java
private void initialize(){
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();
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
2.4 WebServer启动
在Spring容器启动完后(会执行finishRefresh方法),此处会尝试启动WebServer
java
//ServletWebServerApplicationContext#finishRefresh
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
在start方法中,会把前面移除的Connector重新添加到Tomcat中,当Tomcat启动后,如果添加Connector,则会直接调用Connector的start方法,这也是为什么没有显示看到Connector的启动。然后会对Container重新进行load;最后会检查Connector的状态是否成功。
java
//TomcatWebServer#start
public void start() {
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
performDeferredLoadOnStartup();
}
checkThatConnectorsHaveStarted();
}
2.5 自动配置
TomcatServletWebServerFactory
TomcatServletWebServerFactory会在ServletWebServerFactoryAutoConfiguration通过@Import(ServletWebServerFactoryConfiguration.EmbeddedTomcat)进行创建,EmbeddedTomcat就会创建TomcatServletWebServerFactory。创建的时候,会添加TomcatConnectorCustomizer、TomcatContextCustomizer、TomcatProtocolHandlerCustomizer这几个定制器。
java
//ServletWebServerFactoryConfiguration$EmbeddedTomcat
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
DispatcherServlet
前面分析到,实际上是DispatcherServletRegistrationBean将DispatcherServlet添加到Context,所以肯定会有DispatcherServletRegistrationBean的创建。这里会@Import(DispatcherServletConfiguration),会对DispatcherServlet进行创建。
java
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
@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;
}
}
DispatcherServlet创建后,还会根据web的一些配置,进行设置属性。
java
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {
@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;
}
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
3. 参考资料
- SpringBoot源码2.2.x分支。