前言
在上半部分我們分析了Tomcat請(qǐng)求響應(yīng)的生成過程,以及對(duì)應(yīng)請(qǐng)求容器的映射過程刘离,就像客人去朋友家小聚室叉,首先肯定要知道朋友的地址和門牌號(hào)碼,知道之后當(dāng)然就要敲門進(jìn)去硫惕,一陣吃喝吹吹牛逼茧痕,再從朋友家出來返回。地址和門牌號(hào)碼對(duì)應(yīng)請(qǐng)求映射關(guān)系恼除,吃吃喝喝對(duì)應(yīng)Servlet
處理業(yè)務(wù)邏輯踪旷,回家對(duì)應(yīng)返回response
。本文就對(duì)下半部分:根據(jù)門牌號(hào)碼找到朋友家再返回的過程進(jìn)行解析
在Tomcat請(qǐng)求響應(yīng)處理(一)的最后豁辉,我們看到一個(gè)很長的鏈?zhǔn)秸{(diào)用connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
令野,我們一點(diǎn)點(diǎn)來分析一下。connector.getService()
得到Connector
的父容器StandardService
徽级,由于StandardService
是連接Tomcat兩大組件的橋梁气破,自然getContainer()
又可以得到Container
頂層容器StandardEngine
,在Tomcat架構(gòu)中各個(gè)組件及組件間關(guān)系(二)中說過每個(gè)Container
都存在一個(gè)StandardPipeline
管道餐抢,每個(gè)管道中存在一個(gè)或者多個(gè)Valve
閥門现使。當(dāng)請(qǐng)求來時(shí)會(huì)按照容器的父子關(guān)系依次流入一個(gè)個(gè)管道,遇到管道中一個(gè)個(gè)閥門旷痕,既然管道有順序碳锈,里面的閥門也有順序,getPipeline().getFirst()
就對(duì)應(yīng)著第一個(gè)閥門StandardEngineValve
欺抗,進(jìn)而調(diào)用其invoke(Request, Response)
由于參數(shù)
request
中已經(jīng)保存了正確的“門牌號(hào)碼”殴胧,自然能得到請(qǐng)求對(duì)應(yīng)的虛擬主機(jī)StandardHost
,如果此時(shí)該對(duì)象為空自然有問題,將錯(cuò)誤碼塞入response
中返回团滥,最后責(zé)任鏈模式再次出現(xiàn)竿屹,調(diào)用StandardHost
中管道的第一個(gè)閥門,默認(rèn)情況下在server.xml
中存在一個(gè)Valve
灸姊,對(duì)應(yīng)的實(shí)體為AccessLogValve
拱燃,主要用來記錄該虛擬主機(jī)的訪問情況其中并沒有做其他的操作,僅僅調(diào)用了管道中下一個(gè)閥門力惯,下一個(gè)閥門依然不是基礎(chǔ)閥門碗誉,在
StandardHost
啟動(dòng)時(shí)Tomcat又為其添加了另一個(gè)“錯(cuò)誤上報(bào)閥門”getErrorReportValveClass()
返回該閥門對(duì)應(yīng)全路徑字符串org.apache.catalina.valves.ErrorReportValve
,當(dāng)管道中不存在對(duì)應(yīng)名稱的閥門就將該閥門加入管道中該閥門的第一句直接調(diào)用了下一個(gè)閥門父晶,我們可以把該閥門的功能理解為spring中的后置增強(qiáng)哮缺,即在響應(yīng)之后再進(jìn)行某些操作,因?yàn)樵撻y門是用來記錄處理請(qǐng)求中產(chǎn)生錯(cuò)誤的甲喝,而上面說過尝苇,當(dāng)流程中發(fā)生錯(cuò)誤會(huì)存在一個(gè)對(duì)應(yīng)的錯(cuò)誤碼,而該錯(cuò)誤碼又封裝在
response
中埠胖,那這里就不難理解為什么要在調(diào)用鏈返回過程中再做處理糠溜。下一個(gè)閥門就是StandardHost
的基礎(chǔ)閥門StandardHostValve
,代碼清單1
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Context to be used for this Request
Context context = request.getContext();
if (context == null) {
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
sm.getString("standardHost.noContext"));
return;
}
// Bind the context CL to the current thread
if( context.getLoader() != null ) {
// Not started - it should check for availability first
// This should eventually move to Engine, it's generic.
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction<Void> pa = new PrivilegedSetTccl(
context.getLoader().getClassLoader());
AccessController.doPrivileged(pa);
} else {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
}
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(context.getPipeline().isAsyncSupported());
}
// (1)
boolean asyncAtStart = request.isAsync();
boolean asyncDispatching = request.isAsyncDispatching();
if (asyncAtStart || context.fireRequestInitEvent(request)) {
// Ask this Context to process this request. Requests that are in
// async mode and are not being dispatched to this resource must be
// in error and have been routed here to check for application
// defined error pages.
try {
if (!asyncAtStart || asyncDispatching) {
// (2)
context.getPipeline().getFirst().invoke(request, response);
} else {
// Make sure this request/response is here because an error
// report is required.
if (!response.isErrorReportRequired()) {
throw new IllegalStateException(sm.getString("standardHost.asyncStateError"));
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
container.getLogger().error("Exception Processing " + request.getRequestURI(), t);
// If a new error occurred while trying to report a previous
// error allow the original error to be reported.
if (!response.isErrorReportRequired()) {
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
throwable(request, response, t);
}
}
// Now that the request/response pair is back under container
// control lift the suspension so that the error handling can
// complete and/or the container can flush any remaining data
response.setSuspended(false);
Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// Protect against NPEs if the context was destroyed during a
// long running request.
if (!context.getState().isAvailable()) {
return;
}
// Look for (and render if found) an application level error page
if (response.isErrorReportRequired()) {
if (t != null) {
throwable(request, response, t);
} else {
status(request, response);
}
}
if (!request.isAsync() && (!asyncAtStart || !response.isErrorReportRequired())) {
context.fireRequestDestroyEvent(request);
}
}
// Access a session (if present) to update last accessed time, based on a
// strict interpretation of the specification
if (ACCESS_SESSION) {
request.getSession(false);
}
// Restore the context classloader
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction<Void> pa = new PrivilegedSetTccl(
StandardHostValve.class.getClassLoader());
AccessController.doPrivileged(pa);
} else {
Thread.currentThread().setContextClassLoader
(StandardHostValve.class.getClassLoader());
}
}
標(biāo)注(1)下的兩行代碼判斷該請(qǐng)求是否異步直撤,默認(rèn)為false非竿,Context.fireRequestInitEvent(Request)
,該方法內(nèi)封裝了ServletRequestEvent
事件谋竖,并由一系列的應(yīng)用事件監(jiān)聽器applicationEventListenersObjects
負(fù)責(zé)處理红柱,同樣默認(rèn)不存在具體的監(jiān)聽器,返回true蓖乘,導(dǎo)致代碼走到標(biāo)注(2)處锤悄,再一次責(zé)任鏈調(diào)用StandardContext
內(nèi)的第一個(gè)閥門
看到第一個(gè)if判斷對(duì)
/META-INF/
和/WEB-INF
目錄下的資源進(jìn)行了過濾,記得我剛學(xué)web編程時(shí)老師讓我們把自定義的Servlet
放在/WEB-INF
下驱敲,說是不讓直接訪問只能內(nèi)部跳轉(zhuǎn)從而保證安全铁蹈,那不能訪問的秘密就是這段代碼了。之后調(diào)用請(qǐng)求映射的Wrapper
众眨,進(jìn)而invoke
管道中的第一個(gè)閥門握牧,對(duì)應(yīng)的類為StandardWrapperValve
圖中我刪除了很多非重點(diǎn)代碼,并將主要流程分成兩部分娩梨,第一紅框域具體的
Servlet
有關(guān)沿腰,第二個(gè)與該Servlet
相關(guān)的過濾器有關(guān)。我們從第一個(gè)開始分析狈定,wrapper.allocate()
最終會(huì)調(diào)用StandardWrapper.loadServlet()
颂龙,代碼清單2
public synchronized Servlet loadServlet() throws ServletException {
if (unloading) {
throw new ServletException(
sm.getString("standardWrapper.unloading", getName()));
}
// (1)
// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
// (2)
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", servletClass), e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
// Added extra log statement for Bugzilla 36630:
// http://bz.apache.org/bugzilla/show_bug.cgi?id=36630
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// (3)
if (multipartConfigElement == null) {
MultipartConfig annotation =
servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement =
new MultipartConfigElement(annotation);
}
}
processServletSecurityAnnotation(servlet.getClass());
// Special handling for ContainerServlet instances
if ((servlet instanceof ContainerServlet) &&
(isContainerProvidedServlet(servletClass) ||
((Context) getParent()).getPrivileged() )) {
((ContainerServlet) servlet).setWrapper(this);
}
classLoadTime=(int) (System.currentTimeMillis() -t1);
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
instancePool = new Stack<Servlet>();
}
singleThreadModel = true;
}
// (4)
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
標(biāo)注(1)判斷該Servlet
是否為單例习蓬,默認(rèn)Servlet
是多例的,如果實(shí)現(xiàn)一個(gè)過時(shí)的SingleThreadModel
標(biāo)記接口措嵌,Tomcat就會(huì)將標(biāo)識(shí)singleThreadModel
置為true躲叼,而這里就會(huì)直接返回;否則進(jìn)入標(biāo)注(2)根據(jù)解析的servletClass
反射創(chuàng)建出用戶自己配置的Servlet
企巢。標(biāo)注(3)是在Servlet3.0
中引入的注解形式的文件上傳方式校驗(yàn)枫慷,標(biāo)注(4)最終會(huì)調(diào)用Servlet
的init(ServletConfig)
,該方法GenericServlet.init(ServletConfig)
浪规,內(nèi)部又調(diào)用了一個(gè)無參的init()
或听,當(dāng)我們創(chuàng)建Servlet
時(shí)可以覆寫該方法,從而在第一次調(diào)用Servlet
時(shí)進(jìn)行一些初始化的操作
我們回到圖6中的第二個(gè)紅框笋婿,代碼使用工廠創(chuàng)建了一個(gè)過濾器鏈filterChain
誉裆,具體創(chuàng)建代碼如 代碼清單3
public ApplicationFilterChain createFilterChain
(ServletRequest request, Wrapper wrapper, Servlet servlet) {
// get the dispatcher type
DispatcherType dispatcher = null;
if (request.getAttribute(Globals.DISPATCHER_TYPE_ATTR) != null) {
dispatcher = (DispatcherType) request.getAttribute(
Globals.DISPATCHER_TYPE_ATTR);
}
String requestPath = null;
Object attribute = request.getAttribute(
Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}
// If there is no servlet to execute, return null
if (servlet == null)
return (null);
boolean comet = false;
// Create and initialize a filter chain object
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
comet = req.isComet();
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
if (comet) {
req.setFilterChain(filterChain);
}
} else {
// (1)
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
// (2)
filterChain.setServlet(servlet);
filterChain.setSupport
(((StandardWrapper)wrapper).getInstanceSupport());
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return (filterChain);
// Acquire the information we will need to match filter mappings
String servletName = wrapper.getName();
// Add the relevant path-mapped filters to this filter chain
// (3)
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
continue;
}
boolean isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception e) {
// Note: The try catch is there because getFilter has a lot of
// declared exceptions. However, the filter is allocated much
// earlier
Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(t);
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}
// Add filters that match on servlet name second
// (4)
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
continue;
}
boolean isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception e) {
// Note: The try catch is there because getFilter has a lot of
// declared exceptions. However, the filter is allocated much
// earlier
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}
// Return the completed filter chain
return (filterChain);
}
標(biāo)注(1)和(2)創(chuàng)建出過濾器鏈并將請(qǐng)求與之對(duì)應(yīng)的Servlet
與之關(guān)聯(lián),通過StandardContext
得到web.xml
中配置的所有<filter-mapping>
對(duì)應(yīng)的實(shí)體FilterMap
數(shù)組缸濒,該數(shù)組中的數(shù)據(jù)來源逆序調(diào)用為StandardContext -> addFilterMap(FilterMap) -> WebXml.configureContext(Context) -> ContextConfig.webConfig() -> ContextConfig.configureStart() -> ContextConfig檢測(cè)到Lifecycle.CONFIGURE_START_EVENT事件
足丢,具體的流程分析可以參考Tomcat架構(gòu)中各個(gè)組件及組件間關(guān)系(二)。還有一點(diǎn)需要注意的是绍填,每一個(gè)過濾器都存在一個(gè)類型為DispatcherType
的調(diào)度類型dispatcher
霎桅,表示該過濾器對(duì)哪一種請(qǐng)求類型進(jìn)行攔截栖疑,共有FORWORD
讨永、INCLUDE
、REQUEST
遇革、ASYNC
卿闹、ERROR
五種類型,默認(rèn)為REQUEST
標(biāo)注(3)遍歷所有的過濾器數(shù)組萝快,進(jìn)行兩輪匹配判斷锻霎,第一輪matchDispatcher(FilterMap, DispatcherType)
判斷是否filter
配置的調(diào)度類型在上述五種類型之中;第二輪matchFiltersURL(FilterMap, String)
判斷請(qǐng)求URL
是否命中一個(gè)filter
揪漩,如若存在一個(gè)匹配過濾器旋恼,那么根據(jù)對(duì)應(yīng)filter
名稱從StandardContext
中的成員變量HashMap<String, ApplicationFilterConfig> filterConfigs
中得到對(duì)應(yīng)的實(shí)例ApplicationFilterConfig
。之前的文章曾經(jīng)分析過在解析web.xml
時(shí)奄容,過濾器對(duì)應(yīng)的對(duì)象為FilterDef
冰更,但在StandardContext
啟動(dòng)時(shí)中間有一步是啟動(dòng)所有的過濾器,此時(shí)會(huì)將所有的FilterDef
轉(zhuǎn)成ApplicationFilterConfig
放入該Map
中
要理解標(biāo)注(4)必須先回憶一下<filter-mapping>
配置的方式昂勒,要攔截請(qǐng)求其實(shí)有兩種方式:1.配置<url-pattern>
過濾請(qǐng)求路徑蜀细;2.配置<servlet-name>
過濾特定Servlet
,那代碼中的兩個(gè)for
循環(huán)就對(duì)應(yīng)兩種方式了戈盈。最后將請(qǐng)求路徑或者請(qǐng)求對(duì)應(yīng)Servlet
的過濾器通過addFilter(filterConfig)
加入到ApplicationFilterChain
中成員變量filters
數(shù)組中
回到圖6第二個(gè)紅框中最后一句奠衔,終于見到了我們熟悉的doFilter(Request, Response)
谆刨,內(nèi)部最終走到ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse)
,見代碼清單4
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
filter = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
filter, request, response);
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
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);
}
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response);
} catch (IOException e) {
if (filter != null) {
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response, e);
}
throw e;
} catch (ServletException e) {
if (filter != null) {
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response, e);
}
throw e;
} catch (RuntimeException e) {
if (filter != null) {
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response, e);
}
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
if (filter != null) {
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response, e);
}
throw new ServletException
(sm.getString("filterChain.filter"), e);
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
servlet, request, response);
if (request.isAsyncSupported()
&& !support.getWrapper().isAsyncSupported()) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse)) {
if (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(request, response);
}
} else {
servlet.service(request, response);
}
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response);
} catch (IOException e) {
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response, e);
throw e;
} catch (ServletException e) {
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response, e);
throw e;
} catch (RuntimeException e) {
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response, e);
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response, e);
throw new ServletException
(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
雖然代碼比較長但是結(jié)構(gòu)還是很清晰的归斤,主要分為上下兩部分痊夭。第一部分由整個(gè)if
塊包裹,pos
是當(dāng)前遍歷ApplicationFilter
元素對(duì)應(yīng)的數(shù)組下標(biāo)脏里,n
為整個(gè)數(shù)組長度生兆,總的意思就是遍歷過濾器數(shù)組中每一個(gè)filter
,并依次調(diào)用它的doFilter(ServletRequest, ServletResponse)
膝宁,該方法就由我們自己實(shí)現(xiàn)了鸦难。當(dāng)所有的filter
處理完畢走到第二部分,就調(diào)用serlvet.service(request, response)
员淫。至此Tomcat整個(gè)請(qǐng)求響應(yīng)處理的過程分析完畢
后記
對(duì)于Tomcat源碼的解析暫時(shí)告一段落了合蔽,我從整個(gè)過程中學(xué)到了很多。好的代碼就像好的作文一樣介返,對(duì)程序員的影響是潛移默化的拴事,很難想象一個(gè)從來不看范文的人能寫出多好的文章