在JSP中,往往需要写Java代码,这样前后端不够分离。EL表达式使得JSP页面只有标签的存在,而标签的逻辑代码写在对应的.java文件中。
一个EL自定义标签包括.tld配置文件(放在/WebContent/WEB-INF目录下)和标签所对应的.java 文件。
.tld文件所必需的部分:
<?xml version="1.0" encoding="UTF-8" ?>
<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">
<description>JSTL 1.1 core library</description>
<display-name>JSTL core</display-name>
<tlib-version>1.1</tlib-version>
<!-- 建议在jsp页面上使用的标签的前缀 -->
<short-name>test</short-name>
<!-- 作为tld文件的id,用来标识当前的tld文件 -->
<uri>http://java.zhang.com/jsp/jstl/core</uri>
<tag>
<!-- 标签的名字 -->
<name>hello</name>
<!-- 标签所在的全类名 -->
<tag-class>tag.HelloSipleTag</tag-class>
<!--标签体类型 -->
<body-content>empty</body-content>
<!-- 描述当前标签的属性 -->
<attribute>
<!-- 属性名 -->
<name>value</name>
<!-- 该属性是否为必须 -->
<required>true</required>
<!-- runtime expression value
当前属性是否可以接收运行时表达式的动态值-->
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
1.简单标签
标签需要实现SimpleTag接口或者继承SimpleTagSupport类。
1)当实现SimpleTag接口时,需要实现接口所带的这四个方法
@Override
public JspTag getParent() {
// TODO Auto-generated method stub
return null;
}
//三个get方法由servlet引擎调用,把相应对象传入
@Override
public void setJspBody(JspFragment arg0) {
// TODO Auto-generated method stub
}
//写一个全局变量 用于接收传入的pageContext(可以获取到其他的八个隐含对象)
private PageContext pageContext;
@Override
public void setJspContext(JspContext arg0) {
// TODO Auto-generated method stub
this.pageContext=(PageContext)arg0;
}
@Override
public void setParent(JspTag arg0) {
// TODO Auto-generated method stub
}
具体标签实现的逻辑由doTag()方法实现:
//实际的标签体逻辑
public void doTag() throws JspException, IOException {
// TODO Auto-generated method stub
System.out.println("doTag");
System.out.println(value+","+count);
HttpServletRequest request=(HttpServletRequest)pageContext.getRequest();
pageContext.getOut().print("Hello:"+request.getParameter("name"));
}
如果标签附带属性,需要在.java文件中写属性所对应的setter方法
注意:在.tld文件中声明的属性要和setter方法中名字一致
//设置标签体的属性
private String value;
private String count;
public void setValue(String value) {
this.value = value;
}
public void setCount(String count) {
this.count = count;
}
JSP页面的标签:
<test:hello value="111"/>
运行结果为:
简单标签的设计思路:首先在.tld文件中声明所需要的标签,然后在.java文件中写具体的方法,最后在JSP页面调用写好的标签。
注意:在实现SimpleTag接口时,获取PageContext对象需要先写一个全局变量
private PageContext pageContext
然后再强制类型转换
public void setJspContext(JspContext arg0) {
// TODO Auto-generated method stub
this.pageContext=(PageContext)arg0;
}
通过PageContext对象可以获得其他八个隐含对象。如:pageContext.getOut()
2)继承SimpleTagSupport类(常用)
在SimpleTagSupport类中只需要写doTag方法,获取pageContext对象不需要在setJspContext方法中类型转换获取, SimpleTagSupport类添加了getJspContext()方法获取JspContext对象
//实际的标签体逻辑
public void doTag() throws JspException, IOException {
// TODO Auto-generated method stub
System.out.println("doTag");
System.out.println(value+","+count);
//在SimpleTagSupport类中只需要写doTag方法,获取pageContext对象不需要在setJspContext方法中获取,继承SimpleTag类后有内置的getJspContext方法获取JspContext对象
PageContext pageContext=(PageContext)getJspContext();
//通过pageContext对象获得request对象
HttpServletRequest request=(HttpServletRequest)pageContext.getRequest();
getJspContext().getOut().print("Hello:"+request.getParameter("name"));
}
实现原理:
1.在JSP页面中调用标签时,JSP引擎利用setJspContext()和setXXX()将代表当前页面的pageContext对象和属性传给标签处理器类,
2.在.java文件中将pagecontext对象和属性取出,在doTag方法中进行标签的逻辑操作
2.带标签体的标签
/*
* 1.若一个标签有标签体<test:testJspFragment>Hello</test:testJspFragment>
* 在自定义标签处理器中使用JSPFragment对象封装标签体信息
* 2.若配置了标签含有标签体,则JSP 引擎会调用setJspBody()方法,把JSPFragment对象传递给标签处理器类
* 在SimpleTagSupport中还定义了一个getJspBody()方法,用于返回JspFragment对象,便于直接调用
* 3.JSPFragment的 invoke(Writer)方法:把标签体内容从Writer中输出
* 若为invoke(null),则等同于invoke(getJspContext().getOut()),即直接把标签体内容输出到页面上
* 4. 在.tld文件中使用body-content节点来描述标签体类型:
* 1)empty:没有标签体
* 2)(大部分)scriptless:标签体可以包含el表达式(${param.name})和jsp动作元素,但不能包含jsp的脚本元素(<% request.getParameter("name")%>)
* 3)tagdependent:表示标签体交由标签本身去处理,若指定tagdependent,在标签体中的所有代码都会原封不动的交给标签处理器,而不是将执行结果交给标签处理器
*/
public class TestJspFragment extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
JspFragment jspFragment = getJspBody();
//JspFragment.invoke(Writer):Writer即为标签体内容输出的字符流,若为null,则输出到getJspContext().getOut(),即输出到页面上
//标签的属性可以通过getJspContext().getOut().print(xxx)输出到页面上
//而标签体的内容通过jspFragment.invoke(null)输出到页面上
jspFragment.invoke(null);
}
}
3.带父标签的标签
父标签:
/*
* 1.父标签无法获取子标签的引用,父标签把子标签作为标签体来引用
* 2.子标签可以通过getParent()方法获取父标签的引用。
* 若此标签确实具有父标签,JSP引擎会把代表父标签的引用通过setParent(JspTag parent)方法赋给标签处理器,然后通过getParent()取出
* 3.父标签是JspTag类型,该接口是一个空接口,是用来统一SimpleTag和Tag的,实际使用需要类型转换
* 4.在.tld文件中,无需为父标签配额外的配置,子标签是以标签体形式存在
*
*/
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class ParentTag extends SimpleTagSupport {
private String name="ParentTag";
public String getName() {
return name;
}
@Override
public void doTag() throws JspException, IOException {
System.out.println("父标签的标签处理器类name:" + name);
getJspBody().invoke(null);
}
}
子标签:
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class SonTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
//1.得到父标签的引用
JspTag parent = getParent();
//2.获取父标签的name属性
ParentTag parentTag = (ParentTag)parent;
String name = parentTag.getName();
//3.把 name 值打印到JSP页面上
getJspContext().getOut().print("子标签输出name:"+name);
}
}
4.foreach标签
import java.io.IOException;
import java.util.Collection;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class ForEachTag extends SimpleTagSupport {
private Collection<?> items;
public void setItems(Collection<?> items) {
this.items = items;
}
private String var;
public void setVar(String var) {
this.var = var;
}
@Override
/*
* 1.将标签体的属性利用setXXX方法传入到.java中,并准备遍历item集合 此时键(var)是l
* 2.把正在遍历的对象和var形成键值对放入到pageContext中 ,利用setAttribute方法 (getJspContext().setAttribute(var, obj);) 键:var 值:正在遍历的对象
* 在JSP页面使用el表达式利用键取出setAttribute设置好的值 (${l} 等同于 <% pageContent.getAttribute("l") %>
* 3.遍历集合,把标签体的el表达式获取到的内容输出到页面上
*/
public void doTag() throws JspException, IOException {
if(items != null) {
for(Object obj : items) {
//把正在遍历的对象以键值对的形式放入到pageContext中
getJspContext().setAttribute(var, obj);
/*
不能通过getJspContext().getOut()方法直接输出集合对象,因为集合的对象可能包含多个属性,只能先放入PageContext域中,在Jsp页面上的foreach标签体中利用EL表达式获取到对象的每个属性,然后利用getJspBody().invoke(null)输出标签体中获取的内容
*/
getJspBody().invoke(null);
}
}
}
}
<%
List<String> list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
request.setAttribute("list", list);
%>
<!-- var是setAttribute和getAttribute的键 ,通过var,把遍历的集合对象存入pageContext并在JSP中利用var取出(即标签体的内容就是每个的集合对象的内容),并把标签体的内容输出到页面上-->
<test:forEach items="${requestScope.list }" var="l">
${pageScope.l};
<br>
${l};
<br>
</test:forEach>
5.choose标签
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class ChooseTag extends SimpleTagSupport {
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
public boolean isFlag() {
return flag;
}
@Override
public void doTag() throws JspException, IOException {
getJspBody().invoke(null);
}
}
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class WhenTag extends SimpleTagSupport {
private boolean test;
public void setTest(boolean test) {
this.test = test;
}
@Override
public void doTag() throws JspException, IOException {
if(test) {
ChooseTag chooseTag = (ChooseTag)getParent();
boolean flag = chooseTag.isFlag();
if(flag) {
getJspBody().invoke(null);
chooseTag.setFlag(false);
}
}
}
}
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 chooseTag = (ChooseTag)getParent();
if(chooseTag.isFlag())
getJspBody().invoke(null);
}
}
<test:choose>
<test:when test="${param.age>24 }">大学毕业</test:when>
<test:when test="${param.age>20 }">高中毕业</test:when>
<test:otherwise>高中</test:otherwise>
</test:choose>
<br>
choose标签的思路是 在choose标签中设置一个flag 根据flag来判断语句是否执行,执行了语句flag就变为false,不再继续执行别的语句。