[关闭]
@cxm-2016 2016-12-28T16:33:25.000000Z 字数 12304 阅读 3000

Java Web (八)——JSP标签与自定义标签

Web

版本:1
作者:陈小默
声明:禁止商业,禁止转载


JSP标签

可用的标准动作元素如下:

语法 描述
jsp:include 在页面被请求的时候引入一个文件。
jsp:useBean 寻找或者实例化一个JavaBean。
jsp:setProperty 设置JavaBean的属性。
jsp:getProperty 输出某个JavaBean的属性。
jsp:forward 把请求转到一个新的页面。
jsp:plugin 根据浏览器类型为Java插件生成OBJECT或EMBED标记。
jsp:element 定义动态XML元素
jsp:attribute 设置动态定义的XML元素属性。
jsp:body 设置动态定义的XML元素内容。
jsp:text 在JSP页面和文档中使用写入文本的模板

常见的属性

所有的动作要素都有两个属性:id属性和scope属性。

jsp:include动作元素

<jsp:include>动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下:

  1. <jsp:include page="相对 URL 地址" flush="true" />

前面已经介绍过include指令,它是在JSP文件被转换成Servlet的时候引入文件,而这里的jsp:include动作不同,插入文件的时间是在页面被请求的时候。
以下是include动作相关的属性列表。

属性 描述
page 包含在页面中的相对URL地址。
flush 布尔属性,定义在包含资源前是否刷新缓存区。

实例
以下我们定义了两个文件 date.jspmain.jsp,代码如下所示:
date.jsp文件代码:

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <p>
  4. 今天的日期是: <%= (new java.util.Date()).toLocaleString()%>
  5. </p>

main.jsp文件代码:

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html>
  4. <html>
  5. <head>
  6. <meta charset="utf-8">
  7. <title>菜鸟教程(runoob.com)</title>
  8. </head>
  9. <body>
  10. <h2>include 动作实例</h2>
  11. <jsp:include page="date.jsp" flush="true" />
  12. </body>
  13. </html>

现在将以上两个文件放在服务器的根目录下,访问main.jsp文件。显示结果如下:

include 动作实例
今天的日期是: 2016-6-25 14:08:17

jsp:useBean动作元素

jsp:useBean 动作用来加载一个将在JSP页面中使用的JavaBean。
这个功能非常有用,因为它使得我们可以发挥 Java 组件复用的优势。
jsp:useBean动作最简单的语法为:

  1. <jsp:useBean id="name" class="package.class" />

在类载入后,我们既可以通过 jsp:setProperty 和 jsp:getProperty 动作来修改和检索bean的属性。
以下是useBean动作相关的属性列表。

属性 描述
class 指定Bean的完整包名。
type 指定将引用该对象变量的类型。
beanName 通过 java.beans.Beans 的 instantiate() 方法指定Bean的名字。

执行原理

  1. <jsp:useBean id="currentDate"
  2. class="java.util.Date"/>

上述代码在被翻译成Servlet的时候是这个样子的

  1. java.util.Date currentDate = null;
  2. synchronized (_jsp_page_context) {
  3. currentDate = (Date)_jsp_page_context.getAttribute("currentDate",PageContext.PAGE_SCOPE);
  4. if(currentDate == null){
  5. currentDate = new java.util.Date();
  6. _jsp_page_context.("currentDate", currentDate, PageContext.PAGE_SCOPE)
  7. }
  8. }

jsp:setProperty动作元素

jsp:setProperty用来设置已经实例化的Bean对象的属性,有两种用法。首先,你可以在jsp:useBean元素的外面(后面)使用jsp:setProperty,如下所示:

  1. <jsp:useBean id="myName" ... />
  2. ...
  3. <jsp:setProperty name="myName" property="someProperty" .../>

此时,不管jsp:useBean是找到了一个现有的Bean,还是新创建了一个Bean实例,jsp:setProperty都会执行。第二种用法是把jsp:setProperty放入jsp:useBean元素的内部,如下所示:

  1. <jsp:useBean id="myName" ... >
  2. ...
  3. <jsp:setProperty name="myName" property="someProperty" .../>
  4. </jsp:useBean>

此时,jsp:setProperty只有在新建Bean实例时才会执行,如果是使用现有实例则不执行jsp:setProperty。
jsp:setProperty动作有下面四个属性,如下表:

属性 描述
name name属性是必需的。它表示要设置属性的是哪个Bean。
property property属性是必需的。它表示要设置哪个属性。有一个特殊用法:如果property的值是"*",表示所有名字和Bean属性名字匹配的请求参数都将被传递给相应的属性set方法。
value value 属性是可选的。该属性用来指定Bean属性的值。字符串数据会在目标类中通过标准的valueOf方法自动转换成数字、boolean、Boolean、 byte、Byte、char、Character。例如,boolean和Boolean类型的属性值(比如"true")通过 Boolean.valueOf转换,int和Integer类型的属性值(比如"42")通过Integer.valueOf转换。value和param不能同时使用,但可以使用其中任意一个。
param param 是可选的。它指定用哪个请求参数作为Bean属性的值。如果当前请求没有参数,则什么事情也不做,系统不会把null传递给Bean属性的set方法。因此,你可以让Bean自己提供默认属性值,只有当请求参数明确指定了新值时才修改默认属性值。

jsp:getProperty动作元素

jsp:getProperty动作提取指定Bean属性的值,转换成字符串,然后输出。语法格式如下:

  1. <jsp:useBean id="myName" ... />
  2. ...
  3. <jsp:getProperty name="myName" property="someProperty" .../>

下表是与getProperty相关联的属性:

属性 描述
name 要检索的Bean属性名称。Bean必须已定义。
property 表示要提取Bean属性的值

实例
以下实例我们使用了Bean:

  1. package com.runoob.main;
  2. public class TestBean {
  3. private String message = "菜鸟教程";
  4. public String getMessage() {
  5. return(message);
  6. }
  7. public void setMessage(String message) {
  8. this.message = message;
  9. }
  10. }

编译完成后会在当前目录下生成一个 TestBean.class 文件, 将该文件拷贝至当前 JSP 项目的 WebContent/WEB-INF/classes/com/runoob/main 下( com/runoob/main 包路径,没有需要手动创建)。

下面是一个很简单的例子,它的功能是装载一个Bean,然后设置/读取它的message属性。
现在让我们在main.jsp文件中调用该Bean:

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html>
  4. <html>
  5. <head>
  6. <meta charset="utf-8">
  7. <title>菜鸟教程(runoob.com)</title>
  8. </head>
  9. <body>
  10. <h2>Jsp 使用 JavaBean 实例</h2>
  11. <jsp:useBean id="test" class="com.runoob.main.TestBean" />
  12. <jsp:setProperty name="test"
  13. property="message"
  14. value="菜鸟教程..." />
  15. <p>输出信息....</p>
  16. <jsp:getProperty name="test" property="message" />
  17. </body>
  18. </html>

jsp:forward 动作元素

 jsp:forward动作把请求转到另外的页面。jsp:forward标记只有一个属性page。语法格式如下所示:

  1. <jsp:forward page="相对 URL 地址" />

以下是forward相关联的属性:

属性 描述
page page属性包含的是一个相对URL。page的值既可以直接给出,也可以在请求的时候动态计算,可以是一个JSP页面或者一个 Java Servlet.

实例
以下实例我们使用了两个文件,分别是: date.jsp 和 main.jsp。
date.jsp 文件代码如下:

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <p>
  4. 今天的日期是: <%= (new java.util.Date()).toLocaleString()%>
  5. </p>

main.jsp文件代码:

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html>
  4. <html>
  5. <head>
  6. <meta charset="utf-8">
  7. <title>菜鸟教程(runoob.com)</title>
  8. </head>
  9. <body>
  10. <h2>forward 动作实例</h2>
  11. <jsp:forward page="date.jsp" />
  12. </body>
  13. </html>

现在将以上两个文件放在服务器的根目录下,访问main.jsp文件。显示结果如下:

今天的日期是: 2016-6-25 14:37:25

jsp:plugin动作元素

jsp:plugin动作用来根据浏览器的类型,插入通过Java插件 运行Java Applet所必需的OBJECT或EMBED元素。
如果需要的插件不存在,它会下载插件,然后执行Java组件。 Java组件可以是一个applet或一个JavaBean。
plugin动作有多个对应HTML元素的属性用于格式化Java 组件。param元素可用于向Applet 或 Bean 传递参数。
以下是使用plugin 动作元素的典型实例:

  1. <jsp:plugin type="applet" codebase="dirname" code="MyApplet.class"
  2. width="60" height="80">
  3. <jsp:param name="fontcolor" value="red" />
  4. <jsp:param name="background" value="black" />
  5. <jsp:fallback>
  6. Unable to initialize Java Plugin
  7. </jsp:fallback>
  8. </jsp:plugin>

如果你有兴趣可以尝试使用applet来测试jsp:plugin动作元素,元素是一个新元素,在组件出现故障的错误是发送给用户错误信息。

jsp:element 、 jsp:attribute、 jsp:body动作元素

<jsp:element><jsp:attribute><jsp:body>动作元素动态定义XML元素。动态是非常重要的,这就意味着XML元素在编译时是动态生成的而非静态。
以下实例动态定义了XML元素:

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html>
  4. <html>
  5. <head>
  6. <meta charset="utf-8">
  7. <title>菜鸟教程(runoob.com)</title>
  8. </head>
  9. <body>
  10. <jsp:element name="xmlElement">
  11. <jsp:attribute name="xmlElementAttr">
  12. 属性值
  13. </jsp:attribute>
  14. <jsp:body>
  15. XML 元素的主体
  16. </jsp:body>
  17. </jsp:element>
  18. </body>
  19. </html>

jsp:text动作元素

<jsp:text>动作元素允许在JSP页面和文档中使用写入文本的模板,语法格式如下:

  1. <jsp:text>模板数据</jsp:text>

以上文本模板不能包含其他元素,只能只能包含文本和EL表达式(注:EL表达式将在后续章节中介绍)。请注意,在XML文件中,您不能使用表达式如 ${whatever > 0},因为>符号是非法的。 你可以使用 ${whatever gt 0}表达式或者嵌入在一个CDATA部分的值。

  1. <jsp:text><![CDATA[<br>]]></jsp:text>

如果你需要在 XHTML 中声明 DOCTYPE,必须使用到<jsp:text>动作元素,实例如下:

  1. <jsp:text><![CDATA[<!DOCTYPE html
  2. PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  3. "DTD/xhtml1-strict.dtd">]]>
  4. </jsp:text>
  5. <head><title>jsp:text action</title></head>
  6. <body>
  7. <books><book><jsp:text>
  8. Welcome to JSP Programming
  9. </jsp:text></book></books>
  10. </body>
  11. </html>

你可以对以上实例尝试使用及不使用该动作元素执行结果的区别。

自定义标签

传统自定义标签

实现TAG接口

  1. class TraditionalTag : Tag {
  2. override fun setParent(t: Tag?) {
  3. TODO()
  4. // 如果当前标签有福标签,则此方法会传入该标签的父标签
  5. }
  6. override fun getParent(): Tag? {
  7. TODO()
  8. }
  9. override fun doStartTag(): Int {
  10. TODO()
  11. //执行到开始标签时调用的方法,返回值指定标签体是否执行
  12. // SKIP_BODY 跳过标签体
  13. // EVAL_BODY_INCLUDE 执行标签体
  14. }
  15. override fun setPageContext(pc: PageContext) {
  16. TODO()
  17. // 传入当前JSP页面的PageContext对象
  18. }
  19. override fun release() {
  20. TODO()
  21. // 表示标签资源被释放,通常在其被容器移除时(服务器shutdown时)调用。
  22. }
  23. override fun doEndTag(): Int {
  24. TODO()
  25. //执行到结束标签时调用,返回值指定标签结束后剩下的页面内容是否执行
  26. // SKIP_PAGE 不执行剩下的内容
  27. // EVAL_PAGE 执行页面剩下的内容
  28. }
  29. }

那么假如我们需要实现一个能够将字符串转换为大写的功能,比如下面这样:

  1. <t:toUpper item="hello world" var="s">
  2. ${s}
  3. </t:toUpper>

那么我们要去实现这个标签,注意:我们在标签中声明了两个属性itemvar,那么一定要在标签对象中提供属性的getter和setter方法(Kotlin直接声明可读写的属性即可)。

  1. class TraditionalTag : Tag {
  2. var item: String? = null
  3. var `var`: String? = null
  4. private var context: PageContext? = null
  5. private var parent: Tag? = null
  6. override fun setParent(parent: Tag?) {
  7. this.parent = parent
  8. }
  9. override fun getParent(): Tag? {
  10. return parent
  11. }
  12. override fun doStartTag(): Int {
  13. val upper = item!!.toUpperCase()
  14. context!!.setAttribute(`var`, upper)
  15. return Tag.EVAL_BODY_INCLUDE
  16. }
  17. override fun setPageContext(context: PageContext) {
  18. this.context = context
  19. }
  20. override fun release() {
  21. }
  22. override fun doEndTag(): Int {
  23. context!!.removeAttribute(`var`)
  24. return Tag.EVAL_PAGE
  25. }
  26. }

实现tld文件

然后创建一个tld文件

  1. <taglib xmlns="http://java.sun.com/xml/ns/javaee"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
  4. version="2.1">
  5. <tlib-version>1.0</tlib-version>
  6. <short-name>tag</short-name>
  7. <uri>http://cccxm.github.com/tag</uri>
  8. <!-- Invoke 'Generate' action to add tags or functions -->
  9. <tag>
  10. <name>toUpper</name>
  11. <tag-class>com.github.cccxm.tag.TraditionalTag</tag-class>
  12. <body-content>JSP</body-content>
  13. <attribute>
  14. <name>item</name>
  15. <type>java.lang.String</type>
  16. <required>yes</required>
  17. </attribute>
  18. <attribute>
  19. <name>var</name>
  20. <type>java.lang.String</type>
  21. <required>yes</required>
  22. </attribute>
  23. </tag>
  24. </taglib>

标签体类型

tld文件中有四种标签体(body-content)类型 :emptyJSPscriptlesstagdependent

在简单标签(SampleTag)中标签体body-content的值只允许是emptyscriptlesstagdependent,不允许设置成JSP,如果设置成JSP就会出现异常。

body-content的值如果设置成empty,那么就表示该标签没有标签体,如果是设置成scriptless,那么表示该标签是有标签体的,但是标签体中的内容不可以是<%java代码%>

在传统标签中标签体body-content的值允许是emptyJSPscriptlesstagdependentbody-content的值如果是设置成JSP,那么表示该标签是有标签体的,并且标签体的内容可以是任意的,包括java代码,如果是设置成scriptless,那么表示该标签是有标签体的,但是标签体的内容不能是java代码

引入标签资源

  1. <%@ taglib uri="http://cccxm.github.com/tag" prefix="t" %>

传统TAG标签的生命周期

在每一次被访问的使用调用setPageContext->setParent->doStartTag->doEndTag
当服务器关闭时调用release

使用最基本的TAG接口实现的话,功能太少,如果我们需要使用更多一点的功能,可以使用SimpleTag接口。

简单自定义标签

简单自定义标签的特点是每次解析过程都会创建一个处理类对象,而不是像传统TAG标签那样只在第一次被解析时创建对象之后永驻内存。当解析完成之后解析对象自动销毁。

简单标签的生命周期

当web容器开始执行标签时,会调用如下方法完成标签的初始化:

  1. WEB容器调用标签处理器对象的setJspContext方法,将代表JSP页面的pageContext对象传递给标签处理器对象。
  2. WEB容器调用标签处理器对象的setParent方法,将父标签处理器对象传递给这个标签处理器对象。注意,只有在标签存在父标签的情况下,WEB容器才会调用这个方法。
  3. 如果调用标签时设置了属性,容器将调用每个属性对应的setter方法把属性值传递给标签处理器对象。如果标签的属性值是EL表达式或脚本表达式,则WEB容器首先计算表达式的值,然后把值传递给标签处理器对象。
  4. 如果简单标签有标签体,WEB容器将调用setJspBody方法把代表标签体的JspFragment对象传递进来。
  5. 执行标签时WEB容器调用标签处理器的doTag()方法,开发人员在方法体内通过操作JspFragment对象,就可以实现是否执行、迭代、修改标签体的目的。
  1. class StringTag : SimpleTag {
  2. override fun setParent(parent: JspTag?) {
  3. TODO() //TODO
  4. }
  5. override fun getParent(): JspTag? {
  6. TODO() //TODO
  7. }
  8. override fun setJspBody(jspBody: JspFragment?) {
  9. TODO() //TODO
  10. // 获取到的标签体
  11. }
  12. override fun setJspContext(pc: JspContext?) {
  13. TODO() //TODO
  14. }
  15. override fun doTag() {
  16. TODO() //TODO
  17. //业务实现方法
  18. }
  19. }

接下来我们实现一个简单的分割字符串的标签

  1. class StringTag : SimpleTag {
  2. var item: String? = null
  3. var `var`: String? = null
  4. var regex: String? = null
  5. private var parent: JspTag? = null
  6. private var context: JspContext? = null
  7. private var body: JspFragment? = null
  8. override fun setParent(parent: JspTag) {
  9. this.parent = parent
  10. }
  11. override fun getParent() = parent
  12. override fun setJspBody(jspBody: JspFragment?) {
  13. this.body = jspBody
  14. }
  15. override fun setJspContext(context: JspContext) {
  16. this.context = context
  17. }
  18. override fun doTag() {
  19. val items = item!!.split(regex!!.toRegex())
  20. for (i in items) {
  21. context!!.setAttribute(`var`, i)
  22. body!!.invoke(null)
  23. }
  24. }
  25. }

修改标签体

如果我们需要修改标签体的话,则需要指定标签体内容的输出流

  1. val writer = StringWriter()
  2. jspBody.invoke(writer)
  3. //处理writer以修改标签体
  4. jspContext.out.write(writer.toString())

然后再配置tld文件

  1. <tag>
  2. <name>forToken</name>
  3. <tag-class>com.github.cccxm.tag.StringTag</tag-class>
  4. <body-content>scriptless</body-content>
  5. <attribute>
  6. <name>item</name>
  7. <type>java.lang.String</type>
  8. <required>yes</required>
  9. </attribute>
  10. <attribute>
  11. <name>regex</name>
  12. <type>java.lang.String</type>
  13. <required>yes</required>
  14. </attribute>
  15. <attribute>
  16. <name>var</name>
  17. <type>java.lang.String</type>
  18. <required>yes</required>
  19. </attribute>
  20. </tag>

然后我们就可以使用forEach的方式输出分割后的字符串了。

  1. <t:forToken item="a1b2c3d4e" regex="[0-9]" var="s">
  2. ${s}<br>
  3. </t:forToken>

简化的自定义标签

上面的接口比TAG接口要方便的多,可是除了doTag其他的方法我们一般都不会做额外的操作,那么为什么不把他们抽取出来呢?

于是,SimpleTagSupport类就诞生了。接下来我们使用简化了的自定义标签实现一个验证字符串的标签。

  1. class MatchTag : SimpleTagSupport() {
  2. var item: String? = null
  3. var regex: String? = null
  4. var result = false
  5. override fun doTag() {
  6. val res = regex!!.toRegex().matches(item!!)
  7. result = res
  8. jspBody.invoke(null)
  9. }
  10. }
  11. class TrueTag : SimpleTagSupport() {
  12. override fun doTag() {
  13. if (parent != null) {
  14. val result = (parent as MatchTag).result
  15. if (result) {
  16. jspBody.invoke(null)
  17. }
  18. }
  19. }
  20. }
  21. class FalseTag : SimpleTagSupport() {
  22. override fun doTag() {
  23. if (parent != null) {
  24. val result = (parent as MatchTag).result
  25. if (!result) {
  26. jspBody.invoke(null)
  27. }
  28. }
  29. }
  30. }
  1. <tag>
  2. <name>match</name>
  3. <tag-class>com.github.cccxm.tag.MatchTag</tag-class>
  4. <body-content>scriptless</body-content>
  5. <attribute>
  6. <name>item</name>
  7. <type>java.lang.String</type>
  8. <required>yes</required>
  9. </attribute>
  10. <attribute>
  11. <name>regex</name>
  12. <type>java.lang.String</type>
  13. <required>yes</required>
  14. </attribute>
  15. </tag>
  16. <tag>
  17. <name>true</name>
  18. <tag-class>com.github.cccxm.tag.TrueTag</tag-class>
  19. <body-content>scriptless</body-content>
  20. </tag>
  21. <tag>
  22. <name>false</name>
  23. <tag-class>com.github.cccxm.tag.FalseTag</tag-class>
  24. <body-content>scriptless</body-content>
  25. </tag>
  1. <t:match item="13012345678" regex="1[0-9]{10}">
  2. <t:true>是手机号</t:true>
  3. <t:false>不是手机号</t:false>
  4. </t:match>

简单标签的跳过实现

在普通的TAG中,我们通过返回值来决定是否跳过剩余页面,那么在简单标签实现时使用方式是抛出异常

  1. throw SkipPageException()
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注