jsp标签
一、简介
- jsp标签库是使用XML语法格式完成程序操作的一种方法,其使用的形式类似于javaBean的使用语法jsp:userBean,主要目的就是为了减少页面的Scriptlet代码,使程序更加便于理解和修改。
二、空标签
- 要实现一个自定义标签,可以直接继承javax.servlet.jsp.tagext.TagSupport类,重写doStartTag()方法的作用是在标签起始时进行调用。
- 一个标签类定义完成后,下面就要编写标签描述文件了,在.tld文件中可以描述标签名称、简介、处理类、标签使用属性等。
- 在jsp页面中映射该标签
<%@ taglib prefix="标签前缀" uri="tld文件路径"%>
,并且调用<标签前缀 jsp标签使用名称>
。
package taeyeon.com.jsp.tld;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;
public class HelloTag extends TagSupport {
@Override
public int doStartTag() throws JspException {
JspWriter writer = super.pageContext.getOut(); // 取得jsp的输出流对象
try {
writer.println("<h2>Hello World!</h2>");
} catch (IOException e) {
e.printStackTrace();
}
return super.SKIP_BODY;//没有标签体
}
}
<?xml version="1.0" encoding="ISO-8859-1"?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<tlib-version>1.0</tlib-version><!--标签库版本-->
<short-name>hello</short-name><!--标签库在tld中的描述名称-->
<uri>http://mycompany.com</uri><!--jsp页面中taglib标签中的uri映射路径,可自己定义。只要满足书写标准-->
<tag>
<name>hello</name><!--在jsp中使用的名称-->
<tag-class>taeyeon.com.jsp.tld.HelloTag</tag-class><!--标签指向的操作类-->
<body-content>empty</body-content><!--是否有标签体-->
</tag>
</taglib>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="hello" uri="/WEB-INF/tld/hello.tld" %>
<html>
<head>
<title>第一个tld标签页面</title>
</head>
<body>
<hello:hello/>
</body>
</html>
输出
Hello World!
- 有时候uri太长,后期不好维护,我们就可以在web.xml文件中映射该uri,给该uri取虚拟名,以后在jsp页面中引用时就可以直接书写该虚拟名,来访问该标签描述文件。
<!--映射tag的uri,操作tld文件-->
<jsp-config>
<taglib>
<taglib-uri>hello_uri</taglib-uri>
<taglib-location>/WEB-INF/tld/hello.tld</taglib-location>
</taglib>
</jsp-config>
注意:这里在web.xml文件中配置的 <jsp-config>,在web.xml2.4版本之前是可以书写的,但是在之后书写会报错,因为tld文件中新增了一个标签<uri>可以直接映射jsp页面中的引用uri,所以只要在tld文件中书写就可以了。具体在tld文本的那一个版本修改的有兴趣的可以自己查阅一下。
- 当tld文件第一次运行之后会被加载到jvm中,第二次调用的时候就不需要重复加载,可直接使用。
三、定义有属性的标签
- 例如:<jsp:forward page="">语句中的page就是一个属性,在具体的标签中我们也要常用到属性。下面我们举例一个日期格式化标签。
- 创建格式化日期标签类
package taeyeon.com.jsp.tld;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateTag extends TagSupport {
private String formateDate;
public String getFormateDate() {
return formateDate;
}
public void setFormateDate(String formateDate) {
this.formateDate = formateDate;
}
@Override
public int doStartTag() throws JspException {
LocalDateTime date = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(this.formateDate);
try {
super.pageContext.getOut().write(date.format(formatter));
} catch (IOException e) {
e.printStackTrace();
}
return TagSupport.SKIP_BODY;
}
}
- 书写tld文件
<?xml version="1.0" encoding="ISO-8859-1"?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>myshortname</short-name>
<uri>dateuri</uri>
<tag>
<name>date</name>
<tag-class>taeyeon.com.jsp.tld.DateTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>formateDate</name><!--属性名称-->
<required>true</required><!--是否为必输项-->
<rtexprvalue>true</rtexprvalue><!--是否支持表达式输出-->
</attribute>
</tag>
</taglib>
-引用tld
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="date" uri="dateuri" %>
<html>
<head>
<title>带属性的标签体</title>
</head>
<body>
<h2><date:date formateDate="yyyy-MM-dd HH:mm:ss"/></h2>
</body>
</html>
- 输出
2019-12-02 17:03:28
注意:这里没有用SimpleDateFormat类来格式化时期,实用为SimpleDateFormat类时非线程安全的,而在jdk1.8之后提供了新的DateTimeFormatter类,该类线程安全也是作用于日期的格式化,两者的具体不同和使用,怎样让SimpleDateFormat变的线程安全可以参考我后面博文写的文章
- rtexprvalue标签值为true时,则支持表达式输出
<名称: tld中name名称 属性="${}"/属性="<%= %>"/>
四、TagSupport类
- 基本的标签掌握之后,可以发现标签的实现都需要继承TagSupport这个类,所以TagSupport类是整个标签编程的核心类:
public class TagSupport extends Object implements IterationTag,Serializable
- TagSupport类同时实现了IterationTag,Serializable两个接口,IterationTag接口定义如下:
public interface IterationTag extends Tag {
public final static int EVAL_BODY_AGAIN = 2;
int doAfterBody() throws JspException;
- Tag接口定义如下:
public interface Tag extends JspTag {
public final static int SKIP_BODY = 0;
public final static int EVAL_BODY_INCLUDE = 1;
public final static int SKIP_PAGE = 5;
public final static int EVAL_PAGE = 6;
void setPageContext(PageContext pc);
void setParent(Tag t);
Tag getParent();
int doStartTag() throws JspException;
int doEndTag() throws JspException;
void release();
}
- TagSupport类中定义的常量和方法
- doStartTag():此方法在标签开始时执行,有如下两种返回值。
- SKIP-BODY:表示忽略标签体的内容,而将执行权交给doEndTag()方法。
- EVAL_BODY_INCLUDE:表示执行标签体内容
- doAfterBody():此方法是子接口Iteration与父接口Tag的差别所在,用来重复执行标签体内容,返回值有:
- SKIP_BODY:忽略标签体内容,而将执行权交给doEndTag()方法。
- EVAL_BODY_AGAIN:重复调用doAfterBody()方法,一支到返回值为SKIP_BODY为止。
- doEndTag():标签体结束时执行,有人如下返回值:
- SKIP_PAGE:直接终止jsp页面执行,将所有输出值立刻回传到浏览器上。
- EVAL_PAGE:表示jsp可以正常的运行结束。
- release():表示标签处理类所占用的资源全部被释放,等待下一次的调用。
- 下图为接口Iteration的执行图:
五、有标签体的标签库
- 存在标签体则就表明该标签之间时存在内容的。下面我们就举例来看一看:
- 标签处理类
package taeyeon.com.jsp.tld;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
public class BodyTag extends TagSupport {
private String name;
private String scope;
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int doStartTag() throws JspException {
Object value=null;
if("page".equals(this.scope)){
value=super.pageContext.getAttribute(name , PageContext.PAGE_SCOPE);
}else if("request".equals(this.scope)){
value=super.pageContext.getAttribute(name , PageContext.REQUEST_SCOPE);
}else if("session".equals(this.scope)){
value=super.pageContext.getAttribute(name , PageContext.SESSION_SCOPE);
}else if("application".equals(this.scope)){
value=super.pageContext.getAttribute(name , PageContext.APPLICATION_SCOPE);
}
if(value==null){
return super.SKIP_BODY;
}
else {
return super.EVAL_BODY_INCLUDE;
}
}
}
- 书写tld文件
<?xml version="1.0" encoding="ISO-8859-1"?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>myshortname</short-name>
<uri>bodytag</uri>
<tag>
<name>body</name>
<tag-class>taeyeon.com.jsp.tld.BodyTag</tag-class>
<body-content>JSP</body-content><!-- 执行标签体内容-->
<attribute>
<name>scope</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
- jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="body" uri="bodytag" %>
<html>
<head>
<title>有标签体的标签</title>
</head>
<body>
<%! String scope = "session";%>
<%
session.setAttribute("name", "Yoona");
%>
<body:body name="name" scope="<%=scope%>">
<h2>session属性范围</h2>
</body:body>
</body>
</html>
- 页面输出
session属性范围
注意:
- 当我们书写的带属性字段的标签时,我们的tld文件里的attribute标签里的name标签要和标签处理类里的属性字段名一样,不然前端会报500错误。
- 当我们把scope换成标签处理类中不存在的分支return常数SKIP_BODY,那么这里就不会执行标签体了,页面显示为空,对于该例子来说
六、开发迭代标签
- 在程序中开发迭代输出是常见的一种输出形式,jsp的一个主要功能就是输出,为了避免大量的scriptlet代码的出现,使用迭代标签是很有价值的。
- 开发迭代标签处理类
package taeyeon.com.jsp.tld;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
import java.util.Iterator;
import java.util.List;
public class IterationTag extends TagSupport {
private String name;
private String scope;
private Iterator<?> iter;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public Iterator<?> getIter() {
return iter;
}
public void setIter(Iterator<?> iter) {
this.iter = iter;
}
@Override
public int doStartTag() throws JspException {
Object value = null;
if ("page".equals(this.scope)) {
value = super.pageContext.getAttribute(name, PageContext.PAGE_SCOPE);
} else if ("request".equals(this.scope)) {
value = super.pageContext.getAttribute(name, PageContext.REQUEST_SCOPE);
} else if ("session".equals(this.scope)) {
value = super.pageContext.getAttribute(name, PageContext.SESSION_SCOPE);
} else if ("application".equals(this.scope)) {
value = super.pageContext.getAttribute(name, PageContext.APPLICATION_SCOPE);
}
if (value != null && value instanceof List<?>) {
this.iter = ((List<?>) value).iterator();
if (iter.hasNext()) {
super.pageContext.setAttribute("msg", iter.next());
return super.EVAL_BODY_INCLUDE;
} else {
return super.SKIP_BODY;
}
} else {
return super.SKIP_BODY;
}
}
@Override
public int doAfterBody() throws JspException {
if (iter.hasNext()) {
super.pageContext.setAttribute("msg", iter.next());
return super.EVAL_BODY_AGAIN;
} else {
return super.SKIP_BODY;
}
}
}
- 书写tld文件
<?xml version="1.0" encoding="ISO-8859-1"?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>myshortname</short-name>
<uri>iteratortag</uri>
<tag>
<name>iterator</name>
<tag-class>taeyeon.com.jsp.tld.IterationTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
- jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="iterator" uri="iteratortag" %>
<html>
<head>
<title>迭代标签库</title>
</head>
<body>
<%
String name1 = "session";
List<String> list = new ArrayList<String>();
list.add("18");
list.add("yoona");
list.add("Korea");
session.setAttribute("name", list);
%>
<iterator:iterator name="name" scope="<%=name1%>">
<h2>${msg}</h2>
</iterator:iterator>
</body>
</html>
- 页面输出
18
yoona
Korea
在获取属性值的时候一定要注意访问域的问题,不然有可能获取不到值。
七、BodyTagSupport类
- BodyTagSupport是TagSupport类的子类,通过继承BodyTagSupport类实现的标签可以直接处理标签内容的数据,定义如下:
public class BodyTagSupport extends TagSupport implements BodyTag
- BodyTag接口的定义如下:
public interface BodyTag extends IterationTag {
public final static int EVAL_BODY_TAG = 2;
public final static int EVAL_BODY_BUFFERED = 2;
void setBodyContent(BodyContent b);
void doInitBody() throws JspException;
}
- BodyTagSupport的扩充方法和常量
- 在BodyTagSupport类中定义一个bodyContent的受保护的属性,而bodyContent时BodyContent类的对象,如下:
public abstract class BodyContent extends JspWriter
- BodyContent是JspWriter类的子类,可以直接打印和输出基本类型与对象值,但是BodyContent类与JspWriter类的区别在于,BodyContent类的任何写入内容并不自动像页面输出,如下:
- BodyTag接口的执行流程图如下:从图中可以看出来,当返回值为EVAL_BODY_BUFFERED时则会将所有的处理内容都保存道BodyContent类中,并且返回执行doAfterBody()方法;而如果doStartTag()方法返回的是EVAL_BODY_INCLUDE,则就不会保存到BodyContent类中。