Spring Boot一個非常突出的優點就是不需要我們額外再部署Servlet容器,它內建了多種容器的支援。我們可以通過配置來指定我們需要的容器。
本文以我們平時最常使用的容器Tomcat為列來介紹以下兩個知識點:
- Spring Boot是怎麼整合啟動Tomcat容器的;
- 在Spring Boot中,怎麼進行Tomcat的深度配置。
Spring Boot整合啟動Tomcat的流程
對於看原始碼,每個人都有自己的方法。我自己在看原始碼的時候喜歡結合IDEA的Debug功能一起看。比如說現在我們要研究Spring Boot是在哪個環節點啟動Tomcat的,
我的思路是:Tomcat在啟動時會呼叫各個元件的init方法和start方法,那麼我只需要在這些方法上打上端點,然後就能在呼叫棧上看出Spring Boot是在哪個環節點啟用
Tomcat的了。
按照這個思路,我在Tomcat的Connector元件的init方法上打了端點,通過呼叫棧能很清楚的看出Spring Boot是在容器的onRefresh方法中呼叫Tomcat的。
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
找到了呼叫點,那麼一切都好辦了。從上面的方法中可以看出,重點內容就在this.createWebServer()這個方法中。
在Spring Boot中使用的容器類是ServletWebServerApplicationContext系列的容器,這個系列的容器可以內嵌Web容器。這個我們
可以從這個容器的屬性和方法中可以看出來。
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext {
//...省略部分程式碼
public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
//內嵌容器
private volatile WebServer webServer;
private ServletConfig servletConfig;
//...省略部分程式碼
//建立Web容器
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
//webServer和servletContext都是null,表示還沒建立容器,進入建立容器的邏輯
if (webServer == null && servletContext == null) {
//獲取建立容器的工廠,可以通過WebServerFactoryCustomizer介面對這個工廠進行自定義設定
ServletWebServerFactory factory = this.getWebServerFactory();
//具體的建立容器的方法,我們進去具體看下
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
}
下面是TomcatServletWebServerFactory的getWebServer方法。
public class TomcatServletWebServerFactory的getWebServer{
//...
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
//建立Tomcat容器
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
//建立聯結器,預設NIO模式,可以通過WebServerFactoryCustomizer改變具體模式
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
//自定義聯結器
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
//可以通過WebServerFactoryCustomizer新增額外的聯結器,這邊將這些聯結器繫結到Tomcat
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
//組測Listener、Filter和Servlet,自定義Context等操作
//這個方法可以重點看下
prepareContext(tomcat.getHost(), initializers);
//建立TomcatWebServer,並呼叫start方法
return getTomcatWebServer(tomcat);
}
//內嵌的Tomcat容器
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
//這邊觸發Tomcat的啟動流程,是Tomcat啟動的入口點
initialize();
}
//...省略部分程式碼
}
至此Spring Boot內嵌的Tomcat已將順序啟動了。那麼Spring Boot是在什麼時候註冊DispatchServlet的呢?
配置Listener、Filter和Servlet
Spring Boot配置Listener、Filter和Servlet可以參考我之前寫的文章Spring Boot使用嵌入式容器,那怎麼配置自定義Filter呢
推薦使用ServletListenerRegistrationBean、FilterRegistrationBean和ServletRegistrationBean的方式註冊Listener、Filter和Servlet。
Spring Boot註冊DispatcherServlet
在傳統的Spring MVC專案中,我們都會在web.xml中註冊DispatcherServlet這個入口類,那麼在Spring Boot中是在哪裡註冊的呢?
大家如果看Spring Boot的原始碼,這邊有個小技巧大家可以參考下。就是Spring Boot把之前傳統專案中的配置項都通過AutoConfig的形式
做配置了。所以這邊在尋找DispatcherServlet是在哪裡配置的也可以順著這個思路去尋找。
在IDEA的類查詢功能中輸入DispatcherServlet關鍵字,我們能看到一個DispatcherServletAutoConfiguration類。從名字上就能看出這個
類是DispatcherServlet的自動配置類,我們點進去看下是否是在這個類內部註冊的DispatcherServlet?
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {
/*
* The bean name for a DispatcherServlet that will be mapped to the root URL "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/*
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
private final WebMvcProperties webMvcProperties;
public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
this.webMvcProperties = webMvcProperties;
}
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(
this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(
this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(
this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
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;
}
}
@Configuration
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
private final ServerProperties serverProperties;
private final WebMvcProperties webMvcProperties;
private final MultipartConfigElement multipartConfig;
public DispatcherServletRegistrationConfiguration(
ServerProperties serverProperties, WebMvcProperties webMvcProperties,
ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
this.serverProperties = serverProperties;
this.webMvcProperties = webMvcProperties;
this.multipartConfig = multipartConfigProvider.getIfAvailable();
}
//很熟悉的程式碼有沒有,ServletRegistrationBean就是我們上一節中介紹的註冊Servlet的方式
//只不過這邊註冊的是DispatcherServlet這個特殊的Servlet
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
dispatcherServlet,
this.serverProperties.getServlet().getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
}
//...省略部分程式碼
}
好了通過這邊的介紹,我們直到DispatcherServlet是通過DispatcherServletAutoConfiguration這個自動配置類註冊的。
Spring Boot中關於Tomcat的一些其他配置
server.tomcat.accept-count |
100.0 |
Maximum queue length for incoming connection requests when all possible request processing threads are in use.(backlog的長度) |
---|---|---|
server.tomcat.accesslog.buffered |
true |
Whether to buffer output such that it is flushed only periodically. |
server.tomcat.accesslog.check-exists |
false |
Whether to check for log file existence so it can be recreated it if an external process has renamed it. |
server.tomcat.accesslog.condition-if |
Whether logging of the request will only be enabled if "ServletRequest.getAttribute(conditionIf)" does not yield null. | |
server.tomcat.accesslog.condition-unless |
Whether logging of the request will only be enabled if "ServletRequest.getAttribute(conditionUnless)" yield null. | |
server.tomcat.accesslog.directory |
logs |
Directory in which log files are created. Can be absolute or relative to the Tomcat base dir. |
server.tomcat.accesslog.enabled |
false |
Enable access log. |
server.tomcat.accesslog.encoding |
Character set used by the log file. Default to the system default character set. | |
server.tomcat.accesslog.file-date-format |
.yyyy-MM-dd |
Date format to place in the log file name. |
server.tomcat.accesslog.ipv6-canonical |
false |
Whether to use IPv6 canonical representation format as defined by RFC 5952. |
server.tomcat.accesslog.locale |
Locale used to format timestamps in log entries and in log file name suffix. Default to the default locale of the Java process. | |
server.tomcat.accesslog.max-days |
-1.0 |
Number of days to retain the access log files before they are removed. |
server.tomcat.accesslog.pattern |
common |
Format pattern for access logs. |
server.tomcat.accesslog.prefix |
access_log |
Log file name prefix. |
server.tomcat.accesslog.rename-on-rotate |
false |
Whether to defer inclusion of the date stamp in the file name until rotate time. |
server.tomcat.accesslog.request-attributes-enabled |
false |
Set request attributes for the IP address, Hostname, protocol, and port used for the request. |
server.tomcat.accesslog.rotate |
true |
Whether to enable access log rotation. |
server.tomcat.accesslog.suffix |
.log |
Log file name suffix. |
server.tomcat.additional-tld-skip-patterns |
Comma-separated list of additional patterns that match jars to ignore for TLD scanning. The special '?' and '*' characters can be used in the pattern to match one and only one character and zero or more characters respectively. | |
server.tomcat.background-processor-delay |
10s |
Delay between the invocation of backgroundProcess methods. If a duration suffix is not specified, seconds will be used. |
server.tomcat.basedir |
Tomcat base directory. If not specified, a temporary directory is used. | |
server.tomcat.connection-timeout |
Amount of time the connector will wait, after accepting a connection, for the request URI line to be presented. | |
server.tomcat.max-connections |
8192.0 |
Maximum number of connections that the server accepts and processes at any given time. Once the limit has been reached, the operating system may still accept connections based on the "acceptCount" property. |
server.tomcat.max-http-form-post-size |
2MB |
Maximum size of the form content in any HTTP post request. |
server.tomcat.max-swallow-size |
2MB |
Maximum amount of request body to swallow. |
server.tomcat.mbeanregistry.enabled |
false |
Whether Tomcat's MBean Registry should be enabled. |
server.tomcat.processor-cache |
200.0 |
Maximum number of idle processors that will be retained in the cache and reused with a subsequent request. When set to -1 the cache will be unlimited with a theoretical maximum size equal to the maximum number of connections. |
server.tomcat.redirect-context-root |
true |
Whether requests to the context root should be redirected by appending a / to the path. |
server.tomcat.relaxed-path-chars |
Comma-separated list of additional unencoded characters that should be allowed in URI paths. Only "< > [ \ ] ^ ` { | }" are allowed. | |
server.tomcat.relaxed-query-chars |
Comma-separated list of additional unencoded characters that should be allowed in URI query strings. Only "< > [ \ ] ^ ` { | }" are allowed. | |
server.tomcat.remoteip.host-header |
X-Forwarded-Host |
Name of the HTTP header from which the remote host is extracted. |
server.tomcat.remoteip.internal-proxies |
Regular expression that matches proxies that are to be trusted. | |
server.tomcat.remoteip.port-header |
X-Forwarded-Port |
Name of the HTTP header used to override the original port value. |
server.tomcat.remoteip.protocol-header |
Header that holds the incoming protocol, usually named "X-Forwarded-Proto". | |
server.tomcat.remoteip.protocol-header-https-value |
https |
Value of the protocol header indicating whether the incoming request uses SSL. |
server.tomcat.remoteip.remote-ip-header |
Name of the HTTP header from which the remote IP is extracted. For instance, X-FORWARDED-FOR . |
|
server.tomcat.resource.allow-caching |
true |
Whether static resource caching is permitted for this web application. |
server.tomcat.resource.cache-ttl |
Time-to-live of the static resource cache. | |
server.tomcat.threads.max |
200.0 |
Maximum amount of worker threads. |
server.tomcat.threads.min-spare |
10.0 |
Minimum amount of worker threads. |
server.tomcat.uri-encoding |
UTF-8 |
Character encoding to use to decode the URI. |
server.tomcat.use-relative-redirects |
false |
Whether HTTP 1.1 and later location headers generated by a call to sendRedirect will use relative or absolute redirects. |
這邊給出一個配置的列子
server:
port: ${port:9999}
tomcat:
accept-count: 200
#最好進行這段配置,預設會在tmp目錄下建立,Linux有時會有定時任務刪除tmp目錄下的內容
basedir: my-tomcat
accesslog:
enabled: true
pattern: '%t %a "%r" %s %S (%b M) (%D ms)'
max-http-post-size: 2MB
max-swallow-size: 2M
uri-encoding: GBK
threads:
max: 100
min-spare: 10
具體使用時可以參考Spring Boo官閘道器於Tomcat的配置。
一些其他類
Spring Boot還提供了很多自定義類,讓使用者對Tomcat的元件做自定義配置。這個符合Spring的設計哲學:只提供選擇,而不是強制使用者使用某項技術。
關於Tomcat的自定義配置類還有以下幾個,大家可以按需使用。
- WebServerFactoryCustomizer介面:自定義Web容易工廠
- WebServerFactoryCustomizerBeanPostProcessor處理類:WebServerFactoryCustomizer類通過WebServerFactoryCustomizerBeanPostProcessor類生效
- TomcatConnectorCustomizer:聯結器自定義處理類
- TomcatContextCustomizer:Context自定義介面