How JSP is compiled into servlet and provides services

Overview

The server that provides JSP request service externally is JspServlet, which inherits from HttpServlet. The core service entrance is in the service method, and the general process is as follows:

  1. First obtain the requested jspUri, if the client initiates a request: https://xxx.xx.com/jsp/test.jsp, then the obtained jspUri is: /jsp/test.jsp

  2. Then check whether the cache (Map structure) contains the JspServletWrapper of the jspUri, if not, you need to create a JspServletWrapper and cache it, and call the service method of JspServletWrapper

  3. If it is in development mode, or the first request, then you need to execute the JspCompilationContext.compile() method

  4. In the JspCompilationContext.compile method, it will judge whether the file has been updated (out dated) based on the lastModified of the jsp file. If it has been updated, you need to delete the related files generated before, and then empty the jspLoader (when you need to load it later) If the jspLoader is empty, a new jspLoader will be created), call the Compiler.compile method to generate the servlet, set reload to true (default is true), and then judge whether the servlet needs to be reloaded according to the reload parameter

  5. Call the JspServletWrapper.getServlet method to obtain the servlet that finally provides the service. This process will check whether the servlet needs to be reloaded according to the reload parameter. If reloading is required, then it will get jspLoader to instantiate a new servlet (if the jsp file is found to be expired, then At this time, if the obtained jspLoader is empty, a new jspLoader will be created), and set reload to false

  6. Call the service method of the servlet to provide services. If the servlet implements the SingleThreadModel interface, then synchronized will be used for synchronization control

 

Source code analysis

First look at the core logic of JspServlet, mainly to obtain jspUri and obtain JspServletWrapper, which are the entry service method and serviceJspFile method respectively. The code is as follows (partial):

/**
 * 获取jspUri,然后调用serviceJspFile方法
 */
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String jspUri = this.jspFile;
        String pathInfo;
        if (jspUri == null) {
            pathInfo = (String)request.getAttribute(Constants.JSP_FILE);
            if (pathInfo != null) {
                jspUri = pathInfo;
                request.removeAttribute(Constants.JSP_FILE);
            }
        }

        if (jspUri == null) {
            jspUri = (String)request.getAttribute("javax.servlet.include.servlet_path");
            if (jspUri != null) {
                pathInfo = (String)request.getAttribute("javax.servlet.include.path_info");
                if (pathInfo != null) {
                    jspUri = jspUri + pathInfo;
                }
            } else {
                jspUri = request.getServletPath();
                pathInfo = request.getPathInfo();
                if (pathInfo != null) {
                    jspUri = jspUri + pathInfo;
                }
            }
        }


        boolean precompile = this.preCompile(request);
        this.serviceJspFile(request, response, jspUri, precompile);
    }


/**
 * 主要获取JspServletWrapper,然后调用JspServletWrapper.service方法
 */
private void serviceJspFile(HttpServletRequest request, HttpServletResponse response, String jspUri, boolean precompile) throws ServletException, IOException {
        JspServletWrapper wrapper = this.rctxt.getWrapper(jspUri);
        if (wrapper == null) {
            synchronized(this) {
                wrapper = this.rctxt.getWrapper(jspUri);
                if (wrapper == null) {
                    if (null == this.context.getResource(jspUri)) {
                        this.handleMissingResource(request, response, jspUri);
                        return;
                    }

                    wrapper = new JspServletWrapper(this.config, this.options, jspUri, this.rctxt);
                    this.rctxt.addWrapper(jspUri, wrapper);
                }
            }
        }

        try {
            //核心服务方法
            wrapper.service(request, response, precompile);
        } catch (FileNotFoundException var8) {
            this.handleMissingResource(request, response, jspUri);
        }

    }

Then enter the JspServletWrapper.service method (part of the code):

//注意这个reload是volatile修饰的
private volatile boolean reload = true;

public void service(HttpServletRequest request, HttpServletResponse response, boolean precompile) throws ServletException, IOException, FileNotFoundException {
        Servlet servlet;
        try {
            if (this.ctxt.isRemoved()) {
                throw new FileNotFoundException(this.jspUri);
            }
            //判断development模式和firstTime(首次请求)
            if (!this.options.getDevelopment() && !this.firstTime) {
                if (this.compileException != null) {
                    throw this.compileException;
                }
            } else {
                synchronized (this) {
                    this.firstTime = false;
                    //调用JspCompilationContext.compile方法
                    this.ctxt.compile();
                }
            }
            //获取最终提供服务的servlet
            servlet = this.getServlet();
            if (precompile) {
                return;
            }
        }
        try {
            //根据是否实现SingleThreadModel决定是否需要做同步控制
            if (servlet instanceof SingleThreadModel) {
                synchronized (this) {
                    servlet.service(request, response);
                }
            } else {
                servlet.service(request, response);
            }
        }
    }

Here mainly look at the JspCompilationContext.complie method:

public void compile() throws JasperException, FileNotFoundException {
        this.createCompiler();
        if (this.jspCompiler.isOutDated()) {
            if (this.isRemoved()) {
                throw new FileNotFoundException(this.jspUri);
            }
            try {
                //清楚文件数据
                this.jspCompiler.removeGeneratedFiles();
                //置空jspLoader,现在置null,后面就会创建一个新的JspLoader
                this.jspLoader = null;
                //根据jsp生成servlet的逻辑,实现主要有AntCompiler和JDTCompiler,默认JDTCompiler
                this.jspCompiler.compile();
                //设置reload为true,后面根据reload参数判断是否需要重新加载
                this.jsw.setReload(true);
                this.jsw.setCompilationException((JasperException) null);
            }
        }
    }

It should be noted that the judgment of the isOutDated method is not simply to check whether the jsp file is updated for each request, but there is an interval. If the time of this check for update is within the last check for update + interval, it means there is no Exceed the interval time, then will not check the update of the jsp file. This is what we call the jsp hot update delay effective, isOutDated is the method of Compiler, as follows (part of the code):

    public boolean isOutDated(boolean checkClass) {
        if (this.jsw != null && this.ctxt.getOptions().getModificationTestInterval() > 0) {
            //getModificationTestInterval就是检查最短间隔时间,单位为秒
            if (this.jsw.getLastModificationTest() + (long)(this.ctxt.getOptions().getModificationTestInterval() * 1000) > System.currentTimeMillis()) {
                return false;
            }

            this.jsw.setLastModificationTest(System.currentTimeMillis());
        }

        Long jspRealLastModified = this.ctxt.getLastModified(this.ctxt.getJspFile());
        if (jspRealLastModified < 0L) {
            return true;
        } else {
            long targetLastModified = 0L;
            File targetFile;
            if (checkClass) {
                targetFile = new File(this.ctxt.getClassFileName());
            } else {
                targetFile = new File(this.ctxt.getServletJavaFileName());
            }

            if (!targetFile.exists()) {
                return true;
            } else {
                targetLastModified = targetFile.lastModified();
                if (checkClass && this.jsw != null) {
                    this.jsw.setServletClassLastModifiedTime(targetLastModified);
                }

                if (targetLastModified != jspRealLastModified) {
                    if (this.log.isDebugEnabled()) {
                        this.log.debug("Compiler: outdated: " + targetFile + " " + targetLastModified);
                    }

                    return true;
                } else if (this.jsw == null) {
                    return false;
                } 
            }
        }

In addition, the compilation of JSP is also involved here. The compilation is mainly done by the org.apache.jasper.compiler.Compiler compiler. Compiler is an abstract class. Two implementations are provided in apache-jsp: AntCompiler and JDTCompiler, which are used by default The compiler is JDTCompiler.

Finally, return to the JspServletWrapper.getServlet method:

private volatile boolean reload = true;
public Servlet getServlet() throws ServletException {
        //reload是被volatile修饰的一个boolean变量
        //这里进行双重检测
        if (this.reload) {
            synchronized (this) {
                if (this.reload) {
                    //需要重载
                    this.destroy();
                    Servlet servlet;
                    try {
                        InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(this.config);
                        //创建一个新的serlvet实例对象,注意这里的getJspLoader方法
                        servlet = (Servlet) instanceManager.newInstance(this.ctxt.getFQCN(), this.ctxt.getJspLoader());
                    } catch (Exception var6) {
                        Throwable t = ExceptionUtils.unwrapInvocationTargetException(var6);
                        ExceptionUtils.handleThrowable(t);
                        throw new JasperException(t);
                    }

                    servlet.init(this.config);
                    if (!this.firstTime) {
                        this.ctxt.getRuntimeContext().incrementJspReloadCount();
                    }
                    this.theServlet = servlet;
                    this.reload = false;
                }
            }
        }
        return this.theServlet;
    }

It can be seen that a double detection mechanism is used in the method to determine whether reloading is required, and the reload parameter is modified by volatile to ensure visibility. When creating a new servlet instance, the classLoader is obtained through the JspCompilationContext.getJspLoader method. Take a look at the logic of this method:

public ClassLoader getJspLoader() {
        if (this.jspLoader == null) {
            this.jspLoader = new JasperLoader(new URL[]{this.baseUrl}, this.getClassLoader(), this.rctxt.getPermissionCollection());
        }

        return this.jspLoader;
    }

In the previous logic of JspCompilationContext.complie, if it is detected that the jsp file has been updated (expired), then the jspLoader will be set to null, at this time a new jspLoader (JasperLoader) will be created, and then the new loader will be used to load the new one Servlet, in order to complete the hot update of the jsp, the old classloader will be directly recycled by the GC later.

Guess you like

Origin blog.csdn.net/huangzhilin2015/article/details/114893129