设为首页 收藏本站
查看: 368|回复: 0

[经验分享] Tomcat请求处理(七)

[复制链接]

尚未签到

发表于 2017-2-6 13:00:59 | 显示全部楼层 |阅读模式
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中的<jsp-file>标签定义
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的请求处理结束了。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-338412-1-1.html 上篇帖子: Tomcat启动部分源代码分析(三) -- 载入 下篇帖子: Coder 爱翻译 How Tomcat Works 第五章 第三部分
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表