Tomcat编译jsp的条件

当我们请求jsp页面时,tomcat将请求交由JspServlet来处理,servlet处理请求的方法为service方法,源代码如下(源码太长,这里只保留关键代码)

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

    ...
    try {
        boolean precompile = preCompile(request);
        serviceJspFile(request, response, jspUri, precompile);
    } catch (RuntimeException e) {
        ...
    }
}
preCompile(request)方法的作用就是检查请求jsp页面时有没有带?jsp_precompile查询字符串,然后将检查结果precompile传入serviceJspFile方法(其作用见后面讲解),而对jsp页面的处理主要是通过该方法来实现的,源代码如下。
private void serviceJspFile(HttpServletRequest request,
                                HttpServletResponse response, String jspUri,
                                boolean precompile)
    throws ServletException, IOException {

    JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
    if (wrapper == null) {
        synchronized(this) {
            wrapper = rctxt.getWrapper(jspUri);
            if (wrapper == null) {
                // Check if the requested JSP page exists, to avoid
                // creating unnecessary directories and files.
                if (null == context.getResource(jspUri)) {
                    handleMissingResource(request, response, jspUri);
                    return;
                }
                wrapper = new JspServletWrapper(config, options, jspUri,
                                                rctxt);
                rctxt.addWrapper(jspUri,wrapper);
            }
        }
    }

    try {
        wrapper.service(request, response, precompile);
    } catch (FileNotFoundException fnfe) {
        handleMissingResource(request, response, jspUri);
    }

}

它的主要作用就是检查JspRuntimeContext中是否已经存在与当前jsp文件相对应的JspServletWrapper(如果存在的话,说明这个文件之前已经被访问过了)。有的话就取出来,没有则检查对应的jsp文件是否存在,如果存在的话就新创建一个 JspServletWrapper并添加到JspRuntimeContext中去。随后会进入JspServletWrapper的service方法,源代码如下(同样,保留关键代码)

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

    Servlet servlet;

    try {
        ...

        /*
            * (1) Compile
            */
        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) (Re)load servlet class file
            */
        servlet = getServlet();

        // If a page is to be precompiled only, return.
        if (precompile) {
            return;
        }

    } catch (ServletException ex) {
        ...
    }
        /*
            * (4) Service request
            */
        if (servlet instanceof SingleThreadModel) {
            // sync on the wrapper so that the freshness
            // of the page is determined right before servicing
            synchronized (this) {
                servlet.service(request, response);
            }
        } else {
            servlet.service(request, response);
        }
    } catch (UnavailableException ex) {
        ...
    }
}

主要流程就是编译jsp,若是precompile为真,即请求中带有?jsp_precompile查询字符串,则直接返回,否则进入编译出来的 jsp的service方法,开始执行jsp内的代码。判断是否要对该jsp页面进行编译的关键代码为

if (options.getDevelopment() || firstTime ) {
    synchronized (this) {
        firstTime = false;

        // The following sets reload to true, if necessary
        ctxt.compile();
    }
}

这段代码先判断tomcat是否处于development模式中,或者当前jsp是不是第一次被访问。如果是,则会进入

ctxt.compile();
ctxt是JspCompilationContext的对象,该对象内封装了与编译jsp相关的所有信息,每一个JspServletWrapper里面都有一个自己的 JspCompilationContext。也就是在compile方法里面,对jsp文件的时间戳以及编译条件进行了判断。
public void compile() throws JasperException, FileNotFoundException {
    createCompiler();
    if (jspCompiler.isOutDated()) {
        if (isRemoved()) {
            throw new FileNotFoundException(jspUri);
        }
        try {
            jspCompiler.removeGeneratedFiles();
            jspLoader = null;
            jspCompiler.compile();
            jsw.setReload(true);
            jsw.setCompilationException(null);
        } catch (JasperException ex) {
            ...
        }
    }
}

JspCompilationContext对象内有一个Compile对象jspCompiler,用它来对jsp进行更新检查以及编译。jspCompile.isOutDated方法代码如下:

public boolean isOutDated() {
    return isOutDated(true);
}
 isOutDated的重载方法如下:
public boolean isOutDated(boolean checkClass) {
    if (jsw != null
            && (ctxt.getOptions().getModificationTestInterval() > 0)) {

        if (jsw.getLastModificationTest()
                + (ctxt.getOptions().getModificationTestInterval() * 1000) > System
                .currentTimeMillis()) {
            return false;
        }
        jsw.setLastModificationTest(System.currentTimeMillis());
    }

    Long jspRealLastModified = ctxt.getLastModified(ctxt.getJspFile());
    if (jspRealLastModified.longValue() < 0) {
        // Something went wrong - assume modification
        return true;
    }
    ...
}

我们可以看到通过getModificationTestInterval()方法来获取tomcat中web.xml设置的modificationTestInterval参数,来检测是否需要对jsp页面进行重新编译,并通过jsw.setLastModificationTest()方法来记录此次的编译时间,用以下次对jsp的编译条件进行判断。若距离上次编译时间超过了modificationTestInterval的值,则通过jspCompiler的compile()方法来对jsp文件进行编译。

好了,上述就是tomcat在开发模式下,对jsp页面时间戳的检测与编译时机的确定方式,那在生产模式是怎么确定的呢?

我们知道,生产模式下,tomcat会用后台编译的方式来对jsp进行编译,后台编译程序会调用JspRuntimeContext对象的checkCompile()方法来对jsp页面进行检测,源代码如下:

public void checkCompile() {
    if (lastCompileCheck < 0) {
        // Checking was disabled
        return;
    }
    long now = System.currentTimeMillis();
    if (now > (lastCompileCheck + (options.getCheckInterval() * 1000L))) {
        lastCompileCheck = now;
    } else {
        return;
    }

    Object [] wrappers = jsps.values().toArray();
    for (int i = 0; i < wrappers.length; i++ ) {
        JspServletWrapper jsw = (JspServletWrapper)wrappers[i];
        JspCompilationContext ctxt = jsw.getJspEngineContext();
        // JspServletWrapper also synchronizes on this when
        // it detects it has to do a reload
        synchronized(jsw) {
            try {
                ctxt.compile();
            } catch (FileNotFoundException ex) {
                ctxt.incrementRemoved();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                jsw.getServletContext().log("Background compile failed",
                                            t);
            }
        }
    }

}

可以看到,在该方法中,通过getCheckInterval()方法来获取tomcat中web.xml设置的checkInterval参数,来检测是否需要对jsp页面进行重新编译,并更新lastCompileCheck的值,用以下次编译条件的检测。若距离上次编译时间超过了checkInterval的值,则通过ctxt.compile()方法来对jsp文件进行编译。

tomcat会在work目录里把这个jsp页面转成.java文件,比如将index.jsp转换成index_jsp.java文件,而后编译为index_jsp.class文件,最后tomcat容器通过类加载器把这个index_jsp.class类装载入内存,进行响应客户端的工作。

猜你喜欢

转载自blog.csdn.net/qq_38293564/article/details/80372283