One Page速查是将一类技术的全部知识点用一个网页进行归纳整理,便于开发的时候对忘记的知识点进行速查。它起到提示作用,内容涉及知识点罗列,简单示例,原理介绍。但不涉及知识的详细解说。
JSP语法
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类的。
Standard Directives
标准指令的语法为<%@ 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” }
ContentType与pageEncoding
在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"
Standard Actions
以下表格是JSP的标准行为。
!表示required
=表示此属性为rtexprvalue接受expression
Scriptlets
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=""%>中声明要引入的类。
Expression Language
在MVC的时代,EL+Custom Actions几乎取代了java scriptlets。在JSP中,再也看不到scriptlets的身影了。无论是struts,jstl,EL都大大的规范了JSP的用途,jsp已经变成了纯粹的View层,代码逻辑运算都转到了Controle和Model层。而JSP中简单的逻辑运算都是为展现所服务的。这样的JSP页面非常干净,便于维护。
EL提供了一些隐藏变量。比如${pageContext.request.contextPath}。
注:EL中的function可以通过custom actions来扩展。
下面列表是EL中的隐藏变量:
EL可以出现在两种地方:
Template content (isELIgnored=false)
Actions' attributes (isELIgnored=false, attribute=rtexprvalue)
JSP Document and Javascript Object
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 actions
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的声明。
常用的第三方actions
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
Actions当中的几个疑点
1, <c:set var=”v“ value="">生成的变量是否可以在JSP的引入文件,或者tag文件中被访问?
<%@ 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。
2, JSP主页面如何和其中的actions进行传值?
在tag中声明:
向内传值,<%@ attribute ...%> JSP可以向tag传值
向外传值,<%@ variable scope="" ... %> tag可以向JSP传值, variable产生的变量会被同步到父页面的pageContext中去。
3, <my:tag>body</my:tag>中,body和tag文件的pageContext是不一样的
body和tag文件会被编译成两个不同的class类,body形成的类嵌套在父页面生成的类中,它们共享一个pageContext。而tag生成的类,有自己的pageContext。
在action的类中,操作body的执行:
java 类中,jspFragment=getJspBody(); jspFragment.invoke()
tag文件中,<jsp:doBody/>
4, <%@ variable%>的scope表示的意义:
AT_BEGIN variable在doBody之前要写会父页面的pageContext中,在tag结束的时候,再写一次。
AT_END variable在tag执行结束的时候,写一次。
NESTED 在doBody之前,首先保存父页面的变量状态,然后将variable写回父页面,在执行完doBody之后,tag结束的时候,将variable从父页面抹去,并回复之前父页面的变量状态。可见这个scope是为doBody服务的。把tag中的变量作用域限制在tag执行和doBody之中。
5, Variable属性name-from-attribute的意义:
用于给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();
}
}