为了账号安全,请及时绑定邮箱和手机立即绑定

Tomcat 架构分析(三) Jasper模块

标签:
Java
Tomcat 架构分析(三) Jsper模块

图片描述

前言:Jsper模块: 负责jsp页面的解析、jsp属性的验证,同时也负责将jsp页面动态转换为java代码并编译成class文件。

1.简介

Tomcat 8.5使用Jasper 2 JSP引擎来实现JavaServer Pages 2.3规范。
Jasper 2被重新设计,显著改善了Jasper的表现。除了一般的代码改进之外,以下的更改还包括:

JSP Custom Tag Pooling - 为JSP自定义标记实例化的java对象现在可以被池化和重用。这极大地提高了使用自定义标记的JSP页面的性能。

Background JSP compilation - 如果您对已经编译过的JSP页面进行了更改,那么Jasper 2可以在后台重新编译该页面。以前编译的JSP页面仍然可用来服务请求。一旦新页面被成功编译,它将取代旧页面。这有助于提高生产服务器上JSP页面的可用性。
Recompile JSP when included page changes - Jasper 2现在可以检测到,从JSP中包含的一个页面已经改变了,然后重新编译了父JSP。

JDT used to compile JSP pages - 现在,Eclipse JDT Java编译器被用于执行JSP Java源代码编译。这个编译器从容器类加载器加载源依赖项。Ant和javac仍然可以使用。

Jasper是org.apache.jasper.servlet.JspServlet使用servlet实现类,下面的coding是对JSPServlet源码的分析。

/**
 * Copyright © 2017 http://blog.csdn.net/noseparte © Like the wind, like rain
 * @Author Noseparte
 * @Compile 2017年12月7日 -- 下午4:34:36
 * @Version 1.0
 * @Description  JspServlet源码分析
 */
public class JspServlet extends HttpServlet implements PeriodicEventListener {

      /**
       * @see transient修饰词释义
       * java语言的关键字,变量修饰符,如果用transient声明一个实例变量,
       * 当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。
       */
      private static final long serialVersionUID = 1L;
    private final transient Log log = LogFactory.getLog(JspServlet.class);      
    private transient ServletContext context;   //ServletContext对象包含在ServletConfig对象中,当servlet被初始化时,Web服务器提供servlet。
    private ServletConfig config;  //servlet容器使用的servlet配置对象在初始化期间将信息传递给servlet。
    private transient Options options; //一个类来保存所有与JSP引擎相关的init参数。
    private transient JspRuntimeContext rctxt; //用于跟踪JSP编译时文件依赖项的类
    private String jspFile;
    /**
     * javax.servlet.GenericServlet由servlet容器调用,以指示servlet正在被放置到服务中。
     * 看到Servlet.init(javax.servlet.ServletConfig)。
     * 该实现存储从servlet容器接收的ServletConfig对象,以供以后使用。
     * 当覆盖该方法的这种形式时,调用super.init(config)。
     * @param config
     */
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        this.config = config;
        this.context = config.getServletContext();
        String engineOptionsName = config.getInitParameter("engineOptionsClass");
        if ((Constants.IS_SECURITY_ENABLED) && (engineOptionsName != null)) {
            this.log.info(Localizer.getMessage("jsp.info.ignoreSetting",
                    "engineOptionsClass", engineOptionsName));
            engineOptionsName = null;
        }
        if (engineOptionsName != null) {
            try {
                ClassLoader loader = Thread.currentThread()
                                           .getContextClassLoader();
                Class<?> engineOptionsClass = loader.loadClass(engineOptionsName);
                Class<?>[] ctorSig = { ServletConfig.class, ServletContext.class };
                Constructor<?> ctor = engineOptionsClass.getConstructor(ctorSig);
                Object[] args = { config, this.context };
                this.options = ((Options) ctor.newInstance(args));
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                this.log.warn("Failed to load engineOptionsClass", e);
                this.options = new EmbeddedServletOptions(config, this.context);
            }
        } else {
            this.options = new EmbeddedServletOptions(config, this.context);
        }
        this.rctxt = new JspRuntimeContext(this.context, this.options);
        if (config.getInitParameter("jspFile") != null) {
            this.jspFile = config.getInitParameter("jspFile");
            try {
                if (null == this.context.getResource(this.jspFile)) {
                    return;
                }
            } catch (MalformedURLException e) {
                throw new ServletException("cannot locate jsp file", e);
            }
            try {
                if (SecurityUtil.isPackageProtectionEnabled()) {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                            public Object run()
                                throws IOException, ServletException {
                                JspServlet.this.serviceJspFile(null, null,
                                    JspServlet.this.jspFile, true);
                                return null;
                            }
                        });
                } else {
                    serviceJspFile(null, null, this.jspFile, true);
                }
            } catch (IOException e) {
                throw new ServletException("Could not precompile jsp: " +
                    this.jspFile, e);
            } catch (PrivilegedActionException e) {
                Throwable t = e.getCause();
                if ((t instanceof ServletException)) {
                    throw ((ServletException) t);
                }
                throw new ServletException("Could not precompile jsp: " +
                    this.jspFile, e);
            }
        }
        if (this.log.isDebugEnabled()) {
            this.log.debug(Localizer.getMessage("jsp.message.scratch.dir.is",
                    this.options.getScratchDir().toString()));
            this.log.debug(Localizer.getMessage(
                    "jsp.message.dont.modify.servlets"));
        }
    }
    /**
     * 返回JspServletWrappers存在的jsp数量
     * @return
     */
    public int getJspCount() {
        return this.rctxt.getJspCount();
    }
    /**
     * 重新设置JSP重新加载计数器。
     * @param count
     */
    public void setJspReloadCount(int count) {
        this.rctxt.setJspReloadCount(count);
    }
    /**
     * 获取已重新加载的jsp的数量。
     * @return
     */
    public int getJspReloadCount() {
        return this.rctxt.getJspReloadCount();
    }
    /**
     * 获得JSP限制队列中JSP的数量
     * @return
     */
    public int getJspQueueLength() {
        return this.rctxt.getJspQueueLength();
    }
    /**
     * 获得已卸载的jsp的数量。
     * @return
     */
    public int getJspUnloadCount() {
        return this.rctxt.getJspUnloadCount();
    }
    boolean preCompile(HttpServletRequest request) throws ServletException {
        String queryString = request.getQueryString();
        if (queryString == null) {
            return false;
        }
        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);
        if (value.equals("true")) {
            return true;
        }
        if (value.equals("false")) {
            return true;
        }
        throw new ServletException("Cannot have request parameter " +
            Constants.PRECOMPILE + " set to " + value);
    }
    /**
     * javax.servlet.http.HttpServlet从公共服务方法接收标准HTTP请求,并将它们分派到这个类中定义的doMethod方法。
     * 该方法是servlet.service(javax.servlet的一个特定于http的版本。
     * ServletRequest,javax.servlet.ServletResponse)方法。没有必要覆盖这个方法。
     */
    public void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        String jspUri = this.jspFile;
        if (jspUri == null) {
            jspUri = (String) request.getAttribute(
                    "javax.servlet.include.servlet_path");
            if (jspUri != null) {
                String pathInfo = (String) request.getAttribute(
                        "javax.servlet.include.path_info");
                if (pathInfo != null) {
                    jspUri = jspUri + pathInfo;
                }
            } else {
                jspUri = request.getServletPath();
                String pathInfo = request.getPathInfo();
                if (pathInfo != null) {
                    jspUri = jspUri + pathInfo;
                }
            }
        }
        if (this.log.isDebugEnabled()) {
            this.log.debug("JspEngine --> " + jspUri);
            this.log.debug("\t     ServletPath: " + request.getServletPath());
            this.log.debug("\t        PathInfo: " + request.getPathInfo());
            this.log.debug("\t        RealPath: " +
                this.context.getRealPath(jspUri));
            this.log.debug("\t      RequestURI: " + request.getRequestURI());
            this.log.debug("\t     QueryString: " + request.getQueryString());
        }
        try {
            boolean precompile = preCompile(request);
            serviceJspFile(request, response, jspUri, precompile);
        } catch (RuntimeException e) {
            throw e;
        } catch (ServletException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(e);
        }
    }
    /**
     * javax.servlet.GenericServlet由servlet容器调用,
     * 以指示servlet正在被从服务中取出/销毁
     */
    public void destroy() {
        if (this.log.isDebugEnabled()) {
            this.log.debug("JspServlet.destroy()");
        }
        this.rctxt.destroy();
    }
    /**
     * 周期性的eventlistener,执行一个周期性任务,比如重新加载,等等。
     */
    public void periodicEvent() {
        this.rctxt.checkUnload();
        this.rctxt.checkCompile();
    }
    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)) {
                        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 fnfe) {
            handleMissingResource(request, response, jspUri);
        }
    }
    private void handleMissingResource(HttpServletRequest request,
        HttpServletResponse response, String jspUri)
        throws ServletException, IOException {
        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));
        }
        try {
            response.sendError(404, request.getRequestURI());
        } catch (IllegalStateException ise) {
            this.log.error(Localizer.getMessage("jsp.error.file.not.found",
                    jspUri));
        }
    }
}

2.配置

在默认情况下,Jasper在做web应用程序开发时被配置为使用。

实现Jasper的servlet是在全局中使用init参数配置的 $CATALINA_BASE/conf/web.xml.

  • checkInterval -如果开发是false,而checkInterval大于零,则启用后台编译。checkInterval是在检查之间的几秒之间的时间,以查看是否需要重新编译JSP页面(及其依赖的文件)。默认值0秒。

  • classdebuginfo - 应该使用调试信息编译类文件吗?真或假,默认为真。

  • classpath -定义用于编译生成的servlet的类路径。该参数如果产生影响只有org.apache.jasper.Constants.SERVLET_CLASSPATH不设置。当Jasper在Tomcat中使用时,这个属性总是被设置的。默认情况下,类路径是根据当前的web应用程序动态创建的。

  • compiler -应该使用哪个编译器来编译JSP页面。此值的有效值与Ant的javac任务的编译器属性相同。如果没有设置值,那么将使用默认的Eclipse JDT Java编译器,而不是使用Ant。没有默认值。如果该属性设置为setenv。应该使用shbat来添加ant.jar,ant-launcher jar。jar和工具。jar到CLASSPATH环境变量。

  • compilerSourceVM -什么JDK版本是与之兼容的? (Default value: 1.7)

  • compilerTargetVM - 生成的文件与什么JDK版本兼容? (Default value: 1.7)

  • development -Jasper在发展模式中使用了吗?如果是正确的,可以通过变更测试间隔参数指定对jsp进行修改的频率。真或假,默认为真。

  • displaySourceFragment - 是否应该将一个源片段包含在异常消息中?真或假,默认为真。

  • dumpSmap - 是否应该将JSR45调试的SMAP信息转储到文件中?真或假,默认为假。如果抑制映射为真,则是假的。

  • enablePooling - 确定是否启用了标记处理程序池。这是一个编译选项。它不会改变已经编译的jsp的行为。真或假,默认为真。

  • engineOptionsClass -允许指定用于配置Jasper的选项类。如果不存在,将使用默认的嵌入服务。如果在SecurityManager下运行该选项会被忽略.

  • errorOnUseBeanInvalidClassAttribute -当一个使用bean操作中的类属性值不是一个有效的bean类时,贾斯珀是否会发出错误?真或假,默认为真。

  • fork - 是否有Ant fork JSP页面编译,以便在与Tomcat不同的JVM中执行它们?真或假,默认为真。

  • genStringAsCharArray - 是否应该将文本字符串作为char数组生成,以提高某些情况下的性能?默认的错误。

  • ieClassId - 当使用了标签时,类id值将被发送给Internet Explorer。默认clsid:8 ad9c840 -044 e - 11 - d1 b3e9 f499d93——00805。

  • javaEncoding - 用于生成Java源文件的Java文件编码。use UTF8违约。

  • keepgenerated - 我们应该为每个页面保留生成的Java源代码,而不是删除它吗?真或假,默认为真。

  • mappedfile - 我们是否应该在每个输入行中生成静态内容,以简化调试工作?真或假,默认为真。

  • maxLoadedJsps -将为web应用程序加载的jsp的最大数量。如果加载的jsp数量超过了这一数量,那么最近使用的jsp将会被卸载,以便在任何时候加载的jsp的数量不会超过这个限制。0或更少的值表示没有限制。默认为-1

  • jspIdleTimeout - 在它被卸载之前,JSP可以在秒内空闲的时间。0或更少的值表示永远不会卸载。默认为 -1

  • modificationTestInterval -在指定的时间间隔(以秒为单位)从上一次检查JSP进行修改时,导致JSP(及其相关文件)在指定的时间间隔内不被检查。值为0将导致在每次访问时对JSP进行检查。仅用于开发模式。默认为4秒。

  • recompileOnFail -如果一个JSP编译失败了,是否应该忽略修改后的测试间隔,而下一个访问将触发重新编译的尝试?只在开发模式中使用,默认情况下是禁用的,因为编译可能很昂贵,可能导致资源的过度使用。

  • scratchdir -在编译JSP页面时,我们应该使用什么scratch目录?默认是当前web应用程序的工作目录。如果在SecurityManager下运行,这个选项会被忽略。

  • suppressSmap - 对于jsr 45调试的SMAP信息是否应该被抑制?真或假,默认为假。

  • trimSpaces - 应该删除完全由空格组成的模板文本吗?真或假,默认为假。

  • xpoweredBy - 确定由生成的servlet添加的x-power-响应头是否被添加。真或假,默认为假。

  • strictQuoteEscaping -当scriptlet表达式用于属性值时,是否应该严格地应用JSP.1.6中的规则来转义引号中的字符?真或假,默认为真。

  • quoteAttributeEL -当在JSP页面的属性值中使用EL时,是否应该将JSP.1.6中所描述的属性引用的规则应用到表达式中?真或假,默认为真。

Eclipse JDT中的Java编译器被包括为默认编译器。它是一个高级的Java编译器,它将加载来自Tomcat类加载器的所有依赖项,这将极大地帮助在大型安装中使用数十个jar进行编译。在快速服务器上,这将允许甚至大型JSP页面的次秒重新编译周期。
在以前的Tomcat版本中使用的Apache Ant可以通过配置上面解释的编译器属性来代替新的编译器。

3.知识点

正如在bug 39089中所描述的,一个已知的JVM问题,bug 6294277,可能会导致 java.lang.InternalError: name is too long to represent,当编译一个很大的jsp文件时 。如果观察到这一点,那么就可以用下面的一个方法来解决这个问题:

减小JSP的大小

通过将抑制映射设置为true,禁用SMAP生成和JSR-045支持。

4.产品配置

可以完成的主要JSP优化是对JSP的预编译。然而,这可能是不可能的(例如,在使用jsp-property group特性时)或实际操作,在这种情况下,贾斯珀servlet的配置变得至关重要。

当在一个生产Tomcat服务器中使用Jasper 2时,您应该考虑从默认配置中做出以下更改:

  • development - 要禁用JSP页面编译的访问检查,将此设置为false。

  • genStringAsCharArray - 为了生成更高效的char数组,请将其设置为true。
  • modificationTestInterval -如果开发必须以任何理由(如jsp的动态生成)被设置为true,那么将其设置为高值将会极大地提高性能。
  • trimSpaces - 为了从响应中删除无用的字节,将其设置为true。

5.Web应用程序

使用Ant是使用JSPC编译web应用程序的首选方法。注意,在预编译jsp时,SMAP信息只有在最后的类中包含,如果抑制映射是假的,编译是正确的。使用下面给出的脚本(一个类似的脚本包含在"deployer"下载中)来预编译一个webapp:

<project name="Webapp Precompilation" default="all" basedir=".">

   <import file="${tomcat.home}/bin/catalina-tasks.xml"/>

   <target name="jspc">

    <jasper
             validateXml="false"
             uriroot="${webapp.path}"
             webXmlFragment="${webapp.path}/WEB-INF/generated_web.xml"
             outputDir="${webapp.path}/WEB-INF/src" />

  </target>

  <target name="compile">

    <mkdir dir="${webapp.path}/WEB-INF/classes"/>
    <mkdir dir="${webapp.path}/WEB-INF/lib"/>

    <javac destdir="${webapp.path}/WEB-INF/classes"
           optimize="off"
           debug="on" failonerror="false"
           srcdir="${webapp.path}/WEB-INF/src"
           excludes="**/*.smap">
      <classpath>
        <pathelement location="${webapp.path}/WEB-INF/classes"/>
        <fileset dir="${webapp.path}/WEB-INF/lib">
          <include name="*.jar"/>
        </fileset>
        <pathelement location="${tomcat.home}/lib"/>
        <fileset dir="${tomcat.home}/lib">
          <include name="*.jar"/>
        </fileset>
        <fileset dir="${tomcat.home}/bin">
          <include name="*.jar"/>
        </fileset>
      </classpath>
      <include name="**" />
      <exclude name="tags/**" />
    </javac>

  </target>

  <target name="all" depends="jspc,compile">
  </target>

  <target name="cleanup">
    <delete>
        <fileset dir="${webapp.path}/WEB-INF/src"/>
        <fileset dir="${webapp.path}/WEB-INF/classes/org/apache/jsp"/>
    </delete>
  </target>

</project>

可以使用下面的命令行来运行脚本(用Tomcat基路径替换标记,以及应该预先编译的webapp的路径):

$ANT_HOME/bin/ant -Dtomcat.home=<$TOMCAT_HOME> -Dwebapp.path=<$WEBAPP_PATH>

然后,在预编译期间生成的servlet的声明和映射必须添加到web应用程序部署描述符中。添加 ${webapp.path}/WEB-INF/generated_web.xml 到 ${webapp.path}/WEB-INF/web.xml 文件中.重新启动web应用程序(使用管理器)并对其进行测试,以验证它是否与预编译的servlet运行良好。在web应用程序部署描述符中放置适当的标记也可以用于自动插入生成的servlet声明和使用Ant过滤功能的映射。这实际上是如何将所有与Tomcat一起分发的web应用程序作为构建过程的一部分自动编译的。

在jasper任务中,您可以使用选项addWebXmlMappings映射来自动合并${webapp.path}/WEB-INF/generated_web.xml。当前的web应用程序部署描述符在$webapp.path/WEB-INF/web.xml中。当您想要在jsp中使用Java 6特性时,添加以下javac编译器任务属性:source="1.6"目标="1.6"。对于实时应用程序,您还可以使用 optimize="on"进行编译,而不需要调试信息debug="off"。

当您不想在第一个jsp语法错误中停止jsp生成时,使用failOnError="false",并使用showSuccess="true"将所有成功的jsp都打印出来。有时候,当您在$webapp中清理生成的java源文件时,这是非常有用的。路径} ${webapp.path}/WEB-INF/src和编译jsp servlet类${webapp.path}/WEB-INF/classes/org/apache/jsp.

提示:

  • 当您切换到另一个Tomcat版本时,使用新的Tomcat版本重新生成并重新编译您的jsp。

  • 使用java系统属性在服务器运行时禁用PageContext池org.apache.jasper.runtime.JspFactoryImpl.USE_POOL
    = false。和限制缓冲org.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER = true。请注意,更改默认值可能会影响性能,但它将根据应用程序的不同而有所不同。

6.优化

在Jasper提供的一些扩展点中,用户可以优化他们的环境的行为。

这些扩展点的第一个是标记插件机制。这允许为web应用程序提供可替代的标记处理程序实现。标签插件是通过tagPlugins.xml文件位于WEB-INF中。JSTL的一个示例插件包含在Jasper中。第二个扩展点是表达式语言解释器。可以通过ServletContext配置可选的解释器。

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消