One Page速查是将一类技术的全部知识点用一个网页进行归纳整理,便于开发的时候对忘记的知识点进行速查。它起到提示作用,内容涉及知识点罗列,简单示例,原理介绍。但不涉及知识的详细解说。
JSP中存在以下元素,构成了JSP的语法:
Standard directives 标准指令
Standard actions 标准行动
Scripting elements 脚本元素
Expression language 解释语言
Custom Actions 扩展行为(可扩展tag,也可扩展EL函数)
Template content 静态内容
然而,除了JSP的标准语法之外,为了还有更加简洁的页面数据访问和逻辑运算工具存在,比如JSTL,Struts Tags和OGNL.
JSP也需要在web.xml中进行配置。配置的内容包括后缀,编码格式,taglib等等。
JSP必须运行于JSP容器中。JSP生命被分为两个阶段——Translation phase和Request phase. 一个JSP在被request前,必须先进行编译,转为jspServlet,这个过程为translation phase. 当用户的请求发送给jsp的时候,请求实际是由jsp编译好的servlet进行处理的。
jspServlet中存在这么几个方法_jspInit(), _jspService(), _jspDestroy(). 跟servlet非常像。
Tag文件在编译极端,则被转为SimpleTagSupport,它存在_jspInit(), _jspDestroy(), doTag().
在最后再来探讨JSP如何生成JAVA类的。
标准指令的语法为<%@ directives {attr="value"}* %>
JSP和Tag文件的标准指令有以下几种,其中Available的值表示此指令是否可以用于tag文件:
其中最复杂的是page指令。page指令用于生命本页面的属性,控制本页面的功能。下面列出了page所包含的所有可用属性:
page_directive_attr_list ::= { language=”scriptingLanguage”} { extends=”className” } { import=”importList” } { session=”true|false” } { buffer=”none|sizekb” } { autoFlush=”true|false” } { isThreadSafe=”true|false” } { info=”info_text” } { errorPage=”error_url” } { isErrorPage=”true|false” } { contentType=”ctinfo” } { pageEncoding=”peinfo” } { isELIgnored=”true|false” } { deferredSyntaxAllowedAsLiteral=”true|false”} { trimDirectiveWhitespaces=”true|false”}
指令taglib比较简单,定义本页面所要使用的tag,语法如下:
<%@ taglib ( uri=”tagLibraryURI” | tagdir=”tagDir” ) prefix=”tagPrefix” %>
指令include和<jsp:include>很像,区别在于前者先静态导入文件的编码,然后再提交给jsp容器编译,此发生在jsp translation phase;而后者则向jsp容器请求引入jsp片段的request后的内容,此发生在jsp request phase.
<%@ include file=”relativeURLspec” %> <jsp:include page="" />
指令tag类似于page指令,tag指令只存在于tag文件中。其所有属性均为Optional.
tag_directive_attr_list ::= { display-name=”display-name” } { body-content=”scriptless|tagdependent|empty” } { dynamic-attributes=”name” } { small-icon=”small-icon” } { large-icon=”large-icon” } { description=”description” } { example=”example” } { language=”scriptingLanguage” } { import=”importList” } { pageEncoding=”peinfo” } { isELIgnored=”true|false” } { deferredSyntaxAllowedAsLiteral=”true|false”} { trimDirectiveWhitespaces=”true|false”}
指令attribute是tag文件中的指令,用于定义此tag可以接受什么类型的参数:
attribute_directive_attr_list ::= name=”attribute-name” { required=”true|false” } { fragment=”true|false” } { rtexprvalue=”true|false” } { type=”type” } { description=”description” } { deferredValue=”true|false” } { deferredValueType=”type” } { deferredMethod=”true|false” } { deferredMethodSignature=”signature” } Example: <%@ attribute name=”y” type=”java.lang.Integer” %>
指令variable是tag在执行的时候向taghandler所传递的参数:
variable_directive_attr_list ::= ( name-given=”output-name” | ( name-from-attribute=”attr-name” alias=”local-name” ) ) { variable-class=”output-type” } { declare=”true|false” } { scope=”AT_BEGIN|AT_END|NESTED” } { description=”description” }
在page指令中,存在两个属性:
contentType="text/html;charset=utf-8";
pageEncoding="GBK"
从上面可以看出,pageEncoding与contentType中的charset都生命了字符集名称,为什么要声明两遍,并且声明的字符集却不同呢?解释如下:
pageEncoding是在translation phase使用的。它声明的是此jsp文件是以什么形式的编码存储的。jsp容器读入jsp文件,编译为class文件,则以utf-16编码格式来存储。contentType的charset是在request phase使用的。当request到来,jsp容易将jspServlet产生的html内容,以charset的编码形式发送给客户。
<%@ page contentType="text/html;charset=utf-8" pageEncoding="GBK" %>这个例子说明,我的jsp文件是GBK编码,jsp编译器应该使用GBK编码形式读取我的jsp文件。当用户请求到来以后,jsp容易必须把jsp产生的内容以utf-8的编码格式发给用户。
以"http://www.test.com?nihao=你好"为例,当用户第一次把URL输入到浏览器地址栏,并点击发送请求以后,浏览器并不知道该网站的contentType,所以并不知道应该用什么编码来转换“你好”两个字。这时候,浏览器会使用浏览器设置的默认编码格式对“你好”两个字转换。
中文操作系统下浏览器的默认语言编码是GBK,“你好”的GBK编码为“C4 E3 BA C3”,所以,URL被转义为:
http://www.test.com?nihao=%C4%E3%BA%C3
页面请求返回以后,返回的http header中存在contentType="text/html;charset=utf-8". 在此页面上,存在一个<a href="http://www.test.com?nihao=你好"/>,若用户点击这个link,由于浏览器已经知道此网页的编码格式为utf-8,"你好"的UTF-8编码为“e4bda0e5a5bd”,所以URL将被转义为:
http://www.test.com?nihao=%E4%BD%A0%E5%A5%BD"
以下表格是JSP的标准行为。
!表示required
=表示此属性为rtexprvalue接受expression
JSP中的java脚本包含4种:
声明 <%! int a=1, b=2, sum;%>
脚本 <% sum=a+b; %>
输出 <%=sum%>
注释 <%--comments --%>
注意<!-- -->与<%-- --%>的区别。前者中的内容会输出给用户,只是在HTML渲染的时候不会显示,后者在jsp编译的时候,直接变成java代码的注释。
在脚本中,有一些隐藏的变量可以直接使用,例如 <%=request.getContextPath()%>. 所有的隐藏变量如下表:
exception变量时在指令page中的isErrorPage="true"的时候,才会存在。
如果脚本中将使用其他类,则必须在<%@ page import=""%>中声明要引入的类。
在MVC的时代,EL+Custom Actions几乎取代了java scriptlets。在JSP中,再也看不到scriptlets的身影了。无论是struts,jstl,EL都大大的规范了JSP的用途,jsp已经变成了纯粹的View层,代码逻辑运算都转到了Controle和Model层。而JSP中简单的逻辑运算都是为展现所服务的。这样的JSP页面非常干净,便于维护。
EL提供了一些隐藏变量。比如${pageContext.request.contextPath}。
EL可以出现在两种地方:
Template content (isELIgnored=false)
Actions' attributes (isELIgnored=false, attribute=rtexprvalue)
Web service和web 2.0的兴起,是的通过HTTP传输的XML越来越多。JAXP可以产生XML,JSP也可以动态产生XML。因为JSP的所见即所得编辑模式,加上JSP中各种好用EL,actions,使得JSP更加容易生成XML。
JSP编译器判断此JSP页面是不是XML Document的方法在于:
web.xml中的<jsp-property-group>中声明了<is-xml>为true。或者
在web.xml 2.4版本中,后缀为jspx的jsp页面。或者
为了兼容jsp1.2,jsp起始位<jsp:root>的jsp页面。
出了XML,现在JSON也比较流行,有时候会使用JSP来产生JSON实例。除此外,JSP可以用于伪装各种javascript的返回值。
<%@ page contentType="text/javascript;charset=utf-8" %> true&&{a:1, b:2}
<%@ page contentType="text/javascript;charset=utf-8" %> true&&function(){}
以上两个例子一个返回json,一个返回一个function。为什么要在前面加“true&&"呢?因为eval的原因。AJAX在拿到返回内容以后,会调用eval(responseText)来执行其中含有的javascript。请做个实验,eval方法中直接传function或者json的话,是不会成功的,因为它们不是一个具有返回值得表达式。所以,将它们伪装成一个&&表达式: eval(true&&a)永远返回a。
custom action存在两种写法:
Java Class (tag handler implements Tag, IterationTag, or BodyTag, extends TagSupport, BodyTagSupport, or SimpleTagSupport).
TLD
<%@ taglib uri="" prefix="" %>
*.tag (将被编译成java tag handler, extends SimpleTagSupport).
<%@ taglib tagdir="" prefix="" %>
每当用到custom action的时候,都需要TLD生成的validator来验证语法的正确性。所以在jsp中,必须要能建立action和tld的联系。
TLD的放置位置可以为:
J2EE container自带提供的。(隐式加载)
/WEB-INF/lib/*.jar/META-INF 或者其子目录。(隐式加载)
/WEB-INF/ 或者其子目录。(lib, classes, tags除外)(显示加载)
TLD与uri的建立mapping关系:
J2EE container自带提供的,优先级最高。使用TLD中的uri和本身建立mapping。
web.xml中声明uri和tld位置的对应关系,加载并建立mapping。在web.xml中声明的uri可以为一个别名,不一定要和TLD中的uri相同。
扫描WEB-INF下面和*.jar/META-INF下面的TLD,使用TLD中的uri和本身建立mapping。
Action的prefix与TLD建立联系:
基于上一步创建的uri mapping关系,在jsp中声明<%@ taglib uri="http://my.test/core" prefix="c"%>
或者,撇弃uri mapping关系,直接使用<%@ taglib uri=”/WEB-INF/tlds/core.tld” prefix=”c” %>
编写tag文件,则不需要这么复杂,它不需要java,不需要tld。编写好的tag文件可以放在
WEB-INF/tags或者其子目录中
也可以放在*.jar/META-INF/tags下面,但这需要相应的tld进行uri mapping的声明。
JSTL, 安装JSTL的支持jar包。TLD应该已经在jar包中,如果没有则导入WEB-INF然后在web.xml中声明。
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
Struts2
Spring
<%@ include file="a.jspf" %> 所引入的文件是可以的,因为a.jspf在父页面编译之前,已经被copy到了父页面中。
<jsp:include page="a.jsp"/>不可以,因为a.jsp是由容器单独编译,虽然父页面的jspContext传给了a.jsp,但是a.jsp的jspContext=wrapper(jspContext). 他们不绝对相等,而是做了一层装饰。这使得c:set产生的变量紧紧局限于当前jsp产生的类中。
<prefix:tag>文件中也不可以,原因同上。
<prefix:tag> ${v}</prefix:tag>中的body可以,因为body在编译过程中,会产生一个当前jsp页面的嵌套类,它们共享一个jspContext。
在tag中声明:
向内传值,<%@ attribute ...%> JSP可以向tag传值
向外传值,<%@ variable scope="" ... %> tag可以向JSP传值, variable产生的变量会被同步到父页面的pageContext中去。
body和tag文件会被编译成两个不同的class类,body形成的类嵌套在父页面生成的类中,它们共享一个pageContext。而tag生成的类,有自己的pageContext。
在action的类中,操作body的执行:
java 类中,jspFragment=getJspBody(); jspFragment.invoke()
tag文件中,<jsp:doBody/>
AT_BEGIN variable在doBody之前要写会父页面的pageContext中,在tag结束的时候,再写一次。
AT_END variable在tag执行结束的时候,写一次。
NESTED 在doBody之前,首先保存父页面的变量状态,然后将variable写回父页面,在执行完doBody之后,tag结束的时候,将variable从父页面抹去,并回复之前父页面的变量状态。可见这个scope是为doBody服务的。把tag中的变量作用域限制在tag执行和doBody之中。
用于给variable起名字。它的名字是从父页面通过attribute传递进来的。增加了action的通用性,不必把变量的名字写死。例如:
parent.jsp <my:tag attribute="i"> variable i = ${i} </my:tag> my.tag <%@ attribute name="varName" required="true" rtexprvalue="false" %> <%@ variable name-from-attribute="varName" variable-class="java.lang.String" scope="NESTED" alias="j"%> <c:set var="j" value="hello"/> <jsp:doBody/>
这里并不讲如何用container提供的工具,提前把jsp编译成class.这里讲一个jsp被编译成java代码,是什么样子的。任何一个web container都可以设置参数,是否保留jsp编译以后的java文件。这样可以去debug你的jsp。
接下来讲的是jsp与java代码的转换。这里使用的都是伪代码:
High level来看一个jsp或tag总共会产生2个大类,而大类中会有嵌套类。
<html> <body> <my:tag>tagBody</my:tag> <my:tag>tagBody2</my:tag> </body> </html>
以上jsp会产生以下类:
parent.jsp public final class parent_jsp extends HttpJspBase { private javax.el.ExpressionFactory _el_expressionfactory; public void _jspInit() { _el_expressionfactory = ...; } public void _jspDestroy() { } public void _jspService(request, response) { pageContext; session = null; application; config; out = null; page = this; _jspx_out = null; _jspx_page_context = null; try { handle jsp content } } private class Helper extends JspFragmentHelper { Helper(int i, pageContext){} invoke0(){ handler tagBody}; invoke1(){ handler tagBody1}; } } my.tag public final class webmy_tag extends SimpleTagSupport { declare attributes, set body, attributes, context _jspInit(){} _jspDestroy(){} doTag(){} }
由上面的伪代码可以看出,jsp被编译成一个jsp的类,tag文件被编译成simpletagsupport类,而jsp中tag的body部分被封装在嵌套类Helper中的invoke方法中。用伪代码来表示他们之间的关系:
JSP.jspService=
{
TAG.setJspBody(new Helper(0, pageContext));
TAG.setJspContext(pageContext);
TAG.setXXX(xxx);
TAG.doTag();
};
TAG.doTag=
{
out.write()...
this.getJspBody().invoke(out);
out.write()...
}
以上为最粗略解释。接下来,我将列出jspService,invoke, doTag中所有的jsp与java代码的对照。以下面的jsp为例:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="my.Hello"%> <%@ taglib tagdir="/WEB-INF/tags" prefix="my"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head><title>test</title></head> <body> <p>Scriptlet context Path <%=request.getContextPath() %></p> <p>EL context path ${pageContext.request.contextPath}</p> <jsp:useBean id="hello" class="my.Hello"/> <jsp:setProperty name="hello" property="text" value="Hello World"/> <p><my:my text="${hello.text }"> invoke successfully. </my:my> </p> <p><jsp:include page="a.jsp" /></p> </body> </html>
在jspService中,代码如下:
response.setContentType("text/html; charset=UTF-8"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write("\r\n"); out.write("\r\n"); out.write("\r\n"); out.write("\r\n"); out.write("<html>\r\n"); out.write("<head><title>test</title></head>\r\n"); out.write("<body>\r\n"); out.write(" <p>Scriptlet context Path "); out.print(request.getContextPath() ); out.write("</p>\r\n"); out.write(" <p>EL context path "); out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${pageContext.request.contextPath}", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, null, false)); out.write("</p>\r\n"); out.write(" "); my.Hello hello = null; hello = (my.Hello) _jspx_page_context.getAttribute("hello", javax.servlet.jsp.PageContext.PAGE_SCOPE); if (hello == null){ hello = new my.Hello(); _jspx_page_context.setAttribute("hello", hello, javax.servlet.jsp.PageContext.PAGE_SCOPE); } out.write("\r\n"); out.write(" "); org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper(_jspx_page_context.findAttribute("hello"), "text", "Hello World", null, null, false); out.write("\r\n"); out.write(" <p>"); if (_jspx_meth_my_005fmy_005f0(_jspx_page_context)) return; out.write("\r\n"); out.write(" </p>\r\n"); out.write(" <p>"); org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "a.jsp", out, false); out.write("</p>\r\n"); out.write("</body>\r\n"); out.write("</html>\r\n"); private boolean _jspx_meth_my_005fmy_005f0(javax.servlet.jsp.PageContext _jspx_page_context) throws java.lang.Throwable { javax.servlet.jsp.PageContext pageContext = _jspx_page_context; javax.servlet.jsp.JspWriter out = _jspx_page_context.getOut(); // my:my org.apache.jsp.tag.webmy_tag _jspx_th_my_005fmy_005f0 = (new org.apache.jsp.tag.webmy_tag()); _jsp_instancemanager.newInstance(_jspx_th_my_005fmy_005f0); _jspx_th_my_005fmy_005f0.setJspContext(_jspx_page_context); // /index.jsp(12,5) name = text type = java.lang.String reqTime = true required = true fragment = false deferredValue = false expectedTypeName = java.lang.String deferredMethod = false methodSignature = null _jspx_th_my_005fmy_005f0.setText((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${hello.text }", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, null, false)); _jspx_th_my_005fmy_005f0.setJspBody(new Helper( 0, _jspx_page_context, _jspx_th_my_005fmy_005f0, null)); _jspx_th_my_005fmy_005f0.doTag(); _jsp_instancemanager.destroyInstance(_jspx_th_my_005fmy_005f0); return false; }
由于调用action的时候,声明了body,body将会在Helper的invoke方法中产生java代码:
public boolean invoke0( javax.servlet.jsp.JspWriter out ) throws java.lang.Throwable { out.write("\r\n"); out.write(" invoke successfully.\r\n"); out.write(" "); return false; }
my.tag的代码如下:
/* <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ attribute name="text" rtexprvalue="true" required="true" %> ==Saying: "${text}". ==<jsp:doBody/> */ public void doTag() throws javax.servlet.jsp.JspException, java.io.IOException { javax.servlet.jsp.PageContext _jspx_page_context = (javax.servlet.jsp.PageContext)jspContext; javax.servlet.http.HttpServletRequest request = (javax.servlet.http.HttpServletRequest) _jspx_page_context.getRequest(); javax.servlet.http.HttpServletResponse response = (javax.servlet.http.HttpServletResponse) _jspx_page_context.getResponse(); javax.servlet.http.HttpSession session = _jspx_page_context.getSession(); javax.servlet.ServletContext application = _jspx_page_context.getServletContext(); javax.servlet.ServletConfig config = _jspx_page_context.getServletConfig(); javax.servlet.jsp.JspWriter out = jspContext.getOut(); _jspInit(config); jspContext.getELContext().putContext(javax.servlet.jsp.JspContext.class,jspContext); if( getText() != null ) _jspx_page_context.setAttribute("text", getText()); try { out.write("\r\n"); out.write("\r\n"); out.write("==Saying: \""); out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${text}", java.lang.String.class, (javax.servlet.jsp.PageContext)this.getJspContext(), null, false)); out.write("\".\r\n"); out.write("=="); ((org.apache.jasper.runtime.JspContextWrapper) this.jspContext).syncBeforeInvoke(); _jspx_sout = null; if (getJspBody() != null) getJspBody().invoke(_jspx_sout); jspContext.getELContext().putContext(javax.servlet.jsp.JspContext.class,getJspContext()); out.write('\r'); out.write('\n'); } catch( java.lang.Throwable t ) { if( t instanceof javax.servlet.jsp.SkipPageException ) throw (javax.servlet.jsp.SkipPageException) t; if( t instanceof java.io.IOException ) throw (java.io.IOException) t; if( t instanceof java.lang.IllegalStateException ) throw (java.lang.IllegalStateException) t; if( t instanceof javax.servlet.jsp.JspException ) throw (javax.servlet.jsp.JspException) t; throw new javax.servlet.jsp.JspException(t); } finally { jspContext.getELContext().putContext(javax.servlet.jsp.JspContext.class,super.getJspContext()); ((org.apache.jasper.runtime.JspContextWrapper) jspContext).syncEndTagFile(); } }