Tomcat请求处理(七) - Servlet实例的调用

Tomcat请求处理中Servlet实例的调用是和Filter的调用联系在一起的,是在StandardWrapperValve类的#invoke()方法中调用的,前面的文章中提到过,就是下面的这句:
filterChain.doFilter(request.getRequest(), response.getResponse());

它的源代码如下:

	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(new java.security.PrivilegedExceptionAction() {
public Object run() throws ServletException, IOException {
internalDoFilter(req, res);
return null;
}
});
} catch (PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException)
throw (ServletException) e;
else if (e instanceof IOException)
throw (IOException) e;
else if (e instanceof RuntimeException)
throw (RuntimeException) e;
else
throw new ServletException(e.getMessage(), e);
}
} else {
internalDoFilter(request, response);
}
}


这个方法只是调用了#internalDoFilter(),这个才是Filter调用的核心,源代码如下所示:

	private void internalDoFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {

if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
// 得到当前Filter
filter = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, filter, request,
response);
// 调用Filter的#doFilter()方法。
// 在Filter内部,如果Filter调用成功,会调用chain.doFilter(request,response); 这个语句。
// 这里传递给忒Filter的chain实例就是ApplicationFilterChain类的对象。
// 于是程序又回到了#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);

args = null;
} 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) {
if (filter != null)
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, filter, request,
response, e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}

try {
if (Globals.STRICT_SERVLET_COMPLIANCE) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
// Filter全部调用完毕后,就会把请求真正的传递给Servlet了,调用它的#service()方法。
support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT, servlet, request,
response);
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);
args = null;
} else {
servlet.service((HttpServletRequest) request, (HttpServletResponse) 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) {
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT, servlet, request,
response, e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (Globals.STRICT_SERVLET_COMPLIANCE) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}

}


程序到了这里,就出现了分水岭,分别对应着对Servlet,JSP,和静态资源的处理。
Servlet比较简单,因为这里的"servlet"就是真实的Servlet实例了,直接调用开发人员自己编写的#service()方法了(#service()内部是会调用#doGet(),#doPost()等方法的)。

对于静态资源,是调用org.apache.catalina.servlets.DefaultServlet的#service()方法,由于DefaultServlet并没有重写这个方法,所以直接使用HttpServlet的#service()方法。但是DefaultServlet重写了#doGet(),#doPost()等方法(#doPost()内部又调用了#doGet()),所以请求就又到了#doGet()这个方法中,DefaultServlet的#doGet()只调用了#serveResource()这个方法来提取资源,代码太长,就不再仔细的看了。
对于JSP资源,就比较复杂了,还有编译等操作,下面来重点看一下。

首先,要调用org.apache.jasper.servlet.JspServlet的#service()方法,源代码如下:

	public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

// 获得请求的JSP文件的路径
String jspUri = null;

String jspFile = (String) request.getAttribute(Constants.JSP_FILE);
if (jspFile != null) {
// 通过web.xml中的标签定义
jspUri = jspFile;
} else {
jspUri = (String) request.getAttribute(Constants.INC_SERVLET_PATH);
if (jspUri != null) {
String pathInfo = (String) request.getAttribute("javax.servlet.include.path_info");
if (pathInfo != null) {
jspUri += pathInfo;
}
} else {
jspUri = request.getServletPath();
String pathInfo = request.getPathInfo();
if (pathInfo != null) {
jspUri += pathInfo;
}
}
}

if (log.isDebugEnabled()) {
log.debug("JspEngine --> " + jspUri);
log.debug("\t ServletPath: " + request.getServletPath());
log.debug("\t PathInfo: " + request.getPathInfo());
log.debug("\t RealPath: " + context.getRealPath(jspUri));
log.debug("\t RequestURI: " + request.getRequestURI());
log.debug("\t QueryString: " + request.getQueryString());
log.debug("\t Request Params: ");
Enumeration e = request.getParameterNames();
while (e.hasMoreElements()) {
String name = (String) e.nextElement();
log.debug("\t\t " + name + " = " + request.getParameter(name));
}
}

try {
// 预编译模式,如果是预编译模式,只是对JSP进行编译,不会返回页面执行结果
boolean precompile = preCompile(request);
// 继续JSP请求
serviceJspFile(request, response, jspUri, null, precompile);
} catch (RuntimeException e) {
throw e;
} catch (ServletException e) {
throw e;
} catch (IOException e) {
throw e;
} catch (Throwable e) {
throw new ServletException(e);
}

}

这里,有两个方法需要看一下,一个是预编译方法#preCompile(),另外一个是调用JSP的#serviceJspFile()方法。

首先来看一下#preCompile():

	boolean preCompile(HttpServletRequest request) throws ServletException {
// 获得查询字符串
String queryString = request.getQueryString();
if (queryString == null) {
return (false);
}
// 看是否有预编译参数,默认是jsp_precompile
int start = queryString.indexOf(Constants.PRECOMPILE);
if (start < 0) {
return (false);
}
queryString = queryString.substring(start + Constants.PRECOMPILE.length());
if (queryString.length() == 0) {
return (true);
}
if (queryString.startsWith("&")) {
return (true);
}
if (!queryString.startsWith("=")) {
return (false);
}
int limit = queryString.length();
int ampersand = queryString.indexOf("&");
if (ampersand > 0) {
limit = ampersand;
}
String value = queryString.substring(1, limit);
// 如果jsp_precompile的值为true,代表此次请求为预编译JSP的请求。
if (value.equals("true")) {
return (true);
} else if (value.equals("false")) {
// 这里要注意下,如果是false,同样是返回true的。
// 因为规范中说如果这个参数是false,那么请求不应该被提交到JSP页面的,那么此次请求就变得毫无意义了,所以索性都预编译好了。
return (true);
} else {// 如果是true和false之外的值就要抛出异常了。
throw new ServletException("Cannot have request parameter " + Constants.PRECOMPILE
+ " set to " + value);
}
}

从这个方法中看出,要想预编译一个页面,只要在页面名字后加上查询字符串jsp_precompile=true就可以了。

下面是#serviceJspFile()方法,这个方法提供对请求进行了进一步的处理。

	private void serviceJspFile(HttpServletRequest request, HttpServletResponse response,
String jspUri, Throwable exception, boolean precompile) throws ServletException,
IOException {
// 获取JspServletWrapper实例
JspServletWrapper wrapper = (JspServletWrapper) rctxt.getWrapper(jspUri);
if (wrapper == null) {
synchronized (this) {
wrapper = (JspServletWrapper) rctxt.getWrapper(jspUri);
if (wrapper == null) {
if (null == context.getResource(jspUri)) {
String includeRequestUri = (String) request
.getAttribute("javax.servlet.include.request_uri");
if (includeRequestUri != null) {
String msg = Localizer.getMessage("jsp.error.file.not.found", jspUri);
throw new ServletException(SecurityUtil.filter(msg));
} else {
try {
response.sendError(HttpServletResponse.SC_NOT_FOUND, request
.getRequestURI());
} catch (IllegalStateException ise) {
log.error(Localizer.getMessage("jsp.error.file.not.found", jspUri));
}
}
return;
}
boolean isErrorPage = exception != null;
wrapper = new JspServletWrapper(config, options, jspUri, isErrorPage, rctxt);
rctxt.addWrapper(jspUri, wrapper);
}
}
}

// 调用它的#service()方法。
wrapper.service(request, response, precompile);

}

org.apache.jasper.servlet.JspServletWrapper的service()方法包括了编译,载入和执行Servlet几个步骤,如下所示:

	public void service(HttpServletRequest request, HttpServletResponse response, boolean precompile)
throws ServletException, IOException, FileNotFoundException {

try {
if (ctxt.isRemoved()) {
throw new FileNotFoundException(jspUri);
}

if ((available > 0L) && (available < Long.MAX_VALUE)) {
if (available > System.currentTimeMillis()) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, Localizer
.getMessage("jsp.error.unavailable"));
return;
} else {
// Wait period has expired. Reset.
available = 0;
}
}

// (1) 编译
if (options.getDevelopment() || firstTime) {
synchronized (this) {
firstTime = false;

// The following sets reload to true, if necessary
ctxt.compile();
}
} else {
if (compileException != null) {
// Throw cached compilation exception
throw compileException;
}
}

// (2) 载入Servlet类文件
getServlet();

// 如果是预编译,那么直接返回
if (precompile) {
return;
}

} catch (ServletException ex) {
if (options.getDevelopment()) {
throw handleJspException(ex);
} else {
throw ex;
}
} catch (IOException ex) {
if (options.getDevelopment()) {
throw handleJspException(ex);
} else {
throw ex;
}
} catch (IllegalStateException ex) {
if (options.getDevelopment()) {
throw handleJspException(ex);
} else {
throw ex;
}
} catch (Exception ex) {
if (options.getDevelopment()) {
throw handleJspException(ex);
} else {
throw new JasperException(ex);
}
}

try {

// (3) 调用Servlet处理请求
if (theServlet instanceof SingleThreadModel) {
synchronized (this) {
theServlet.service(request, response);
}
} else {
theServlet.service(request, response);
}

} catch (UnavailableException ex) {
String includeRequestUri = (String) request
.getAttribute("javax.servlet.include.request_uri");
if (includeRequestUri != null) {
throw ex;
} else {
int unavailableSeconds = ex.getUnavailableSeconds();
if (unavailableSeconds <= 0) {
unavailableSeconds = 60; // Arbitrary default
}
available = System.currentTimeMillis() + (unavailableSeconds * 1000L);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, ex.getMessage());
}
} catch (ServletException ex) {
if (options.getDevelopment()) {
throw handleJspException(ex);
} else {
throw ex;
}
} catch (IOException ex) {
if (options.getDevelopment()) {
throw handleJspException(ex);
} else {
throw ex;
}
} catch (IllegalStateException ex) {
if (options.getDevelopment()) {
throw handleJspException(ex);
} else {
throw ex;
}
} catch (Exception ex) {
if (options.getDevelopment()) {
throw handleJspException(ex);
} else {
throw new JasperException(ex);
}
}
}

#compile()方法实现了对JSP文件向java类(Servlet)的编译,源代码如下所示:

    public void compile() throws JasperException, FileNotFoundException {
// 创建"jspCompiler"的实例
createCompiler();
// 判断是否过期,即是否需要重新编译,这个方法中主要用源代码和编译后的文件的修改时间等因素进行判断
if (jspCompiler.isOutDated()) {
try {
jspCompiler.removeGeneratedFiles();
jspLoader = null;
// 执行编译
jspCompiler.compile();
// 设定reload标志为true,让#getServlet()方法载入新的类
jsw.setReload(true);
jsw.setCompilationException(null);
} catch (JasperException ex) {
// Cache compilation exception
jsw.setCompilationException(ex);
throw ex;
} catch (Exception ex) {
JasperException je = new JasperException(
Localizer.getMessage("jsp.error.unable.compile"),
ex);
// Cache compilation exception
jsw.setCompilationException(je);
throw je;
}
}
}

可以看出,JSP页面没有修改的前提下Tomcat是不会对JSP进行多次编译的,只在第一次调用它的时候编译。

看完#compile()后,再来看一下#getServlet(),它载入了前面编译生成的Servlet类。

	public Servlet getServlet() throws ServletException, IOException, FileNotFoundException {
if (reload) {
synchronized (this) {
if (reload) {
// 销毁旧的Servlet
destroy();

Servlet servlet = null;

try {
// 载入Servlet
servletClass = ctxt.load();
servlet = (Servlet) servletClass.newInstance();
AnnotationProcessor annotationProcessor = (AnnotationProcessor) config
.getServletContext().getAttribute(
AnnotationProcessor.class.getName());
if (annotationProcessor != null) {
annotationProcessor.processAnnotations(servlet);
annotationProcessor.postConstruct(servlet);
}
} catch (IllegalAccessException e) {
throw new JasperException(e);
} catch (InstantiationException e) {
throw new JasperException(e);
} catch (Exception e) {
throw new JasperException(e);
}
// Servlet初始化
servlet.init(config);

if (!firstTime) {
ctxt.getRuntimeContext().incrementJspReloadCount();
}

theServlet = servlet;
// reload值复原
reload = false;
}
}
}
return theServlet;
}

通过这个类,获得了一个"theServlet"实例作为JSP编译之后的Servlet引用,并且在JSP没有改动前,这个实例是不需要重新生成的。

通过这两个步骤后,最后调用了JSP编译后的Servlet类的#service()方法去处理请求。至此,Tomcat的请求处理结束了。

你可能感兴趣的:(Tomcat)