一、JSP自定义标签
1. 什么是标签
标记语言,是一种注释文本的语言,以便于计算机可以操作。很多与“ML”结尾的语言都是标记语言,比如:HTML,XML,XHTML,VML等等。
标记语言与其他语言一样,也需要运行它们的环境,比如HTML的运行环境时浏览器,XML也要自己的解析和运行的环境。
2. 什么是自定义标签
自定义标签是用户定义的JSP语言元素。当包含自定义标签的JSP页面转换为servlet时,这个标签就转换为一个名为tag handler的对象上的操作。之后当JSP页面的servlet执行时,Web容器就调用这些操作。
3. 标签语言的特点
1.形式 :<开始标签 属性="属性值">标签体</结束标签>
2.分类 :
空标签 | br、hr... |
ui标签 | input、table... |
控制标签 | if、foreach... |
数据标签 | out |
二、自定义标签的开发及使用步骤
JSP自定义标签可以分为两种类型:标记库和函数库。
1. 标签库
标记库是由一组标记组成的集合,这些标记可用于在JSP页面上执行各种操作。开发人员可以根据自己的需求创建自定义标记,并将其打包到一个标记库中。这个标记库可以在多个JSP页面中共享,并且可以轻松地部署和维护。
简单来说是标记库一个JSP标签集合,它封装了JSP应用的通用核心功能, 基于JSP标签我们可以理解为,是JSP应该通用功能的一种封装方式。
2. 函数库
函数库是一组函数的集合,这些函数可以在JSP页面上调用。开发人员可以根据自己的需求创建自定义函数,并将它们打包到一个函数库中。这个函数库可以在多个JSP页面中共享,并且可以轻松地部署和维护。
创建函数库需要以下步骤:
- 创建一个名为“tld”的XML文件,并在其中定义函数库的属性,如名称、URI等。
- 在JSP页面中引用这个XML文件,以便可以使用函数库中的函数。
创建函数
创建函数需要以下步骤:
- 创建一个Java类,其中包含所需的函数。
- 在tld文件中定义函数,并指定函数的名称、类、描述等信息。
3. 自定义标签使用步骤
3.1,先建一个 .tld 文件 (必须在) 在WEB-INF目录下
<!-- mytag.tld --> <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/web-jsptaglibrary_2_0.xsd" version="2.0"> <description>My custom tag library</description> <display-name>My Tag Library</display-name> <tlib-version>1.0</tlib-version><!-- 代表标签库的版本号 --> <short-name>mtl</short-name><!-- 你的标签库简称 --> <uri>com.ycxw</uri><!-- taglib引入标签名字 --> <tag> <name>HelloTag</name><!-- 代表自定义标签的名字 --> <tag-class>com.ycxw.DemoTag</tag-class><!-- 对应的标签处理程序(助手类):包名+类名 --> <body-content>JSP</body-content><!-- 标签体内容的格式 --> <attribute> <name>test</name><!-- 自定义标签的属性名称 --> <required>true</required><!-- 该属性是否必填 --> <rtexprvalue>true</rtexprvalue><!-- 该属性值是否支持表达式 --> </attribute> </tag> </taglib>
注:tld文件就是C标签的定义配置文件
- 自定义标签是与 .tld文件相关的
- 标签中的标签与 .tld中的tag元素相关,也就是tag元素对应的助手类有关
3.2 引用标签库
<%@taglib prefix="l" uri="com.ycxw" %>
3.3 每一个标签都必须有一个对于的助手类,继承BodyTagSupport
package com.ycxw; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.BodyTagSupport; /** * * @author 云村小威 * */ public class DemoTag extends BodyTagSupport{ @Override public int doStartTag() throws JspException { System.out.println("—— doStartTag ——"); return super.doStartTag(); } @Override public int doAfterBody() throws JspException { System.out.println("—— doAfterBody ——"); return super.doAfterBody(); } @Override public int doEndTag() throws JspException { System.out.println("—— doEndTag ——"); return super.doEndTag(); } }
3.4 测试
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!-- 引入标签库 --> <%@taglib prefix="c" uri="com.ycxw" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>MyTag Text</title> </head> <body> <c:HelloTag></c:HelloTag> </body> </html>
没有标签体内容运行结果:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!-- 引入标签库 --> <%@taglib prefix="c" uri="com.ycxw" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>MyTag Text</title> </head> <body> <c:HelloTag>hello</c:HelloTag> </body> </html>
有标签体内容运行结果:
下面将解释方法运行结果原因 —— 标签的生命周期
三、标签的生命周期
1. JSP自定义标签生命周期图
返回值作用:
SKIP_BODY | 跳过主体 |
EVAL_BODY_INCLUDE | 计算主体内容并输出 |
EVAL_BODY_AGAIN | 再次计算主体一次 |
EVAL_PAGE | 计算后续内容 |
SKIP_PAGE | 跳过页面后续内容 |
2. 案例论证
根据上一案例有标签体的情况下,默认会调用助手类doStartTag、doAfterBody、doEndTag方法。
1. 如果将doStartTag方法返回值改成SKIP_BODY,则doAfterBogy方法会跳过,不会运行。
@Override public int doStartTag() throws JspException { System.out.println("—— doStartTag ——"); return SKIP_BODY; }
2. 如果将doStartTag方法返回值改成EVAL_BODY_INCLUDE,则doAfterBogy方法就会运行。
3. 如果将doAfterBody方法返回值修改成EVAL_BODY_AGAIN, 就会一直调用该方法进入死循环
4. 如果将doEndTag方法返回值修改成SKIP_PAGE, 则会跳过页面后的内容
修改前:
修改后:
四、标签的开发实例
1. if 标签
1.1 进入.tld文夹定义if标签并对自定义标签进行描述
<tag> <name>if</name> <tag-class>com.ycxw.Demo2Tag</tag-class> <body-content>JSP</body-content> <attribute> <name>text</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
1.2 编写助手类
package com.ycxw; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.BodyTagSupport; /** * if 控制标签 * * @author 云村小威 * */ public class Demo2Tag extends BodyTagSupport{ //接受自定义属性 private boolean text; public boolean isText() { return text; } public void setText(boolean text) { this.text = text; } @Override public int doStartTag() throws JspException { //如果text为true就运行doaftertage,否则就跳过 return text ? EVAL_BODY_INCLUDE : SKIP_BODY; } }
1.3 编写jsp页面进行测试
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!-- 引入标签库 --> <%@taglib prefix="w" uri="com.ycxw" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>MyTag Text</title> </head> <body> <w:if text="true">小黑宝</w:if> <w:if text="false">小黑子</w:if> </body> </html>
运行结果:
2. set和out标签
2.1 进入.tld文夹定义set和out标签并对自定义标签进行描述
<!-- settag --> <tag> <name>set</name> <tag-class>com.ycxw.SetTag</tag-class> <body-content>JSP</body-content> <attribute> <name>var</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <name>value</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> <!-- outtag --> <tag> <name>out</name> <tag-class>com.ycxw.OutTag</tag-class> <body-content>JSP</body-content> <attribute> <name>value</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
2.1 编写助手类
package com.ycxw; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.BodyTagSupport; /** * set 数据标签 * 作用:储存数据 * * @author 云村小威 * */ public class SetTag extends BodyTagSupport { // 接受自定义属性 private String var; private Object value; public String getVar() { return var; } public void setVar(String var) { this.var = var; } public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } @Override public int doStartTag() throws JspException { // 要存储数据,需通过键值对方法进行储存,这里不考虑数据库只用pageContext作用域 pageContext.setAttribute(var, value); return super.doStartTag(); } }
package com.ycxw; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.BodyTagSupport; /** * out 数据标签 * 作用:输出数据,首先要拿到输出流 * * @author 云村小威 * */ public class OutTag extends BodyTagSupport { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } @Override public int doStartTag() throws JspException { JspWriter out = pageContext.getOut(); try { out.print(value); } catch (IOException e) { e.printStackTrace(); } return super.doStartTag(); } }
2.3 编写jsp页面进行测试
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!-- 引入标签库 --> <%@taglib prefix="w" uri="com.ycxw" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>MyTag Text</title> </head> <body> <w:set var="name" value="ikun"></w:set> <w:out value="${name }"></w:out> </body> </html>
运行结果:
3. foreach 标签
3.1 进入.tld文夹定义foreach标签并对自定义标签进行描述
<!-- foreach --> <tag> <name>foreach</name> <tag-class>com.ycxw.ForeachTag</tag-class> <body-content>JSP</body-content> <attribute> <!--标识集合遍历是指针所在的位置,指向当前遍历对象 --> <name>var</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <!--标识集合遍历是指针所在的位置,指向当前遍历对象 --> <name>items</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
3.2 创建助手类
package com.ycxw; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.BodyTagSupport; /** * foreach 标签 * * @author 云村小威 * */ public class ForeachTag extends BodyTagSupport { private String var; private List<Object> items = new ArrayList<Object>(); public String getVar() { return var; } public void setVar(String var) { this.var = var; } public List<Object> getItems() { return items; } public void setItems(List<Object> items) { this.items = items; } @Override public int doStartTag() throws JspException { Iterator<Object> it = items.iterator(); // 集合中没有元素会报错 pageContext.setAttribute(var, it.next()); pageContext.setAttribute("it", it); return EVAL_BODY_INCLUDE; } @Override public int doAfterBody() throws JspException { Iterator<Object> it = (Iterator<Object>) pageContext.getAttribute("it"); //判断如果有元素就循环 if (it.hasNext()) { pageContext.setAttribute(var, it.next()); pageContext.setAttribute("it", it); return EVAL_BODY_AGAIN; } else { //没有就跳过 return EVAL_PAGE; } } }
实体类:
package com.ycxw; /** * 实体类 * * @author 云村小威 * */ public class Person { private int id; private String name; private int age; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Person(int id, String name, int age) { super(); this.id = id; this.name = name; this.age = age; } @Override public String toString() { return "Person [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
3.3 JSP 测试
<%@page import="java.util.ArrayList"%> <%@page import="com.ycxw.Person"%> <%@page import="java.util.List"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib uri="com.ycxw" prefix="w" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% List<Person> list = new ArrayList<Person>(); list.add(new Person(1,"小黑宝",19)); list.add(new Person(2,"纯路人",21)); list.add(new Person(3,"ikun",27)); request.setAttribute("list", list); %> <w:foreach items="${list }" var="p"> ${p } </w:foreach> </body> </html>
运行结果:
4. select 标签
4.1 进入.tld文夹定义select标签并对自定义标签进行描述
<tag> <name>select</name> <tag-class>com.ycxw.SelectTag</tag-class> <body-content>JSP</body-content> <attribute> <name>id</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <!--数据源--> <attribute> <name>items</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>textKey</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <name>textVal</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> <!--不是从数据库加载出来的数据,下拉框的头一个选项的value值--> <attribute> <name>headertextKey</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> <!--不是从数据库加载出来的数据,下拉框的头一个选项的展示值--> <attribute> <name>headertextVal</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <name>selectedVal</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
4.2 创建助手类
package com.ycxw; import java.io.IOException; import java.lang.reflect.Field; import java.util.List; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.BodyTagSupport; import org.apache.commons.beanutils.PropertyUtils; /** * select 标签 * 分析: * 1.后台需要遍历->数据源 items * 2.需要一个对象的属性代表下拉框对应的展示内容->textVal * 3.需要一个对象属性代表下拉框对应的value值 ->textKey * 4.默认的头部选项展示内容 ->headerTextVal * 5.默认的头部选项值->headerTextKey * 6.数据中存储的值,为了方便做数据回显 -> selectedVal * 7.方便获取获取标签设置样式...等等标记 id、name * * @author 云村小威 * */ public class SelectTag extends BodyTagSupport { private static final long serialVersionUID = 1L; private List<Object> items; private String textVal; private String textKey; private String headertextVal; private String headertextKey; private String selectedVal; private String id; public List<Object> getItems() { return items; } public void setItems(List<Object> items) { this.items = items; } public String getTextVal() { return textVal; } public void setTextVal(String textVal) { this.textVal = textVal; } public String getTextKey() { return textKey; } public void setTextKey(String textKey) { this.textKey = textKey; } public String getHeadertextVal() { return headertextVal; } public void setHeadertextVal(String headertextVal) { this.headertextVal = headertextVal; } public String getHeadertextKey() { return headertextKey; } public void setHeadertextKey(String headertextKey) { this.headertextKey = headertextKey; } public String getSelectedVal() { return selectedVal; } public void setSelectedVal(String selectedVal) { this.selectedVal = selectedVal; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public int doStartTag() throws JspException { // 获取io流 JspWriter out = pageContext.getOut(); try { out.print(toHTML()); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return super.doStartTag(); } /** * 数据回显方法 * @return * @throws Exception */ private String toHTML() throws Exception { // 利用stringbuffer拼接标签 StringBuffer sb = new StringBuffer(); sb.append("<select id='"+id+"'>"); //判断不为空就给头部加默认选项 if(headertextVal != null && !"".equals(headertextVal)) { sb.append("<option value='"+headertextKey+"'>"+headertextVal+"</option>"); } //当集合有东西时才执行 if (items.size() > 0) { for (Object obj : items) { // 利用反射获取到页面传过来的属性名对应的属性值(id) Field f = obj.getClass().getDeclaredField(textKey); //打开修饰符访问权限 f.setAccessible(true); if(selectedVal != null && !"".equals(selectedVal) && selectedVal.equals(f.get(obj))) { sb.append("<option selected value='"+f.get(obj)+"'>"+PropertyUtils.getProperty(obj, textVal)+"</option>"); }else { sb.append("<option value='"+f.get(obj)+"'>"+PropertyUtils.getProperty(obj, textVal)+"</option>"); } } } sb.append("</select>"); return sb.toString(); } }
4.3 JSP页面测试 :
<%@page import="java.util.ArrayList"%> <%@page import="com.ycxw.Person"%> <%@page import="java.util.List"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib uri="com.ycxw" prefix="w"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% List<Person> list = new ArrayList<Person>(); list.add(new Person(1, "小黑宝", 19)); list.add(new Person(2, "纯路人", 21)); list.add(new Person(3, "ikun", 27)); request.setAttribute("list", list); %> <w:select headertextVal="请选择" textVal="name" items="${list }" selectedVal="-1" headertextKey="-1" textKey="id"></w:select> </body> </html>
运行结果:
最后总结:开发自定义标签的目的就是给我们带来更方便的操作
1. 提高代码复用性
自定义标签能够提高代码复用性,减少相似的代码出现在不同的页面中。
2. 更好的代码组织结构
通过自定义标签,能够更好地组织代码结构,使代码更加清晰、易于维护、修改和调试。
3. 分离逻辑和展示层
自定义标签能够将业务逻辑与展示层分离,增强了代码的可维护性和可读性。
4. 避免重复劳动
对于那些需要频繁修改的页面元素,如导航栏、页脚等,自定义标签能够避免开发者不停地重复编写代码的工作。
总之,自定义标签是JSP中非常有用的一个特性,它能够极大地提高代码的复用性、可维护性和可读性。