JSP(java server page)
JSP是一种语法规范,在html模板中嵌入jsp语法,其实现处理的方式是把jsp文件转换为servlet类。
当请求到达时,服务器若判别为jsp请求就会转交给jsp引擎处理,它会找到jsp文件并根据语法解析为servlet类文件,然后编译并加载该servlet(实际上jsp引擎会检查相应servlet是否已加载且加载时间是否在jsp最后更改时间之后,只有该条件不成立才会重新生成servlet并重新编译加载,这样减少生成编译和加载servlet的过程以提高访问效率)。可见jsp语法其实就是怎么将jsp文件转换为servlet的语法(jsp转换为servlet只是实现jsp规范的一种可行方案)。
JSP的生命周期
JSP文件生成的servlet都实现了HttpJspPage接口(Servlet的子接口),对于tomcat而言,jsp页面生成的servlet都继承自HttpJspPage的实现类HttpJspBase。其生命周期为:根据jsp文件生成并加载servlet->jspInit->_jspService->jspDestroy,当jsp文件发生更改重新生成并加载servlet时会先销毁系统中原来的servlet,也就是会调用老的servlet的jspDestroy方法并调用新的servlet的jspInit方法。
JSP语法
空jsp文件生成的servlet主要只有一个_jspService方法,该方法分为两段,第一段把几个有用的对象(隐式对象)列了出来,因此后面的第二段可以使用这些对象;第二段为jsp文件的某些部分生成。解析jsp文件的时候就是把jsp的各个部分按顺序插入servlet源文件的相应地方以及对插入的控制。隐式对象有9个,常用的有request(HttpServletRequst)、response(HttpServletResponse)、out(JspWriter类型表示响应的输出流)、session(HttpSession)、application(ServletContext)、config(ServletConfig)、page(就是this关键字)、pageContext(PageContext类的实例,提供对JSP页面所有对象以及命名空间的访问)、exception(当某个页面为异常处理页面时,该值为异常发生页面抛出的异常)。
- 模板部分(除去以下部分的其他部分),该部分的内容会封装为out.write(“模板部分”)插入_jspService的第二部分。
- 注释部分(<%-- 注释部分 --%>),注释部分的内容直接被忽略,不会写入servlet,但<!-- html注释 -->会写入servlet,只是在客户端不会解析显示而已。
- 表达式部分(<%= 表达式部分 %>),该部分的内容会封装为out.print(表达式部分)直接插入_jspService的第二部分。
- 脚本部分(<% 脚本部分 %>),该部分的内容会直接插入_jspService的第二部分。
- 声明部分(<%! 声明部分 %>),该部分的内容可以声明或定义一个或多个变量、方法,会被直接写入servlet类中与_jspService平行部分,也就是servlet的成员变量和成员函数。
- 指令部分(<%@ directive attribute=“value” %>),用来设置整个JSP页面相关的属性,如网页的编码方式和脚本语言。其中directive可以有三个取值page、include或taglib。
- 标签部分 (<prefix:tag attribute=“attr”> 标签内容 </prefix:tag>),在_jspService的第二部分新建一个Tag对象并依次调用其生命周期方法。
- 动作部分 (<jsp:action_name attribute=“value” />),JSP动作元素在请求处理阶段起作用。
JSP指令
JSP指令用来设置整个JSP页面相关的属性,如网页的编码方式和脚本语言,格式为<%@ directive attribute=“value” %>,其中directive可以有三个取值page、include或taglib。
当指令部分的directive为taglib时,表示引入标签库,格式为<%@ taglib uri=“在web.xml里面标签库配置的uri” prefix=“当引用该标签库时使用的前缀” %>。
当directive为include时,表示将另一个文件的内容全部放在此处,在转换为servlet时引入文件内容就成了整个文件的一部分并按照jsp规则解析,格式为<%@ include file=“文件相对url地址” %>。
当directive为page时,attribute有很多取值来表示不同的指令:
- import:导入要使用的Java类,多个用逗号分隔或者从新使用个该标签,如<%@ page import=“java.util.*, java.lang.*” %>
- contentType:指定当前JSP页面的MIME类型和字符编码,如<%@ page language=“java” contentType=“text/html;charset=GBK” %>
- pageEncoding:指定当前JSP页面的字符编码,如果指定了pageEncoding,就以它为主,如果不存在,再找contentType的charset,如<%@ page language=“java” contentType=“text/html” pageEncoding=“GBK” %>
- isELIgnored:是否启用el表达式,默认false,即为启用,如<%@ page isELIgnored=“true”%>
- errorPage:指定当JSP页面发生异常时需要转向的错误处理页面,如<%@ page errorPage=“error.jsp”%>
- isErrorPage:指定当前页面是否可以作为另一个JSP页面的错误处理页面,<%@ page isErrorPage=“true”%>
- language:指定脚本语言,默认为java,因为我们现在只使用java,所以该值不管。
JSP动作
JSP动作的格式为<jsp:action_name attribute=“value” />,常用的action_name如下:
- include:在_jspService插入一条类似于RequestDispatcher的include语句,格式为<jsp:include page=“相对URL地址” flush=“true” />
- forward:在_jspService插入一条类似于RequestDispatcher的forward语句,格式为<jsp:forward page=“相对URL地址” />
- userBean:在_jspService中声明一个变量,并从作用域中获取相应的对象赋值给变量,如果在作用于中不存在相应的对象就新建对象并放入相应的作用域,格式为<jsp:userBean id=“变量名” scope=“page(默认)/request/session/application” class=“具有无参构造函数的带包类”/>或<jsp:userBean id=“变量名” scope="" class="">这部分jsp内容会在userBean新建的时候执行,如用jsp:setProperty/来进行新建对象的初始化</jsp:userBean>
- setProperty:设置bean的属性,调用属性的set方法,会依次从page、request、session、和application作用域寻找bean,格式为<jsp:setProperty name=“变量名”, property=“属性名” value=“属性值”/>
- getProperty:out.write(bean的属性),调用bean属性的get方法,会依次从page、request、session、和application作用域寻找bean,格式为<jsp:getProperty name=“变量名” property=“属性名” />
JSP表达式
EL表达式中可以有对象、常量、标签库中注册的函数以及运算符。当JSP解析器见到
格
式
后
,
J
S
P
解
析
器
会
产
生
代
码
来
计
算
这
个
表
达
式
,
并
用
表
达
式
的
值
来
代
替
表
达
式
的
位
置
,
如
{}格式后,JSP解析器会产生代码来计算这个表达式,并用表达式的值来代替表达式的位置,如
格式后,JSP解析器会产生代码来计算这个表达式,并用表达式的值来代替表达式的位置,如{fn:contains(object.attr, sub)}。
EL支持的运算符有:
- 基本运算:+、-、*、/(div)、%(mod) 加、减、乘、除(除就是除,不是取整)、取模,只适合数字或者由数字组成的字符串
- 逻辑运算:==(eq)、!=(ne)、<(lt)、>(gt)、<=(le)、>=(ge)、&&(and)、||(or)、!(not)
- . 、[] 访问一个Bean属性或者一个映射条目,如bean.attr、bean[“attr”]、map.key、map.[“key”],若映射条目有特殊字符只能用[],数组或者链表只能用[],如list[1].attr
- ( ) 组织一个子表达式以改变优先级
- ? : 条件运算的三目运算符
- empty 测试是否空值,当为null、空字符串或空集合时返回true
EL支持的隐藏对象有:pageScope、requestScope、sessionScope、applicationScope、param、paramValues、header、headerValues、initParam、cookie、pageContext。当对象引用不是以隐藏对象开头的,那么会依次从四大作用域中去找相应的对象。
JSP标签与函数
标签能过通过pageContext获取很多对象,所以常用来进行过程处理,而函数只能拿到输入参数并得到处理后的返回值。
函数的定义很简单,其所在的类不需要实现和继承任何类,但要求函数必须是public static修饰的公有静态函数,函数的调用见EL表达式。
JSP标签对应着一个实现了JspTag的标签处理类,在JSP引擎解析到JSP标签时,会在_jspService的第二部分创建标签对应的处理类,并依次执行其生命周期方法。定义JSP标签的过程分为两步,首先要实现标签处理类,其实现方式有两种,分别为传统方式和简单方式,然后需要将处理类进行注册,注册到标签库文件中。使用标签时需要先在web.xml中用标签下的标签引入标签库,然后需要在需要使用标签的jsp页面用<%@ taglib/>指令引入标签库。
JSP标签库在一个xml格式的tld后缀文件中进行标签与函数的注册,其格式如下:
<?xml version="1.0" encoding="utf-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<!-- 标签库对应的名字,不常用 -->
<display-name>name</display-name>
<!-- 标签库的描述,不常用 -->
<description>description</description>
<!-- 指定标签库版本 -->
<tlib-version>1.0</tlib-version>
<!-- 在jsp中建议使用的前缀,只是建议 -->
<short-name>myshortname</short-name>
<!-- 指定引用该标签的uri地址,如果标签库指定了该值,那么在jsp页面可以直接用该值引入标签库,而无需在web.xml中引入该标签库,当然也可以在web.xml中引入该标签库并指定一个新的uri,在jsp页面依然可以通过新的uri引入标签库 -->
<uri>http://mycompany.com</uri>
<!-- 各个标签的注册,可以有多个 -->
<tag>
<!-- tag对应的名字,不常用 -->
<display-name>name</display-name>
<!-- tag的描述,不常用 -->
<description>description</description>
<!-- 标签的名字,供调用 -->
<name>tagName<name>
<!-- 标签处理类 -->
<tag-class>com.ejie.MyTag</tag-class>
<!-- 对标签体的定义,当为empty时,其内容就算不为空也会被当做空,当为JSP时会当中JSP解析出结果,当为scriptless时,可以为EL表达式和JSP动作元素不能为JSP脚本如<% %>与<%= %>等,当为tagdependent时,不会对内容做任何解析 -->
<body-content>empty/JSP(简单标签不支持该值)/tagdependent/scriptless</body-content>
<!-- 未声明的属性是否允许使用,默认false,不可以与attribute共存 -->
<dynamic-attributes>true/false</dynamic-attributes>
<!-- 定义标签可以有的属性,可以有多个 -->
<attribute>
<!-- 属性名 -->
<name>att</name>
<!-- s属性的描述,不常用 -->
<description>description</description>
<!-- 是否必须赋值的属性,默认false -->
<required>true/false</required>
<!-- 属性是否支持运行时表达式,如JSP表达式(<%=value%>)和EL表达式(${value}) -->
<rtexprvalue>true/false</rtexprvalue>
<!-- 参数类型 -->
<type>java.lang.String</type>
</<attribute>
</tag>
<function>
<!-- 函数对应的名字,不常用 -->
<display-name>name</display-name>
<!-- 函数的描述,不常用 -->
<description>description</description>
<!-- 函数对应的名字,供调用 -->
<name>funName</name>
<!-- 函数的定义所在类 -->
<function-class>org.apache.taglibs.standard.functions.Functions</function-class>
<!-- 函数的签名 -->
<function-signature>java.lang.String substringBefore(java.lang.String, java.lang.String)</function-signature>
</function>
</taglib>
JSP标签处理器的传统定义方式都是实现了JspTag的子接口Tag,他的生命周期方法依次为 setPageContext(PageContext pageContext)->setParent(Tag tag)->doStartTag()->doEndTag()->release()。release方法用于释放资源,在容器关闭时才会调用。PageContext对象可以用来获取几大隐藏对象。当读到标签时调用doStartTag方法,返回值为SKIP_BODY(不会将标签的内容写入response的输出流)与EVAL_BODY_INCLUDE(将标签内容写入response的输出流),当读到结束标签时调用doEndTag方法,其返回值可以取EVAL_PAGE(继续处理标签后面的页面)与SKIP_PAGE(该标签后面的jsp页面不再处理),一般继承有Tag默认实现方法的基类BodyTagSupport。
JSP标签处理器的简单定义方式都是实现了JspTag的子接口SimpleTag,它是jsp2.0之后引入的,除了部分老的框架在使用传统定义方式外,都建议使用简单定义方式。其生命周期方法依次为setJspContext(JspContext jspContext)->setJspBody(JspFragment jspFragment)->setParent(JspTag jspTag)->doTag()。为了定义简单标签的方便,一般继承有SimpleTag默认实现方法的基类SimpleTagSupport,几个set方法都用javabean的方式设置了成员变量并提供了get方法,doTag方法默认为空,一般继承自SimpleTagSupport的标签处理器只需要实现doTag方法,负责解析标签就可以了。只有该标签在另一个自定义标签里面的时候才会有父标签,否则父标签为null。
在标签处理类中定义与属性名相同的成员变量,并定义其set方法,在doTag中就可以使用这些成员变量来获取标签的属性值。获取标签内容的方式为JspFragment(通过getJspBody获取)的invoke(Writer writer)方法,当writer传null的时候,默认为写入response的输出流,如果希望自己处理,可以定义一个StringWriter获取标签内容,通过其toString方法获取标签内容。如果希望该标签之后的jsp页面不再处理,只需要抛出SkipPageException异常。
JSTL
JSP标准标签库(JSTL:Java Standard Tag Library)是一个JSP标签集合,它封装了JSP应用的通用核心功能,使用时需要引入相应的standard包和jstl包。主要的标签库有核心标签库、格式化标签、SQL标签与XML标签以及JSTL函数。
标准库里最常用的就是核心包,引用方式<%@ taglib prefix=“c” uri=“http://java.sun.com/jsp/jstl/core” %>,其标签如下:
- if,条件判断,如同代码中的if,格式为<c:if test=“条件判断” var=“保存判断的结果” scope=“结果保留的作用域”>…</c:if>示例如下:
<c:if test="${booleanA}" var="result" scope="page">
<p>booleanA = true<p>
</c:if>
<c:if test="${result}">
<p>booleanA = false<p>
</c:if>
- choose、when与otherwise,当某个when成立的时候,后面的when和otherwise都无用了,如同代码中的swatch、case与default。
<c:choose>
<c:when test="${booleanA}">
<p>A</p>
</c:when>
<c:when test="${booleanB}">
<p>B</p>
</c:when>
<c:otherwise>
<p>都不是</p>
</c:otherwise>
</c:choose>
- forEach,循环,格式为<c:forEach items=“被遍历的集合对象,默认为一个由begin-end的连续整数集合” begin=“遍历的起始位置,默认为0” end=“遍历的结束位置,默认为最后一个元素下标” step=“遍历的步长,默认1” var=“保存遍历出来的当前对象” varStatus=“可以通过该对象获取如当前的索引index、第几次进入循环count”>…</c:foreach>。
<c:forEach items="list" var="item" begin="6" end="15" step="2" varStatus="status">
<p>${item}</p>
<p>${status.count}</p>
<p>${status.index}</p>
</c:forEach>
- forTokens,循环,与forEach相似,只是items为用符号分割的元素,因此比forEach多一个属性delims来指定分割符号。
<c:forEach items="item1,item2,item3" var="item" begin="6" end="15" step="2" varStatus="status" delims=",">
<p>${item}</p>
<p>${status.count}</p>
<p>${status.index}</p>
</c:forEach>
</c:forEach>
- redirect,重定向,如<c:redirect url=“url”/>
- out,在jsp中显示数据,如<c:out value=“输出” default=“当value通过EL表达式取值为null时的默认值,该属性的默认值为该标签的内容” escapeXml=“true/false”/>
- set,设置变量值或某个变量的属性值,格式为<c:set var=“变量名” value=“变量值” scope=""/>或<c:set target=“对象名” property=“属性名” value=“变量值值” scope=""/>
- catch,异常捕获<c:catch var =“catchException”>可能发生异常的部分</c:catch>,当异常发生时异常被捕获catchException
标准库里还常用的就是函数,引用方式<%@ taglib prefix=“fn” uri=“http://java.sun.com/jsp/jstl/functions”%>,其标签如下:
- trim(),移除首尾的空白符,public static String trim(String input)
- length(),返回字符串长度,public static int length(Object obj)
- replace(),将输入字符串中指定的位置替换为指定的字符串然后返回,public static String replace(String input, String substringBefore, String substringAfter)
- contains(),测试输入的字符串是否包含指定的子串,public static boolean contains(String input, String substring)
- containsIgnoreCase(),测试输入的字符串是否包含指定的子串,大小写不敏感,public static boolean containsIgnoreCase(String input, String substring)
- substring(),返回字符串的子集,public static String substring(String input, int beginIndex, int endIndex)
- substringBefore(),返回字符串在指定子串之前的子集,public static String substringBefore(String input, String substring)
- substringAfter(),返回字符串在指定子串之后的子集,public static String substringAfter(String input, String substring)
- indexOf(),返回指定字符串在输入字符串中出现的位置,public static int indexOf(String input, String substring)
- startsWith(),测试输入字符串是否以指定的前缀开始,public static boolean startsWith(String input, String substring)
- endsWith(),测试输入的字符串是否以指定的后缀结尾,public static boolean endsWith(String input, String substring)
- join(),将数组中的元素合成一个字符串然后输出,public static String join(String[] array, String separator)
- split(),将字符串用指定的分隔符分隔然后组成一个子字符串数组并返回,public static String[] split(String input, String delimiters)
- toLowerCase(),将字符串中的字符转为小写,public static String toLowerCase(String input)
- toUpperCase(),将字符串中的字符转为大写,public static String toUpperCase(String input)
JSP + Servlet
用JSP作为显示层,Servlet作为控制层,请求来到Servlet,然后Servlet将处理好的数据放在请求的attribute中,JSP从请求的attribute中获取数据显示。