前言
在上半部分我们分析了Tomcat请求响应的生成过程,以及对应请求容器的映射过程,就像客人去朋友家小聚,首先肯定要知道朋友的地址和门牌号码,知道之后当然就要敲门进去,一阵吃喝吹吹牛逼,再从朋友家出来返回。地址和门牌号码对应请求映射关系,吃吃喝喝对应Servlet
处理业务逻辑,回家对应返回response
。本文就对下半部分:根据门牌号码找到朋友家再返回的过程进行解析
在Tomcat请求响应处理(一)的最后,我们看到一个很长的链式调用connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
,我们一点点来分析一下。connector.getService()
得到Connector
的父容器StandardService
,由于StandardService
是连接Tomcat两大组件的桥梁,自然getContainer()
又可以得到Container
顶层容器StandardEngine
,在Tomcat架构中各个组件及组件间关系(二)中说过每个Container
都存在一个StandardPipeline
管道,每个管道中存在一个或者多个Valve
阀门。当请求来时会按照容器的父子关系依次流入一个个管道,遇到管道中一个个阀门,既然管道有顺序,里面的阀门也有顺序,getPipeline().getFirst()
就对应着第一个阀门StandardEngineValve
,进而调用其invoke(Request, Response)
由于参数
request
中已经保存了正确的“门牌号码”,自然能得到请求对应的虚拟主机StandardHost
,如果此时该对象为空自然有问题,将错误码塞入response
中返回,最后责任链模式再次出现,调用StandardHost
中管道的第一个阀门,默认情况下在server.xml
中存在一个Valve
,对应的实体为AccessLogValve
,主要用来记录该虚拟主机的访问情况其中并没有做其他的操作,仅仅调用了管道中下一个阀门,下一个阀门依然不是基础阀门,在
StandardHost
启动时Tomcat又为其添加了另一个“错误上报阀门”getErrorReportValveClass()
返回该阀门对应全路径字符串org.apache.catalina.valves.ErrorReportValve
,当管道中不存在对应名称的阀门就将该阀门加入管道中该阀门的第一句直接调用了下一个阀门,我们可以把该阀门的功能理解为spring中的后置增强,即在响应之后再进行某些操作,因为该阀门是用来记录处理请求中产生错误的,而上面说过,当流程中发生错误会存在一个对应的错误码,而该错误码又封装在
response
中,那这里就不难理解为什么要在调用链返回过程中再做处理。下一个阀门就是StandardHost
的基础阀门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());
}
}
标注(1)下的两行代码判断该请求是否异步,默认为false,Context.fireRequestInitEvent(Request)
,该方法内封装了ServletRequestEvent
事件,并由一系列的应用事件监听器applicationEventListenersObjects
负责处理,同样默认不存在具体的监听器,返回true,导致代码走到标注(2)处,再一次责任链调用StandardContext
内的第一个阀门
看到第一个if判断对
/META-INF/
和/WEB-INF
目录下的资源进行了过滤,记得我刚学web编程时老师让我们把自定义的Servlet
放在/WEB-INF
下,说是不让直接访问只能内部跳转从而保证安全,那不能访问的秘密就是这段代码了。之后调用请求映射的Wrapper
,进而invoke
管道中的第一个阀门,对应的类为StandardWrapperValve
图中我删除了很多非重点代码,并将主要流程分成两部分,第一红框域具体的
Servlet
有关,第二个与该Servlet
相关的过滤器有关。我们从第一个开始分析,wrapper.allocate()
最终会调用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;
}
标注(1)判断该Servlet
是否为单例,默认Servlet
是多例的,如果实现一个过时的SingleThreadModel
标记接口,Tomcat就会将标识singleThreadModel
置为true,而这里就会直接返回;否则进入标注(2)根据解析的servletClass
反射创建出用户自己配置的Servlet
。标注(3)是在Servlet3.0
中引入的注解形式的文件上传方式校验,标注(4)最终会调用Servlet
的init(ServletConfig)
,该方法GenericServlet.init(ServletConfig)
,内部又调用了一个无参的init()
,当我们创建Servlet
时可以覆写该方法,从而在第一次调用Servlet
时进行一些初始化的操作
我们回到图6中的第二个红框,代码使用工厂创建了一个过滤器链filterChain
,具体创建代码如 代码清单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);
}
标注(1)和(2)创建出过滤器链并将请求与之对应的Servlet
与之关联,通过StandardContext
得到web.xml
中配置的所有<filter-mapping>
对应的实体FilterMap
数组,该数组中的数据来源逆序调用为StandardContext -> addFilterMap(FilterMap) -> WebXml.configureContext(Context) -> ContextConfig.webConfig() -> ContextConfig.configureStart() -> ContextConfig检测到Lifecycle.CONFIGURE_START_EVENT事件
,具体的流程分析可以参考Tomcat架构中各个组件及组件间关系(二)。还有一点需要注意的是,每一个过滤器都存在一个类型为DispatcherType
的调度类型dispatcher
,表示该过滤器对哪一种请求类型进行拦截,共有FORWORD
、INCLUDE
、REQUEST
、ASYNC
、ERROR
五种类型,默认为REQUEST
标注(3)遍历所有的过滤器数组,进行两轮匹配判断,第一轮matchDispatcher(FilterMap, DispatcherType)
判断是否filter
配置的调度类型在上述五种类型之中;第二轮matchFiltersURL(FilterMap, String)
判断请求URL
是否命中一个filter
,如若存在一个匹配过滤器,那么根据对应filter
名称从StandardContext
中的成员变量HashMap<String, ApplicationFilterConfig> filterConfigs
中得到对应的实例ApplicationFilterConfig
。之前的文章曾经分析过在解析web.xml
时,过滤器对应的对象为FilterDef
,但在StandardContext
启动时中间有一步是启动所有的过滤器,此时会将所有的FilterDef
转成ApplicationFilterConfig
放入该Map
中
要理解标注(4)必须先回忆一下<filter-mapping>
配置的方式,要拦截请求其实有两种方式:1.配置<url-pattern>
过滤请求路径;2.配置<servlet-name>
过滤特定Servlet
,那代码中的两个for
循环就对应两种方式了。最后将请求路径或者请求对应Servlet
的过滤器通过addFilter(filterConfig)
加入到ApplicationFilterChain
中成员变量filters
数组中
回到图6第二个红框中最后一句,终于见到了我们熟悉的doFilter(Request, Response)
,内部最终走到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);
}
}
}
虽然代码比较长但是结构还是很清晰的,主要分为上下两部分。第一部分由整个if
块包裹,pos
是当前遍历ApplicationFilter
元素对应的数组下标,n
为整个数组长度,总的意思就是遍历过滤器数组中每一个filter
,并依次调用它的doFilter(ServletRequest, ServletResponse)
,该方法就由我们自己实现了。当所有的filter
处理完毕走到第二部分,就调用serlvet.service(request, response)
。至此Tomcat整个请求响应处理的过程分析完毕
后记
对于Tomcat源码的解析暂时告一段落了,我从整个过程中学到了很多。好的代码就像好的作文一样,对程序员的影响是潜移默化的,很难想象一个从来不看范文的人能写出多好的文章