解读JSP的解析过程

互联网上,这方面的资料实在太少了,故把自己研究的一些结果公布出来。

首先,问大家几个问题,看大家能不能回答出来,或者在网上能不能找到答案:

1、page、include、taglib这三个编译指令,执行的顺序是什么?

2、JSP文件中的Java代码、编译指令、动作指令、EL标签、第三方标签、静态文本等,被编译的顺序是什么?

3、常用的、与JSP解析/编译相关的类有哪些?换句话说,JSP解析、编译技术是建立在哪些接口和工具之上的?

4、JSP技术所有的编译指令和动作指令有哪些?

5、JSP技术是由谁发起的,现在有哪几个标准?都有哪些服务器或者项目支持JSP的解析和编译?

先回答简单、基础性的问题:

回答问题5:

JSP的发起者为——Sun Microsystems, Inc.

JSP是由Java Servlets发展而来。

JSP和Servlet一样,都是在Java Community Process(Java社区组织)等众多人参与下,共同开发出来的,并且最终制定了一些规范和标准。Java体系中的规范和技术,会被制定成JSR规范。JSP和Servlet也属于这个规范之中,例如JSR-53规定了JSP 1.2和Servlet 2.4的规范,JSR-152规定了JSP 2.0的规范。(详见http://zh.wikipedia.org/wiki/JSR)

JSP常用的版本有1.2、2.0和2.1(最新版本),其中1.2版本最大的进步在于优化了JSTL(JavaServer Pages Standard Tag Library),已经过时了(因为它不支持EL表达式),2.0在1.2的基础上有了巨大的进步,因为它引入了Expression Language(EL表达式)、简化了tag标签的扩展、增强了XML的语法,以及其他一些重要改进,我是翻译过来的,官方原文如下:

The JSP 2.0 specification (JSR-152) substantially extended the technology by integrating a simple yet powerful expression language, simplifying the tag extension API, and enhancing the pure XML syntax,

among other important enhancements. These enhancements greatly reduced the learning curve of the technology, warranting a major version number upgrade.

而JSP 2.1是在JSP 2.0的基础上,为JSF而生的,简而言之,JSP 2.1加强了EL,更能够适应J2EE、J2SE、Servlet API的发展。

JSP 2.1 to enhance the expression language to meet the needs of JSF technology. Many of these enhancements are likely to be useful in other contexts as well.

Enhancements to be considered for the JSP 2.1 expression language include, but are not limited to, the following:

- moving the expression language chapter into its own specification document, to be delivered under this JSR with consultation from the JavaServer Faces expert group

- ability to redefine the behavior of the "." operator through a Property Resolver API

- ability to plug in Variable Resolvers on a per-application and per-page basis

- ability to plug in Property Resolvers on a per-application and per-page basis

- ability to express references to bean methods using the expression language and invoking those methods via a Method Binding API

- ability to express references to bean properties using the expression language and getting/setting those attributes via a Property Binding API

- ability to defer expression evaluation until a time of a tag handler's choosing

目前支持JSP的引擎有很多,NB的人自己都可以写一套出来,最常见的WebLogic、Tomcat、WebSphere、Resin(据说效率比Tomcat等要高,网易等大网站都在用)。

回答问题3:

第一个想到的就是Java Servlet API,即servlet-api.jar ,然后JSP的解析有一个工具包,即jasper-compiler.jar,因为JSP是一个标准,那么在解析工具包之上,应该有一个规范的API包,即jsp-api.jar,JSTL是JSP规范的一部分,故还有jstl-api.jar,另外,还有expression language扩展包,el-api.jar。还注意到一点,JSP是基于XML的,故也涉及到XML的解析(用的是SAX)。

补充一点,可以手动调用程序来解析(也叫预编译)JSP,在Tomcat或WebSphere下,是调用JspC这个工具类:

Tomcat下,jsp是通过org.apache.jasper.JspC编译工具将JSP 页面的预编译,在WAS下,是通过 com.ibm.websphere.ant.tasks.JspC进行预编译。

回答问题4和1:

JSP技术所有的编译指令和动作指令,可以参看官方API文档,我这里找到的如下:

ATTRIBUTE_ACTION"attribute"

ATTRIBUTE_DIRECTIVE_ACTION"directive.attribute"

BODY_ACTION"body"

DECLARATION_ACTION"declaration"

DIRECTIVE_ACTION"directive."

DOBODY_ACTION"doBody"

ELEMENT_ACTION"element"

EXPRESSION_ACTION"expression"

FALLBACK_ACTION"fallback"

FORWARD_ACTION"forward"

GET_PROPERTY_ACTION"getProperty"

INCLUDE_ACTION"include"

INCLUDE_DIRECTIVE_ACTION"directive.include"

INVOKE_ACTION"invoke"

JSP_ATTRIBUTE_ACTION"jsp:attribute"

JSP_ATTRIBUTE_DIRECTIVE_ACTION"jsp:directive.attribute"

JSP_BODY_ACTION"jsp:body"

JSP_DECLARATION_ACTION"jsp:declaration"

JSP_DOBODY_ACTION"jsp:doBody"

JSP_ELEMENT_ACTION"jsp:element"

JSP_EXPRESSION_ACTION"jsp:expression"

JSP_FALLBACK_ACTION"jsp:fallback"

JSP_FORWARD_ACTION"jsp:forward"

JSP_GET_PROPERTY_ACTION"jsp:getProperty"

JSP_INCLUDE_ACTION"jsp:include"

JSP_INCLUDE_DIRECTIVE_ACTION"jsp:directive.include"

JSP_INVOKE_ACTION"jsp:invoke"

JSP_OUTPUT_ACTION"jsp:output"

JSP_PAGE_DIRECTIVE_ACTION"jsp:directive.page"

JSP_PARAM_ACTION"jsp:param"

JSP_PARAMS_ACTION"jsp:params"

JSP_PLUGIN_ACTION"jsp:plugin"

JSP_ROOT_ACTION"jsp:root"

JSP_SCRIPTLET_ACTION"jsp:scriptlet"

JSP_SET_PROPERTY_ACTION"jsp:setProperty"

JSP_TAG_DIRECTIVE_ACTION"jsp:directive.tag"

JSP_TAGLIB_DIRECTIVE_ACTION"jsp:taglib"

JSP_TEXT_ACTION"jsp:text"

JSP_USE_BEAN_ACTION"jsp:useBean"

JSP_VARIABLE_DIRECTIVE_ACTION"jsp:directive.variable"

OUTPUT_ACTION"output"

PAGE_DIRECTIVE_ACTION"directive.page"

PARAM_ACTION"param"

PARAMS_ACTION"params"

PLUGIN_ACTION"plugin"

ROOT_ACTION"root"

SCRIPTLET_ACTION"scriptlet"

SET_PROPERTY_ACTION"setProperty"

TAG_DIRECTIVE_ACTION"directive.tag"

TAGLIB_DIRECTIVE_ACTION"taglib"

TEXT_ACTION"text"

USE_BEAN_ACTION"useBean"

VARIABLE_DIRECTIVE_ACTION"directive.variable"

其中directive就是编译指令,总结起来,有:page、include、taglib、tag、attribute、variable,关于他们的执行顺序,看解析程序是最有说服力的,程序如下:

[ 摘自org.apache.jasper.compiler.Parser ]

private void parseDirective(Node parent)

throws JasperException

{

this.reader.skipSpaces();

String directive = null;

if (this.reader.matches("page")) {

directive = "<%@ page";

if (this.isTagFile) {

this.err.jspError(this.reader.mark(), "jsp.error.directive.istagfile", directive);

}

parsePageDirective(parent);

} else if (this.reader.matches("include")) {

directive = "<%@ include";

parseIncludeDirective(parent);

} else if (this.reader.matches("taglib")) {

if (this.directivesOnly)

{

return;

}

directive = "<%@ taglib";

parseTaglibDirective(parent);

} else if (this.reader.matches("tag")) {

directive = "<%@ tag";

if (!(this.isTagFile)) {

this.err.jspError(this.reader.mark(), "jsp.error.directive.isnottagfile", directive);

}

parseTagDirective(parent);

} else if (this.reader.matches("attribute")) {

directive = "<%@ attribute";

if (!(this.isTagFile)) {

this.err.jspError(this.reader.mark(), "jsp.error.directive.isnottagfile", directive);

}

parseAttributeDirective(parent);

} else if (this.reader.matches("variable")) {

directive = "<%@ variable";

if (!(this.isTagFile)) {

this.err.jspError(this.reader.mark(), "jsp.error.directive.isnottagfile", directive);

}

parseVariableDirective(parent);

} else {

this.err.jspError(this.reader.mark(), "jsp.error.invalid.directive");

}

this.reader.skipSpaces();

if (!(this.reader.matches("%>")))

this.err.jspError(this.start, "jsp.error.unterminated", directive);

}

再来回答问题2:

根据源码,我看到JSP编译的顺序是这样的:

1-- getJspConfigPageEncoding

2-- determineSyntaxAndEncoding

3-- 解析成 Node.Nodes parsedPage 对象,即取出所有节点

4-- 解析每个节点

步,是从web.xml等配置文件中去读取配置(里面有个<jsp-config>配置),如果配置时设置了统一编码,则使用这种类型的编码,第2步,是根据文件来获取编码,注意看如下一段代码:

if ((jspReader.matches("tag ")) || (jspReader.matches("page")))

{

jspReader.skipSpaces();

Attributes attrs = Parser.parseAttributes(this, jspReader);

encoding = getPageEncodingFromDirective(attrs, "pageEncoding");

if (encoding != null) {

break;

}

encoding = getPageEncodingFromDirective(attrs, "contentType");

if (encoding != null) {

saveEncoding = encoding;

}

}

}

程序首先判断有无编译指令tag或者page,如果有,则检查编译指令是否指定了pageEncoding属性或者contentType属性。根据这种逻辑,可知如下这种写法:

<%@ page contentType="text/html;charset=utf-8" pageEncoding="UTF-8"%>

其实是重复指定了编码,解析时会以pageEncoding为准。

第4步,解析每个节点:

while (reader.hasMoreInput()) {

parser.parseElements(root);

}

这里又分为几个步骤,先看程序:

private void parseElements(Node parent)

throws JasperException

{

this.start = this.reader.mark();

if (this.reader.matches("<%--")) {

parseComment(parent);

} else if (this.reader.matches("<%@")) {

parseDirective(parent);

} else if (this.reader.matches("<jsp:directive.")) {

parseXMLDirective(parent);

} else if (this.reader.matches("<%!")) {

parseDeclaration(parent);

} else if (this.reader.matches("<jsp:declaration")) {

parseXMLDeclaration(parent);

} else if (this.reader.matches("<%=")) {

parseExpression(parent);

} else if (this.reader.matches("<jsp:expression")) {

parseXMLExpression(parent);

} else if (this.reader.matches("<%")) {

parseScriptlet(parent);

} else if (this.reader.matches("<jsp:scriptlet")) {

parseXMLScriptlet(parent);

} else if (this.reader.matches("<jsp:text")) {

parseXMLTemplateText(parent);

} else if ((!(this.pageInfo.isELIgnored())) && (this.reader.matches("${"))) {

parseELExpression(parent, '$');

} else if ((!(this.pageInfo.isELIgnored())) && (this.reader.matches("#{")))

{

parseELExpression(parent, '#');

} else if (this.reader.matches("<jsp:")) {

parseStandardAction(parent);

} else if (!(parseCustomTag(parent))) {

checkUnbalancedEndTag();

parseTemplateText(parent);

}

}

处理的顺序如下:

1-- “<%-- --%>”类型的注释

2-- “<%@ %>”编译指令

3-- “<jsp:directive. %>”编译指令

4-- “<%! %>”声明指令

5-- “<%= %>”表达式指令

6-- “<% %>”嵌入脚本

7-- “<jsp:text >”嵌入文本

8-- “${ }”EL表达式

9-- “#{ }”EL表达式

10-- “<jsp: >”其他jsp动作指令

11-- 自定义的tag标签

然后,再看看jsp中的java代码(ScriptingElement)是怎么执行的:

第一步:

new一个Node节点,然后把java的字符串完整地赋值给Node的text属性,然后把node添加到Parent Node 队列(List)里面。

第二步:读取这些Nodes,将其转换成java源代码,然后在调用java编译器将源代码编译成class文件。(注意:这个功能相当于是把字符串,转换成了java字节码)

这个过程,调用了SmapUtil将上面那些nodes转换成Java源文件,然后调用JDTCompiler工具类,将Java源文件编译成.class文件,我看Tomcat调用的是org.eclipse.jdt.internal.compiler.*包下面的编译工具,实际上JDK也为我们提供了自己手动编译Java文件的方法,JDK 1.6可以用javax.tools.JavaCompiler。

借鉴这个过程,我自己也实现了“字符串——生成Java文件——编译成class文件”的全自动化过程。这个过程很有用,可以允许我们自己在网站上 上传Java文件并编译运行!

关于JSP解析、编译的研究,暂时就告一段落,有兴趣的可以和我交流。

解读JSP的解析过程的更多相关文章

  1. Spring-IOC源码解读2.2-BeanDefinition的载入和解析过程

    1. 对IOC容器来说这个载入过程就像是相当于把BeanDefinition定义的信息转化成Spring内部表示的数据结构.容器对bean的管理和依赖注入过程都是通过对其持有的BeanDefiniti ...

  2. DNS原理及其解析过程 精彩剖析

    本文章转自下面:http://369369.blog.51cto.com/319630/812889 DNS原理及其解析过程 精彩剖析 网络通讯大部分是基于TCP/IP的,而TCP/IP是基于IP地址 ...

  3. DNS解析过程详解

    先说一下DNS的几个基本概念: 一. 根域 就是所谓的“.”,其实我们的网址www.baidu.com在配置当中应该是www.baidu.com.(最后有一点),一般我们在浏览器里输入时会省略后面的点 ...

  4. java中文乱码解决之道(七)-----JSP页面编码过程

    我们知道JSP页面是需要转换为servlet的,在转换过程中肯定是要进行编码的.在JSP转换为servlet过程中下面一段代码起到至关重要的作用. <%@ page language=" ...

  5. DNS解析过程

    参考: http://www.maixj.net/ict/dns-chaxun-9208 http://blog.it985.com/8389.html DNS(Domain Name System) ...

  6. DNS原理及其解析过程【精彩剖析】(转)

      2012-03-21 17:23:10 标签:dig wireshark bind nslookup dns 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否 ...

  7. dig理解DNS的解析过程 - 阿权的书房

    关于DNS的常识,可以阅读附录的一些参考资料.本文旨在尝试举例用dig命令理解这个过程,并非权威知识,仅供参考.测试域名为阿权的书房的域名 www.aslibra.com 和 www.163.com. ...

  8. 解决JSP 不解析EL表达式

    解决JSP 不解析EL表达式,jsp在使用EL表达式的时候发现它不被解析,而是直接以字符串的形式显示了出来,经过查阅资料和实践,终于得知了原因并找到了解决方案 原因是:在默认情况下,Servlet 2 ...

  9. HiveSQL解析过程详解 | 学步园

    HiveSQL解析过程详解 | 学步园   http://www.xuebuyuan.com/2210261.html

随机推荐

  1. 20135328信息安全系统设计基础第二周学习总结(vim、gcc、gdb)

    第三周学习笔记 学习计时:共8小时 读书:1 代码:5 作业:1 博客:7 一.学习目标 熟悉Linux系统下的开发环境 熟悉vi的基本操作 熟悉gcc编译器的基本原理 熟练使用gcc编译器的常用选项 ...

  2. 讽刺的是,我在linux下使用最多的命令,竟然是windows的

    $ history | awk '{print $2}' | sort | uniq -c | sort -nr | head dir vi echo cd vim jobs gcc ls less ...

  3. diff: /../Podfile.lock: No such file or directory

    从github上下载源码运行会报错:问题1描述: diff: /../Podfile.lock: No such file or directory diff: /Manifest.lock: No ...

  4. EntityFramework:值不能为 null。参数名: entitySet 异常解决方案

    昨天EF莫名其妙的,掉所有接口访问都出现如下错误:百度,Google了半天,倒是有很多人都遇到了这个问题,但都没有一个解决方案,或者解决方案无效.通过层层排除,终于找到问题的所在.记录下来,给以后再遇 ...

  5. 用html5+js实现掌机游戏赛车demo

    最近无聊,用html5+js做了一个以前玩过的掌机赛车游戏,顺便学习一下画布的api以及巩固一下js基础. 游戏界面,没做什么美化. 游戏规则:游戏界面分为三列,黑色方块随机落下,红色方块可以在三列自 ...

  6. 输入年月,输出月份有几天(分别用了if——else和switch)

    首先是switch做的 class Program { static void Main(string[] args) {/* 题目要求:请用户输入年份,输入月份,输出该月的天数. 思路:一年中月份的 ...

  7. zabbix_agent安装(Centos+Ubuntu)

      Centos安装 安装依赖包    yum -y install mysql-devel libcurl-devel net-snmp-devel 添加用户 groupadd zabbix use ...

  8. formData_html5_map标签

    1 : //更省事 var files = fileInput.files; var formData = new FormData(); //将所有文件插入formData formData .ap ...

  9. 关于obj和基本类通过函数参数传进去执行是否改变原来的值

    var obj = { p1 : 1, p2 : 2 }; (function(_/* 这个东东是地址的应用哦 */){ _.p1 = 3, _.p2 = 4 })(obj) var i = 2; ( ...

  10. Html-Css-a标签的使用

    a标签去掉下划线 a{ text-decoration:none; } 或者把这个属性分别加到a标签下, a:link{ text-decoration:none; } a:visited{ text ...