注:本文源碼分析基於 tomcat 9.0.43,源碼的gitee倉庫倉庫地址:https://gitee.com/funcy/tomcat.
本文是tomcat
源碼分析的第六篇,上一篇文章中我們提前,在Poller
線程中,tomcat
的會把連接請求會包裝爲SocketProcessorBase
,然後丟到線程池中運行。這其中的運行過程是怎麼樣的呢,最終又是怎麼執行到servlet
的?本文將爲你一一揭曉。
需要注意的是,從把請求丟到線程池到servlet
的執行,其中包含的鏈路非常多,通過調試的方式得到的調用鏈路如下:
這其中包含了http協議的解析、servlet規範的實現,對於這些我們就簡單略過了,僅分析關鍵步驟。
1. 解析http
協議:Http11Processor#service
tomcat
解析http
協議的方法爲Http11Processor#service
,方法如下:
public SocketState service(SocketWrapperBase<?> socketWrapper)
...
while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
sendfileState == SendfileState.DONE && !protocol.isPaused()) {
try {
// inputBuffer.parseRequestLine():解析處理請求行
if (!inputBuffer.parseRequestLine(keptAlive, protocol.getConnectionTimeout(),
protocol.getKeepAliveTimeout())) {
...
}
// prepareRequestProtocol():處理 http 協議版本
prepareRequestProtocol();
if (protocol.isPaused()) {
...
} else {
keptAlive = true;
...
// inputBuffer.parseHeaders():解析請求頭
if (!http09 && !inputBuffer.parseHeaders()) {
...
}
}
} catch (...) {
...
}
...
if (getErrorState().isIoAllowed()) {
rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
try {
// 準備request請求
prepareRequest();
} catch (Throwable t) {
...
}
}
...
if (getErrorState().isIoAllowed()) {
try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
// 處理讀事件
getAdapter().service(request, response);
...
} catch (...) {
...
}
}
...
}
...
}
這個方法非常長,不過我已經精簡了許多,僅留下了關鍵方法,列舉如下:
- 解析
http
請求行:inputBuffer.parseRequestLine()
- 處理
http
協議版本:prepareRequestProtocol()
- 解析
http
請求頭:inputBuffer.parseHeaders()
- 準備
http
請求數據:prepareRequest()
- 繼續處理請求:
CoyoteAdapter#service(request, response)
上面4個方法看得讓我十分惆悵,如果不是對http的每個細節有深入瞭解,不建議研究那幾個方法,真的讓人頭大。
解析完http請求後,繼續調用getAdapter().service(request, response);
處理之後的邏輯,也就是 CoyoteAdapter#service(request, response)
方法.
2. 生成httpServletRequest/httpServletResponse
:CoyoteAdapter#service(request, response)
httpServletRequest/httpServletResponse
是在CoyoteAdapter#service(request, response)
中生成的,代碼如下:
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
// 獲取 httpServletRequest,得到的值爲null
Request request = (Request) req.getNote(ADAPTER_NOTES);
// 獲取 httpServletResponse,得到的值爲null
Response response = (Response) res.getNote(ADAPTER_NOTES);
if (request == null) {
// 創建 request 對象
request = connector.createRequest();
request.setCoyoteRequest(req);
// 創建 response 對象
response = connector.createResponse();
response.setCoyoteResponse(res);
...
}
...
try {
// 處理參數,指 servlet 規範的一些參數,如果請求方法,sessionId
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// 調用 container 的 valve 處理
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
...
} catch (IOException e) {
// Ignore
} finally {
...
}
}
在tomcat
中,Request
與Response
有兩種類型:
org.apache.coyote.Request
與org.apache.coyote.Response
,tomcat 提供的,用來存放的http
連接數據的org.apache.catalina.connector.Request
與org.apache.catalina.connector.Response
,也是tomcat提供的,不過它分分別實現了HttpServletRequest/HttpServletResponse
這裏我們來看看org.apache.catalina.connector.Request
的創建過程:
request = connector.createRequest();
再進入Connector#createRequest
方法:
public Request createRequest() {
return new Request(this);
}
調用的是構造方法,繼續跟進去:
/**
* 實現了 HttpServletRequest
*/
public class Request implements HttpServletRequest {
/** 連接器 */
protected final Connector connector;
/** 這是 `org.apache.coyote.Request`, 用來存放 http 請求連接的數據 */
protected org.apache.coyote.Request coyoteRequest;
...
public Request(Connector connector) {
this.connector = connector;
formats = new SimpleDateFormat[formatsTemplate.length];
for(int i = 0; i < formats.length; i++) {
formats[i] = (SimpleDateFormat) formatsTemplate[i].clone();
}
}
/**
* 部分 httpServletRequest 的方法如下:
* 他們最終調用的是 coyoteRequest
*/
@Override
public String getMethod() {
return coyoteRequest.method().toString();
}
@Override
public String getRequestURI() {
return coyoteRequest.requestURI().toString();
}
...
}
到這裏,Request
只是創建了出來,並沒有做什麼實質性的工作,我們繼續看下去。
3. 解析reqeust:postParseRequest(...)
讓我們回到CoyoteAdapter#service()
方法,創建完Request/Response
,接下來就是把這個裏面裝東西了:
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
...
try {
// 處理參數,指 servlet 規範的一些參數,如果請求方法,sessionId
postParseSuccess = postParseRequest(req, request, res, response);
...
}
...
}
處理參數解析的方法爲CoyoteAdapter#postParseRequest
,裏面處理了servlet
規範的一些參數,其實就是給httpServletRequest
類型的Request
進行賦值,它的代碼如下:
protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
org.apache.coyote.Response res, Response response) throws IOException, ServletException {
// 解析 scheme
if (req.scheme().isNull()) {
// 解析 scheme,設置 httServletRequest 類型的 request
req.scheme().setString(connector.getScheme());
request.setSecure(connector.getSecure());
} else {
request.setSecure(req.scheme().equals("https"));
}
// 處理代理
String proxyName = connector.getProxyName();
int proxyPort = connector.getProxyPort();
if (proxyPort != 0) {
req.setServerPort(proxyPort);
} else if (req.getServerPort() == -1) {
if (req.scheme().equals("https")) {
req.setServerPort(443);
} else {
req.setServerPort(80);
}
}
if (proxyName != null) {
req.serverName().setString(proxyName);
}
MessageBytes undecodedURI = req.requestURI();
// 處理請求方法
if (undecodedURI.equals("*")) {
if (req.method().equalsIgnoreCase("OPTIONS")) {
StringBuilder allow = new StringBuilder();
allow.append("GET, HEAD, POST, PUT, DELETE, OPTIONS");
// Trace if allowed
if (connector.getAllowTrace()) {
allow.append(", TRACE");
}
res.setHeader("Allow", allow.toString());
connector.getService().getContainer().logAccess(request, response, 0, true);
return false;
} else {
response.sendError(400, "Invalid URI");
}
}
// 其他的一些解析操作就不看了
...
while (mapRequired) {
// 解析servlet相關內容,如 Host, Context,Wrapper 等
connector.getService().getMapper().map(serverName, decodedURI,
version, request.getMappingData());
...
}
...
return true;
}
這個方法依舊十分長,所做的工作爲解析http
請求的數據,將其轉換爲HttpServletRequest
所需要的參數,對於裏面的一些細節就不分析了。
這個方法中調用了這樣一段代碼:
// 解析servlet相關內容,如 Host, Context,Wrapper 等
connector.getService().getMapper().map(serverName, decodedURI,
version, request.getMappingData());
這段代碼最終調用的是Mapper#map()
,內容如下:
public void map(MessageBytes host, MessageBytes uri, String version,
MappingData mappingData) throws IOException {
// 拿到了 host 與 uri
if (host.isNull()) {
String defaultHostName = this.defaultHostName;
if (defaultHostName == null) {
return;
}
host.getCharChunk().append(defaultHostName);
}
host.toChars();
uri.toChars();
// 處理映射
internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}
這個方法拿到host
與uri
後,繼續請求Mapper#internalMap
方法,在這個方法裏會處理http的請求路徑,也就是根據請求路徑找到最終執行的servlet
,我們繼續。
4. 處理映射路徑:Mapper#internalMap
繼續跟進Mapper#internalMap
方法:
private final void internalMap(CharChunk host, CharChunk uri,
String version, MappingData mappingData) throws IOException {
// 這一步相當於獲取到項目中所有的 Host
MappedHost[] hosts = this.hosts;
// 查找 host
MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
...
mappingData.host = mappedHost.object;
if (uri.isNull()) {
return;
}
uri.setLimit(-1);
// 上面已經查找到了host,接下來就從該host下的所有context中查找
ContextList contextList = mappedHost.contextList;
MappedContext[] contexts = contextList.contexts;
// 查找 context
int pos = find(contexts, uri);
if (pos == -1) {
return;
}
...
mappingData.contextPath.setString(context.name);
ContextVersion contextVersion = null;
// 前面已經找到了 context,接下來就是從該context下所有的contextVersions中查找
ContextVersion[] contextVersions = context.versions;
final int versionCount = contextVersions.length;
if (versionCount > 1) {
Context[] contextObjects = new Context[contextVersions.length];
for (int i = 0; i < contextObjects.length; i++) {
contextObjects[i] = contextVersions[i].object;
}
mappingData.contexts = contextObjects;
if (version != null) {
// 根據版本號查找contextVersions
contextVersion = exactFind(contextVersions, version);
}
}
if (contextVersion == null) {
contextVersion = contextVersions[versionCount - 1];
}
mappingData.context = contextVersion.object;
mappingData.contextSlashCount = contextVersion.slashCount;
if (!contextVersion.isPaused()) {
// 處理 wrapper
internalMapWrapper(contextVersion, uri, mappingData);
}
}
需要注意的是,這裏的Host
、Context
並不是前面提到的StandardHost
、StandardContext
,而是MappedHost
、MappedContext
,MappedHost
中存放的是一個個的MappedContext
,MappedContext
中存放的是一個個ContextVersion
:
如MappedHost
內容如下::
protected static final class MappedHost extends MapElement<Host> {
/** 存放一個個MappedContext的結構,繼續看下去 */
public volatile ContextList contextList;
...
}
protected static final class ContextList {
/** 用數組存放 MappedContext */
public final MappedContext[] contexts;
...
}
MappedContext
內容如下:
protected static final class MappedContext extends MapElement<Void> {
/** 存放多個ContextVersion,使用數組存放 */
public volatile ContextVersion[] versions;
...
}
4.1 查找 host
那麼他們是如何查找host
的呢?在tomcat
創建host
時,默認爲設置一個hostname
:
public class Tomcat {
protected String hostname = "localhost";
public Host getHost() {
// 獲取 engine,不存在時會創建
Engine engine = getEngine();
if (engine.findChildren().length > 0) {
return (Host) engine.findChildren()[0];
}
// 創建 Host
Host host = new StandardHost();
host.setName(hostname);
// 添加到 engine
getEngine().addChild(host);
return host;
}
...
}
在http請求時,tomcat
會解析傳入的host
,當這兩者匹配時,就表示找到了對應的host,看下Mapper#exactFindIgnoreCase
就明白了:
private static final <T, E extends MapElement<T>> E exactFindIgnoreCase(
E[] map, CharChunk name) {
// 查找map數組中與name相等或最接近的元素,返回下標
int pos = findIgnoreCase(map, name);
if (pos >= 0) {
// 再一次判斷,因爲 findIgnoreCase(...) 返回的是與給定最接近或相等的下標
E result = map[pos];
if (name.equalsIgnoreCase(result.name)) {
return result;
}
}
return null;
}
4.2 查找 Context
Context
又是如何查找的呢?在添加Context
時,我們是這麼做的:
// 創建 context
String docBase = System.getProperty("java.io.tmpdir");
Context context = tomcat.addContext("", docBase);
在tomcat.addContext(...)
方法中,我們可以給Context
指定一個請求路徑,像這樣:
Context context = tomcat.addContext("/api", docBase);
這樣就表示以api
爲前綴的請求使用該Context
處理,Mapper#internalMap
方法在查找Context
時,就是根據uri查找對匹配的Context
的。
4.3 處理wrapper
匹配:Mapper#internalMapWrapper
查找到Context
後,接着就是處理Wrapper
了,方法爲Mapper#internalMapWrapper
:
private final void internalMapWrapper(ContextVersion contextVersion, CharChunk path,
MappingData mappingData) throws IOException {
// 獲取所有的 exactWrappers
MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
// 進行查找操作
internalMapExactWrapper(exactWrappers, path, mappingData);
// 省略了很多的內容
...
}
這個方法也是非常長,處理了各種匹配規則,這裏我們僅看其中一個,瞭解下吧匹配流程吧,進入Mapper#internalMapExactWrapper
方法:
private final void internalMapExactWrapper(MappedWrapper[] wrappers,
CharChunk path, MappingData mappingData) {
// 路徑匹配,請求的uri是否匹配servlet path
MappedWrapper wrapper = exactFind(wrappers, path);
if (wrapper != null) {
mappingData.requestPath.setString(wrapper.name);
// 找到後賦值
mappingData.wrapper = wrapper.object;
if (path.equals("/")) {
mappingData.pathInfo.setString("/");
mappingData.wrapperPath.setString("");
mappingData.contextPath.setString("");
mappingData.matchType = MappingMatch.CONTEXT_ROOT;
} else {
mappingData.wrapperPath.setString(wrapper.name);
mappingData.matchType = MappingMatch.EXACT;
}
}
}
這裏的path
就是uri
的路徑,這一步是根據路徑來判斷是否匹配的,即判斷傳入的uri
與servlet path
是否切爾西,匹配成功後,就賦值給mappingData
的wrapper
屬性了。
至些,請求對應的host
、context
、wrapepr
就都找到了。
5. 調用XxxValve
讓我們回到CoyoteAdapter#service
方法:
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
...
try {
// 處理參數,指 servlet 規範的一些參數,如果請求方法,sessionId
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// 調用 container 的 valve 處理
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
...
} catch (IOException e) {
// Ignore
} finally {
...
}
}
在解析完http參數、請求對應的servlet
後,接着就開始調用container
的 Pipeline
處理了:
// 調用 container 的 valve 處理
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
整個pipeline
結構如下:
執行時就是這樣一個接一個的Pipeline
調用。Pipeline
裏存放的是什麼呢?這就是所謂有Valve
,StandardEngine
、StandardHost
、StandardContext
、StandardWrapper
等都着對應的Valve
:
5.1 StandardEngineValve#invoke
我們跟進connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
,由於connector.getService().getContainer()
得到的是StandardEngine
,因此運行的是StandardEngineValve#invoke
方法:
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// 使用對應的host來處理
Host host = request.getHost();
...
// 調用 host 的 valve 處理
host.getPipeline().getFirst().invoke(request, response);
}
該方法關鍵代碼有兩個:
request.getHost()
:獲取該請求對應的host,這個就是前面Mapper#internalMap
千辛萬苦找到的與該請求對應的host
host.getPipeline().getFirst().invoke(request, response)
:繼續調用StandardHostValve#invoke
方法
5.2 StandardHostValve#invoke
我們再進入StandardHostValve#invoke
方法:
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// 從 request 中得到使用的 context
Context context = request.getContext();
...
try {
...
try {
if (!response.isErrorReportRequired()) {
// 調用 context 的 valve 處理
context.getPipeline().getFirst().invoke(request, response);
}
} catch (Throwable t) {
...
}
} finally {
...
}
這個方法的調用套路與StandardEngineValve#invoke
是一樣的:
request.getContext()
:從request
中得到該請求對應的context
,這個也是在Mapper#internalMap
方法找到的context.getPipeline().getFirst().invoke(request, response)
繼續調用StandardContextValve#invoke
處理
5.3 StandardContextValve#invoke
StandardContextValve#invoke
方法如下:
public final void invoke(Request request, Response response)
throws IOException, ServletException {
...
// 從 request 中拿到 wrapper
Wrapper wrapper = request.getWrapper();
...
// 調用 wrapper 的 valve 處理
wrapper.getPipeline().getFirst().invoke(request, response);
}
嗯,還是與StandardEngineValve#invoke
方法一樣的套路,我們繼續看StandardWrapperValve#invoke
方法。
6. StandardWrapperValve#invoke
我們再來看看Pipeline
調用圖:
從調用上來看,StandardWrapperValve
已經調用到底了,servlet
的調用就是在這裏發生的,該方法代碼如下:
public final void invoke(Request request, Response response)
throws IOException, ServletException {
...
try {
if (!unavailable) {
// 獲取 servlet,如果實例存在,直接返回,否則就創建實例並調用 Servlet#init 方法
servlet = wrapper.allocate();
}
} catch (...) {
...
}
...
// 創建過濾器鏈
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
Container container = this.container;
try {
if ((servlet != null) && (filterChain != null)) {
// 在這裏調用 filterChain.doFilter
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
// 調用 filter
filterChain.doFilter(request.getRequest(),
response.getResponse());
}
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
context.getLogger().info(log);
}
}
} else {
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
// 調用 filter
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
}
}
} catch (...) {
...
} finally {
...
}
}
這個方法依舊比較長,不過非關鍵點我都已經取消了,只留下了三個關鍵操作:
servlet = wrapper.allocate()
:獲取servlet
,如果servlet
的loadOnStart
小於0,那麼在這裏纔會實例化並調用Servlet#init
方法ApplicationFilterFactory.createFilterChain(...)
:創建過濾器鏈filterChain.doFilter(...)
:調用過濾器操作
接下來我們就來分析這幾個方法。
6.1 StandardWrapper#allocate
在前面分析servlet
的加載時,對於loadOnStartup
小於0的servlet
並沒有處理(沒有實例化,沒有執行servlet#init
方法),這個方法裏會實例化loadOnStartup
小於0的servlet
的,代碼如下:
public Servlet allocate() throws ServletException {
...
boolean newInstance = false;
// If not SingleThreadedModel, return the same instance every time
if (!singleThreadModel) {
// 實例不存在且未初始化
if (instance == null || !instanceInitialized) {
synchronized (this) {
if (instance == null) {
try {
...
// 調用 StandardWrapper#loadServlet 方法
instance = loadServlet();
newInstance = true;
...
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("standardWrapper.allocate"), e);
}
}
if (!instanceInitialized) {
initServlet(instance);
}
}
}
...
}
...
}
這個先判斷實例是否存在,是否進行進行過初始化操作,如果滿足條件則調用StandardWrapper#loadServlet
方法,關於這個方法前面的文章中已經分析過了,它所做的主要工作就兩個:
- 實例化
servlet
- 調用
Servlet#init(...)
方法
6.2 ApplicationFilterFactory#createFilterChain
ApplicationFilterFactory#createFilterChain
方法所做的是創建過濾器鏈,代碼如下:
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {
if (servlet == null)
return null;
ApplicationFilterChain filterChain = null;
// 創建 filterChain 對象
if (request instanceof Request) {
Request req = (Request) request;
if (Globals.IS_SECURITY_ENABLED) {
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
// 拿到 warpper 對應的 Context,然後從 context中拿到 filterMaps
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
...
// 添加滿足servlet路徑的filter到過濾器鏈
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
// 判斷哪些filter滿足servlet的路徑
if (!matchFiltersURL(filterMap, requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
continue;
}
filterChain.addFilter(filterConfig);
}
// 添加滿足servlet名稱的filter到過濾器鏈
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMap, servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
continue;
}
filterChain.addFilter(filterConfig);
}
return filterChain;
}
ApplicationFilterFactory#createFilterChain
方法所略長,不過邏輯相當清晰,它主要是用來創建過濾器鏈的,流程如下:
- 創建
ApplicationFilterChain
對象 - 獲取當前
wrapper
對應的Context
,然後從Context
拿到filterMaps
,在Context
中有一個結構專門用來存放filter
的,關於filter
的加載,前面的文章也分析了,這裏就不再分析了 - 查找滿足條件的
filter
,然後添加到filterChain
中,注意,這裏滿足條件的filter
是指滿足當前servlet
的filter
,處理匹配條件時,是通過servlet
路徑與servlet
名稱進行匹配的,兩者滿足其一即可。
6.3 ApplicationFilterChain#doFilter
獲取到過濾器鏈後,接下來就是執行了,方法爲ApplicationFilterChain#doFilter
:
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
(java.security.PrivilegedExceptionAction<Void>) () -> {
internalDoFilter(req,res);
return null;
}
);
} catch( PrivilegedActionException pe) {
...
}
} else {
// 執行過濾器
internalDoFilter(request,response);
}
}
調用了internalDoFilter(...)
執行,繼續跟進去:
/** 當前正在使用的過濾器的下標索引 */
private int pos = 0;
/** 當前過濾器鏈中過濾器的數量 */
private int n = 0;
/** 存放過濾器的地方 */
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
/**
* 執行過濾器
*/
private void internalDoFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// 判斷是否執行完了
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
// 獲取要執行的 filter
Filter filter = filterConfig.getFilter();
...
// 執行 filter的doFilter(...) 方法
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal = ((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (...) {
...
}
return;
}
try {
...
// 調用 servlet#service 方法
if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)
&& Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal = ((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service", servlet,
classTypeUsedInService, args, principal);
} else {
// 執行 servlet#service 方法
servlet.service(request, response);
}
} catch (...) {
...
}
}
internalDoFilter(...)
分爲兩個部分:
- 執行
filter.doFilter(...)
方法 - 執行
servlet.service(...)
方法
在ApplicationFilterChain
中有兩個成員變量:
n
:當前過濾器鏈中過濾器的數量post
:當前使用的過濾器的下標索引
在過濾器的調用時,會根據這兩個參數的大小判斷要不要進入過濾器的調用:
// 判斷是否執行完了
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
// 獲取要執行的 filter
Filter filter = filterConfig.getFilter();
...
// 執行 filter的doFilter(...) 方法
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal = ((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (...) {
...
}
return;
}
這個filter.doFilter(request, response, this)
就是我們真正調用Filter
的代碼了。
執行完當前的filter
後,是怎麼執行到下一個的呢?我們在實現Filter
時,都會有這麼一句代碼:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 你的業務邏輯
...
// 調用 FilterChain#doFilter 方法
chain.doFilter(request, response)
// 你的業務邏輯
...
}
在tomcat
中,FilterChain#doFilter
調用的就是ApplicationFilterChain#doFilter
方法,接着又到了ApplicationFilterChain#internalDoFilter
方法,這次調用時,pos
的值會加1,然後繼續調用Filter#doFilter
方法,之後又調回ApplicationFilterChain#doFilter
方法...如此循環往復的方法,在設計模式中,有一個專業的名字:責任鏈模式
。
每一次調用ApplicationFilterChain#doFilter
方法,pos
的值就會加1,直到pos >= n
後,filter
就全部執行完了,接着就開始執行servlet
了:
private void internalDoFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// 判斷是否執行完了
if (pos < n) {
// 執行 filter
...
}
try {
...
// 調用 servlet#service 方法
if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)
&& Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal = ((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
// 執行 servlet#service 方法
SecurityUtil.doAsPrivilege("service", servlet,
classTypeUsedInService, args, principal);
} else {
// 執行 servlet#service 方法
servlet.service(request, response);
}
} catch (...) {
...
}
}
關於servlet
的執行,其實非常簡單:前面已經獲取到了servlet
的實例,這裏就直接調用其service()
方法即可。
7. 總結
本文主要分析了tomcat
線程池處理請求的整個過程,包括http
協議的解析、httpServletRequest/httpServletResponse
的創建及準備、解析映射路徑(查找對應的host
、context
、wrapper
)、執行filter
與servlet
,不過對於http
的解析、servlet
規範的實現,本文只是點出了處理的方法,並沒有深入到這些方法的細節。
限於作者個人水平,文中難免有錯誤之處,歡迎指正!原創不易,商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
本文首發於微信公衆號 Java技術探祕,鏈接:https://mp.weixin.qq.com/s/o6ZKNqdcdB89EP0JEnTsPg. 如果您喜歡本文,歡迎關注該公衆號,讓我們一起在技術的世界裏探祕吧!