解讀JSP的解析過程


解讀JSP解析過程

 

 

互聯網上,這方面的資料實在太少了,故把自己研究的一些結果公布出來。

 

首先,問大家幾個問題,看大家能不能回答出來,或者在網上能不能找到答案:

 

 

1pageincludetaglib這三個編譯指令,執行的順序是什么?

2JSP文件中的Java代碼、編譯指令、動作指令、EL標簽、第三方標簽、靜態文本等,被編譯的順序是什么?

3、常用的、與JSP解析/編譯相關的類有哪些?換句話說,JSP解析、編譯技術是建立在哪些接口和工具之上的?

4JSP技術所有的編譯指令和動作指令有哪些?

5JSP技術是由誰發起的,現在有哪幾個標准?都有哪些服務器或者項目支持JSP的解析和編譯?

 

 

先回答簡單、基礎性的問題:

 

回答問題5

JSP的發起者為——Sun Microsystems, Inc.

JSP是由Java Servlets發展而來。

JSPServlet一樣,都是在Java Community ProcessJava社區組織)等眾多人參與下,共同開發出來的,並且最終制定了一些規范和標准。Java體系中的規范和技術,會被制定成JSR規范。JSPServlet也屬於這個規范之中,例如JSR-53規定了JSP 1.2Servlet 2.4的規范,JSR-152規定了JSP 2.0的規范。(詳見http://zh.wikipedia.org/wiki/JSR

JSP常用的版本有1.22.02.1(最新版本),其中1.2版本最大的進步在於優化了JSTLJavaServer Pages Standard Tag Library已經過時了(因為它不支持EL表達式2.01.2的基礎上有了巨大的進步,因為它引入了Expression LanguageEL表達式)、簡化了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,更能夠適應J2EEJ2SEServlet 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人自己都可以寫一套出來,最常見的WebLogicTomcatWebSphereResin(據說效率比Tomcat等要高,網易等大網站都在用)。

 

 

回答問題3

第一個想到的就是Java Servlet API,即servlet-api.jar ,然后JSP的解析有一個工具包,即jasper-compiler.jar,因為JSP是一個標准,那么在解析工具包之上,應該有一個規范的API包,即jsp-api.jarJSTLJSP規范的一部分,故還有jstl-api.jar,另外,還有expression language擴展包,el-api.jar。還注意到一點,JSP是基於XML的,故也涉及到XML的解析(用的是SAX)。

補充一點,可以手動調用程序來解析(也叫預編譯)JSPTomcatWebSphere下,是調用JspC這個工具類:

Tomcat下,jsp是通過org.apache.jasper.JspC編譯工具將JSP 頁面的預編譯,在WAS下,是通過 com.ibm.websphere.ant.tasks.JspC進行預編譯。

 

 

回答問題41

 

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就是編譯指令,總結起來,有:pageincludetaglibtagattributevariable,關於他們的執行順序,看解析程序是最有說服力的,程序如下:

摘自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-- 解析每個節點

 

1步,是從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的字符串完整地賦值給Nodetext屬性,然后把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解析、編譯的研究,暫時就告一段落,有興趣的可以和我交流。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM