1.探究Tomcat中JSP热部署机制
1.1 JSP热部署源码分析
一直对Tomcat中JSP热部署机制有着极大的兴趣,今天正好有时间写下这篇博文
首先简单说一下:
- 准确的讲,JSP就是Servlet,JSP是一个标准的文本文件,在第一次访问时,每一个Web程序(WebApp,一个Context对应一个Web App)Context容器会将JSP文件”翻译”成Servlet,然后在进行调用.
查看conf/web.xml中的配置:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
我们可以知道,当我们访问所有后缀为”.jsp”等请求将会访问JspServlet(功能就是“翻译”JSP并对其实施访问); 具体conf/web.xml中其他配置或者参数含义详见博文Tomcat服务器原理详解
那我们可以知道JSP的入口则是JspServlet
知道入口就可以阅读JSP加载机制源码,那我们首先从网上下载Tomcat源码:源码地址
找到tomcat-trunk/java/org/apache/jasper/servlet/JspServlet类 JspServlet类继承HttpServlet类,重写service(request, response)方法,这个方法负责处理客户请求;
public void service(HttpServletRequest request, HttpServletResponse response){
.......
//判断你请求jsp页面时有没有带?jsp_precompile查询字符串,如果带了就会重新编译
boolean precompile = preCompile(request);
/*判断jsp是否要进行编译处理,并通过jspUri获取JspServletWrapper对象
*若JspServletWrapper对象为空,则判断jspUrl对应的JSP文件是否存在
*/
serviceJspFile(request,response,jspUri,precompile);
.......
}
找到serviceJspFile方法:
private void serviceJspFile(HttpServletRequest request,
HttpServletResponse response, String jspUri,
boolean precompile){
......
JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
if(wrapper == null){
synchronized(this){
wrapper = rctxt.getWrapper(jspUri);
......
//判断jspUri对应的资源是否存在
handleMissingResource(request, response, jspUri);
......
}
}
......
wrapper.service(request, response, precompile);
......
}
我们继续看JspServletWrapper类中service方法:
public void service(HttpServletRequest request,
HttpServletResponse response, boolean procompile){
/*
* 校验逻辑(判断jsp是否可以使用)
*/
......
//Compile编译
......
//设置reload属性为true;即重新加载servlet
ctxt.compile();
......
//加载Jsp文件对应的Servlet
servlet = getServlet();
......
}
继续看JspCompilationContext类中compile方法:
public void compile(){
//创建编译器
createCompile();
if(jspCompiler.isOutDated()){
//JSP文件不存在则抛出异常
if(isRemove())
throw new FileNotFoundException(jspUri);
......
//进行编译
jspCompiler.compiler();
//设置reload属性true,表明jsp文件修改,需要重新加载
jsw.setReload(true);
......
}
}
最后的核心方法Compiler类中的isOutDated(boolean checkClass)方法
public boolean isOutDated(boolean checkClass){
if (jsw != null
&& (ctxt.getOptions().getModificationTestInterval() > 0)) {
/*
* 4秒缓存机制,其中ctxt.getOptions()得到的是EmbeddedServletOptions
* 类,这个类中定义有modificationTestInterval = 4
* 如果jsp文件修改了,需等到4秒之后才会重新编译加载;
*/
if (jsw.getLastModificationTest()
+ (ctxt.getOptions().getModificationTestInterval() * 1000) > System.currentTimeMillis()) {
return false;
}
jsw.setLastModificationTest(System.currentTimeMillis());
}
File targetFile;
//拿到工作目录下的class文件对象
if (checkClass) {
targetFile = new File(ctxt.getClassFileName());
} else {
targetFile = new File(ctxt.getServletJavaFileName());
}
if (!targetFile.exists()) {
return true;
}
long targetLastModified = targetFile.lastModified();
if (checkClass && jsw != null) {
jsw.setServletClassLastModifiedTime(targetLastModified);
}
/*
* 判断jsp文件的修改时间,实际处理就是将jsp文件的时间与class文件时间进行对比比较
*/
Long jspRealLastModified = ctxt.getLastModified(ctxt.getJspFile());
if (jspRealLastModified.longValue() < 0) {
// Something went wrong - assume modification
return true;
}
//比较JSP文件与class文件的时间戳
if (targetLastModified != jspRealLastModified.longValue()) {
if (log.isDebugEnabled()) {
log.debug("Compiler: outdated: " + targetFile + " "+ targetLastModified);
}
return true;
}
}
最后总结:Tomcat热部署就是当Context容器检测JSP发生修改,就会重新新建一个类加载器重新加载JSP文件对应的Servlet;
Tomcat中JSP热部署机制时序图所示:
最后贴一个大牛博客中的一张图Tomcat的热部署的分析