目录
1. 自定义标签
2. 开发标签库
自定义标签:旨在移除JSP页面中的Java代码。
1. 自定义标签
优点:
1. 减少JSP页面对JSP脚本的需求和依赖。
2. 将JSP页面和业务逻辑分离(增强可维护性、可重用性)。
使用步骤:
1. 创建自定义标签类(实现相关接口、类)。
2. 创建.tld标签库描述文件(.tld文件通常位于WEB-INF目录下)。
用来将类映射成JSP标签。采用XML文件格式进行描述,后缀名为.tld。
1. 标签(配置 标签库的信息) 只能有一个
常用子标签
1. tlib-version
标签库的版本号。
2. jsp-version
标签库使用的JSP版本号。
3. short-name
标签库的前缀。
4. uri
标签库的uri地址。
5. 标签(可以有多个)
每个tag标签代表一个自定义标签。
2. 标签(配置 自定义标签的信息)
常用子标签:
1. name(必填)
自定义标签的名称。
2. tag-class(必填)
自定义标签的实现类路径。
3. description
自定义标签的功能描述。
4. attribute
自定义标签的属性(可以有多个)。
5. body-content
1. empty
没有标签体
2. JSP
有标签体(可以是任意内容)不符合避免出现Java代码的初衷。
3. tagdependent
标签体的内容由标签类自己处理(很少使用)
如:SELECT * FROM USER 中的SQL语句就是给标签类使用的。
4. scriptless
标签体的内容不允许是JSP脚本。
【存疑】在简单标签中标签体body-content的值只允许是empty和scriptless、tagdependent,不允许设置成JSP否则异常。
【存疑】在传统标签中标签体body-content的值4个类型都可以。
6. variable
自定义标签的变量属性。
3. 标签(用来定义标签中的属性)注意子标签顺序
常用子标签:
1. name(必填)
属性名称。
2. description
属性描述。
3. required
属性是否是必须的(默认值:false)
4. rtexprvalue
属性值是否支持JSP表达式
5. type
属性的 Java 类型(默认值:String)
6. fragment
如果声明了该属性,属性值将被视为一个JspFragment。
4. 标签(用来定义标签中的变量属性)
常用子标签:
1. declare
变量声明
2. description
变量描述
3. name-from-attribute
指定的属性名称,其值为变量,在调用JSP页面时可以使用的名字
4. name-given
变量名(标签使用时的变量名)
5. scope
变量的作用范围,有3个值:
1. NESTED 开始和结束标签之间
2. AT_BEGIN 从开始标签到页面结束
3. AT_END 从结束标签之后到页面结束
6. variable-class
变量的Java类型(默认值:String)
3. 在jsp中使用自定义标签。
1. 引用标签库(多个uri相同时,使用td文件路径来区分)
<%@taglib uri="/MyCusTag" prefix="hello" %>
<%@taglib uri="/WEB-INF/MyCusTag.tld" prefix="hi"%>
2. 使用标签
或
标签体
- 相关接口
JspTag接口(所有自定义标签的父接口,接口中没有任何属性和方法) 有2个直接子接口:
传统标签需要使用三个标签接口(Tag、IterationTag、BodyTag)来完成不同的功能,繁琐且不利于推广,所以sun公司推出了SimpleTag接口。
1. Tag接口(实现Tag接口的自定义标签称为传统标签)
接口中定义了如下方法:
1. doStartTag()
返回常量EVAL_BODY_INCLUDE则执行标签体。
返回常量SKIP_BODY则跳过(忽略)标签体,直接执行结束标签。
2. doEndTag()
返回常量EVAL_PAGE则会执行结束标签后的jsp代码。
返回常量SKIP_PAGE则忽略结束标签后的所有内容。
3. setParent()、getParent()、setPageContext()、release()
有1个直接子接口:IterationTag接口(继承自Tag接口,实现功能:重复执行标签体)
接口中定义了doAfterBody()方法(在执行完自定义标签的标签体后会调用该方法)
返回常量EVAL_BODY_AGAIN则重复执行标签体内容。
返回常量SKIP_BODY则去执行doEndTag()。
IterationTag接口的默认实现类:TagSupport(用于简化开发)。
有1个直接子接口:BodyTag接口(继承自IterationTag接口,实现功能:修改标签体内容)
接口中定义了setBodyContent()、doInitBody()方法。
在doStartTag方法还可以返回常量EVAL_BODY_BUFFERED,WEB容器就会创建一个BodyContent对象(用于捕获标签体运行结果),然后调用setBodyContent方法将BodyContent对象的引用传递给标签处理器,WEB容器接着将标签体的执行结果写入到BodyContent对象中。在doEndTag方法中可以通过先前保存的BodyContent对象的引用来获取标签体的执行结果,然后调用BodyContent对象特有的方法对BodyContent对象中的内容(即标签体的执行结果)进行修改和控制其输出。
BodyTag接口的默认实现类:BodyTagSupport(用于简化开发)。
2. SimpleTag接口(实现SimpleTag接口的自定义标签称为简单标签)
接口中定义了如下方法:
1. doTag()
是否执行标签体、迭代标签体、对标签体内容进行修改等功能都可以在doTag方法中完成。
2. getParent()、setParent()、setJspContext()、setJspBody()。
SimpleTag接口的默认实现类:SimpleTagSupport(用于简化开发)。
- 自定义传统标签
JSP引擎遇到自定义标签时的【执行流程】:
首先创建标签类的实例对象,然后依次调用该对象的如下方法:
1. public void setPageContext(PageContext pc)
将JSP页面的pageContext对象传递给标签类,标签类通过pageContext对象和JSP页面通信。
2. public void setParent(Tag t)
将当前标签的父标签传递给标签类,如果没有则为null。
3. public int doStartTag()
处理标签的开始标记。
4. public int doEndTag()
执行完标签体后,会处理标签的结束标记。
5. public void release()
执行完自定义标签后,标签类的实例对象会驻留在内存中直至web应用关闭时会调用release方法进行释放。
自定义标签除了能移除JSP页面中的Java代码外,也可以在JSP页面中实现以下功能:
1. 控制jsp页面的某一部分内容是否执行。
实现Tag接口 或 继承TagSupport类,然后覆写doStartTag() 返回不同常量。
2. 控制整个jsp页面是否执行。
实现Tag接口 或 继承TagSupport类,然后覆写doEndTag() 返回不同常量。
3. 控制jsp页面内容重复执行。
实现IterationTag接口 或 继承TagSupport类,然后覆写doStartTag()返回Tag.EVAL_BODY_INCLUDE、doEndTag()返回不同常量
4. 修改jsp页面内容输出
实现BodyTag接口 或 继承BodyTagSupport类,然后覆写doStartTag()返回BodyTag.EVAL_BODY_BUFFERED、doEndTag()如下:
public int doEndTag() throws JspException {
// this.getBodyContent()得到代表标签体的bodyContent对象
BodyContent bodyContent = this.getBodyContent();
// 拿到标签体内容
String content = bodyContent.getString();
// 修改标签体里面的内容,将标签体的内容转换成大写
String result = content.toUpperCase();
try {
// 输出修改后的内容
this.pageContext.getOut().write(result);
} catch (IOException e) {
throw new RuntimeException(e);
}
return Tag.EVAL_PAGE;
}
示例(获取客户端IP)
===》第1步. 创建自定义标签类(用于获取客户端IP)
package com.sst.cx;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;
public class CusTag implements Tag{
private PageContext pageContext;
@Override
public int doEndTag() throws JspException {
System.out.println("调用doEndTag()方法");
return 0;
}
@Override
public int doStartTag() throws JspException {
System.out.println("调用doStartTag()方法");
// 可以看到pageContext对象在自定义标签中的重要性。
HttpServletRequest request =(HttpServletRequest) pageContext.getRequest();
JspWriter out = pageContext.getOut();
String ip = request.getRemoteAddr();
try {
// 这里输出的时候会抛出IOException异常
out.write(ip);
} catch (IOException e) {
// 捕获IOException异常后继续抛出
throw new RuntimeException(e);
}
return 0;
}
@Override
public Tag getParent() {
return null;
}
@Override
public void release() {
System.out.println("调用release()方法");
}
@Override
public void setPageContext(PageContext pageContext) {
System.out.println("setPageContext(PageContext pageContext)");
this.pageContext = pageContext;
}
@Override
public void setParent(Tag arg0) {
}
}
===》第2步. 创建MyCusTag.tld文件(在WEB-INF目录下)
自定义标签库
1.0
MyCusTag
/MyCusTag
这个标签的作用是用来输出客户端的IP地址
viewIP
com.sst.cx.CusTag
empty
===》第3步. 在jsp中使用
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<%@taglib uri="/MyCusTag" prefix="sx" %>
My JSP 'index.jsp' starting page
不使用 时,需要使用JSP脚本来实现获取客户端IP:
<%
//
String ip = request.getRemoteAddr();
out.write(ip);
%>
可以看到,自定义标签可以将JSP中的Java代码移除。
- 自定义简单标签
自定义简单标签的执行流程
1. setJspContext()
将JSP页面的pageContext对象传递给标签类对象。
2. setParent()
将当前标签的父标签类对象传递给标签类,只有当父标签存在时才会调用。
使用getParent()可获取父标签类对象。
3. 如果标签设置了属性则分别调用setter方法设值。
4. 如果有标签体则调用setJspBody() 方法,并传入代表标签体的JspFragment对象。
5. 执行标签时会调用doTag() ,通过操作JspFragment对象,就可以实现 是否执行、迭代、修改标签体内容。
手动抛出javax.servlet.jsp.SkipPageException异常,则不再执行标签后的所有页面内容。
JspFragment对象
代表标签体,JSP页面中的一段符合JSP语法规范的JSP片段(不能包含JSP脚本元素)
只有2个方法:
1. getJspContext方法
获取JSP页面的pageContext对象。
2. public abstract void invoke(java.io.Writer out)
执行标签体,并将标签体的结果内容写入参数指定的流中(可以将流中的标签体内容进行修改后再输出)。可以传StringWriter输出流对象,也可以传null(会使用JspContext.getOut()方法返回的输出流对象)。
不调用invoke方法则不会执行标签体(即不会输出标签体内容,即忽略标签体内容)。
多次调用invoke方法则会多次执行标签体(即多次输出标签体内容)。
示例
===》第1步. 创建自定义标签类
package com.sst.cx;
import java.io.IOException;
import java.io.StringWriter;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.SkipPageException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
// 不推荐直接实现SimpleTag接口这种方式,推荐使用继承SimpleTagSupport类的方式来自定义简单标签。
public class SimpleTag extends SimpleTagSupport {
// 一般只需重写doTag方法
@Override
public void doTag() throws JspException, IOException {
// 得到代表jsp标签体的JspFragment对象
JspFragment jspFragment = this.getJspBody();
// 得到jsp页面的的PageContext对象
// PageContext pageContext = (PageContext) jspFragment.getJspContext();
// 调用JspWriter将标签体的内容输出到浏览器
// jspFragment.invoke(pageContext.getOut());
/*
1. 控制jsp页面某一部分内容是否执行(控制标签体是否执行)。
调用则会执行标签体,注释掉则不再执行标签体。
jspFragment.invoke(null);
*/
/*
2. 控制jsp页面内容重复执行。
重复调用jspFragment.invoke(null);方法
for (int i = 0; i < 5; i++) {
// 将标签体的内容输出到浏览器
jspFragment.invoke(null);
}
*/
/*
3. 修改jsp页面内容输出
StringWriter sw = new StringWriter();
// 将标签体的内容写入到sw流中
jspFragment.invoke(sw);
// 获取sw流缓冲区的内容
String content = sw.getBuffer().toString();
content = content.toUpperCase();
PageContext pageContext = (PageContext) this.getJspContext();
// 将修改后的content输出到浏览器中
pageContext.getOut().write(content);
*/
/*
4. 控制整个jsp页面是否执行
抛出异常则不再执行标签后的所有内容。
throw new SkipPageException();
*/
}
}
===》第2步. tld中+(.tld文件通常放在WEB-INF下)
SimpleTag(简单标签)
simple
com.sst.cx.SimpleTag
scriptless
===》第3步. 在jsp中使用
引入
<%@taglib uri="/MyCusTag" prefix="hello" %>
使用
nihao
- 开发带有属性标签
自定义标签中可以定义多个属性,在JSP页面中使用标签时设置属性值,通过这些属性可以给标签处理类传递参数信息,从而提高标签的灵活性和复用性。
使用步骤:
1. 在标签处理类中编写每个属性及对应的setter方法(必须符合JavaBean格式)。
2. 在TLD文件中描述标签的属性。
说明:
1. 传统标签会在执行doStartTag方法前调用setter方法设值,简单标签会在setParent()之后setJspBody()方法之前调用setter方法设值。
2. 如果标签的属性值是8种基本数据类型,JSP引擎会自动将字符串转换成相应的类型。但如果标签的属性是复合数据类型则无法自动转换,可使用JSP表达式或EL表达式来解决(如:Date类型
示例
===》第1步. 在标签类中+
private int count; // 重复执行的次数
public void setCount(int count) {
this.count = count;
}
在doTag()方法中+
for (int i = 0; i < this.count; i++) {
// 将标签体的内容输出到浏览器
jspFragment.invoke(null);
}
===》第2步. 在tld文件的tag中+(一个属性对应一个)
描述(可选)
count
true
true
int
===》第3步. 在jsp中使用(会输出3遍nihao)
nihao
2. 开发标签库
- 防盗链标签(防止用户直接输入网址的请求)
第1步. 创建标签类(在com.sst.cx.simpletag包下新建)
package com.sst.cx.simpletag;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.SkipPageException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class RefererTag extends SimpleTagSupport {
// 网站域名
private String site;
// 请求来路不明时 要跳转的页面
private String page;
@Override
public void doTag() throws JspException, IOException {
// 获取jsp页面的PageContext对象
PageContext pageContext = (PageContext) this.getJspContext();
// 通过PageContext对象来获取HttpServletRequest对象
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
// 获取请求的来路(Referer)
String referer = request.getHeader("referer");
// 如果来路是null或者来路不是来自我们自己的site,那么就将请求重定向到page页面
if (referer == null || !referer.startsWith(site)) {
// 获取HttpServletResponse对象
HttpServletResponse response = (HttpServletResponse)pageContext.getResponse();
String webRoot = request.getContextPath();
if (page.startsWith(webRoot)) {
// 重定向到page页面
response.sendRedirect(page);
} else {
// 重定向到page页面
response.sendRedirect(webRoot+page);
}
// 重定向后,控制保护的页面不要执行
throw new SkipPageException();
}
}
public void setSite(String site) {
this.site = site;
}
public void setPage(String page) {
this.page = page;
}
}
第2步. 创建CusTagLib.tld(在WEB-INF目录下新建)
简单标签库
1.0
TagLib
/CusTagLib
referer
com.sst.cx.simpletag.RefererTag
empty
描述标签的site属性
site
true
true
描述标签的page属性
page
true
true
第3步. 在test.jsp文件中测试(访问则跳主页)
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/WEB-INF/CusTagLib.tld" prefix="hello"%>
<%--
在Jsp页面中使用防盗链标签
当用户尝试直接通过URL地址(http://localhost:8080/TestWeb2/simpletag/refererTagTest.jsp)访问这个页面时,防盗链标签的标签处理器内部就会进行处理,将请求重定向到/index.jsp。
--%>
防盗链标签测试
网站
- if标签
第1步. 创建标签类(com.sst.cx.simpletag包下新建)
package com.sst.cx.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class IFTag extends SimpleTagSupport {
/**
* if标签的test属性
*/
private boolean test;
@Override
public void doTag() throws JspException, IOException {
if (test) {
this.getJspBody().invoke(null);
}
}
public void setTest(boolean test) {
this.test = test;
}
}
第2步. CusTagLib.tld中+
if标签
if
com.sst.cx.simpletag.IFTag
scriptless
if标签的test属性
test
true
true
第3步. 在test.jsp中测试
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/WEB-INF/CusTagLib.tld" prefix="hello"%>
<%--if标签的test属性值为true ,标签体的内容会输出--%>
网站
<%--if标签的test属性值为false ,标签体的内容不会输出--%>
不输出
- when otherwise标签
when标签和otherwise标签对应两个不同的标签处理器类。
希望当when标签执行后otherwise标签就不再执行,这时涉及到相互通讯的问题,可以让两个标签类拥有同一个父标签类来共享同一个变量来解决通信问题。
第1步-1. 创建标签类-父(com.sst.cx.simpletag包下新建)
package com.sst.cx.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class ChooseTag extends SimpleTagSupport {
/**
* 定义一个boolean类型的属性,该属性用于标识该标签下的某一个子标签是否已经执行过了,如果该标签下的某一个子标签已经执行过了,就将该属性设置为true。
*/
private boolean isExecute;
@Override
public void doTag() throws JspException, IOException {
// 输出标签体中的内容
this.getJspBody().invoke(null);
}
public boolean isExecute() {
return isExecute;
}
public void setExecute(boolean isExecute) {
this.isExecute = isExecute;
}
}
第1步-2. 创建标签类-when
package com.sst.cx.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class WhenTag extends SimpleTagSupport {
/**
* test属性,该属性值为true时,输出标签体中的内容
*/
private boolean test;
@Override
public void doTag() throws JspException, IOException {
// 获取标签的父标签
ChooseTag parentTag = (ChooseTag) this.getParent();
if (test == true && parentTag.isExecute() == false) {
// 输出标签体中的内容
this.getJspBody().invoke(null);
// 将父标签的isExecute属性设置为true,告诉父标签,我(when标签)已经执行过了
parentTag.setExecute(true);
}
}
public void setTest(boolean test) {
this.test = test;
}
}
第1步-3. 标签处理类-otherWise
package com.sst.cx.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class OtherWiseTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
// 获取标签的父标签
ChooseTag parentTag = (ChooseTag) this.getParent();
// 如果父标签下的when标签没有执行过
if (parentTag.isExecute() == false) {
// 输出标签体中的内容
this.getJspBody().invoke(null);
// 设置父标签的isExecute属性为true,告诉父标签,我(otherwise标签)已经执行过了
parentTag.setExecute(true);
}
}
}
第2步. CusTagLib.tld中+
choose标签
choose
com.sst.cx.simpletag.ChooseTag
scriptless
when标签
when
com.sst.cx.simpletag.WhenTag
scriptless
when标签的test属性
test
true
true
otherwise标签
otherwise
com.sst.cx.simpletag.OtherWiseTag
scriptless
第3步. 在test.jsp中测试
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/WEB-INF/CusTagLib.tld" prefix="hello"%>
when标签标签体输出的内容:
用户为空
用户不为空