标签的开发
Tag Library(标签库)
自定义标签实际是一个实现了特定接口的java类,封装了一些常用的功能,在运行时,标签将被相应的代码所替换,由web容器调用
API javax.servlet.jsp.tagext中
要开发自定义标签,其核心就是编写标签处理器类,一个标签对应一个标签处理器类.所有的标签处理器类都需要实现JspTag接口(传统标签),简单标签实现SimpleTag接口
空标签<hello/>
带有属性的标签<max num1=”13” num2=“32”/>
带有内容的标签<greeting>welcome you!</greeting>
实现Tag接口的标签处理器的生命周期
setPageContext(PageContext pc)设置当前页面的上下文
setParment(Tag t)如果标签被嵌套,该方法被用来设置父标签
(1).容器在创建标签处理器的实例后,调用setPageContext()方法设置标签的页面上下文,然后调用setParent()方法设置这个标签的父标签,如果该标签没有父标签,就设置为null,页面初始化
(2).设置标签的属性,如果没有属性则跳过这一步
(3).调用doStartTag()方法,该方法可以返回EVAL_BODY_INCLUDE或者SKIP_BODY
----EVAL_BODY_INCLUDE将标签体输出到当前的输出流中
----SKIP_BODY则忽略该标签体
(4).调用doEndTag()方法,该方法可以返回EVAL_PAGE或者SKIP_PAGE
----EVAL_PAGE执行JSP余下的部分
----SKIP_PAGE忽略JSP页面余下的部分
(5)容器会缓存标签处理器实例,一旦遇到同样的标签,则重复使用缓存的标签处理器实例
(6)当需要释放标签处理器实例时,调用release()方法
IterationTag接口
继承自Tag接口,新增方法doAfterBody,新增返回值EVAL_BODY_AGAIN,请求重复执行标签体
BodyTag接口
继承自IterationTag接口,新增了两个方法
setBodyConten(BodyContent b);//设置BodyContent属性,空标签不会调用该方法
doInitBody()//在调用上面的方法之后,标签体第一次被执行之前,该方法被调用,为标签体的执行做准备
标签库描述符(tld文件)
这是一个xml文档,用来描述一个标签库,包含标签的名字、标签处理器类、标签的属性等
开发自定义标签步骤
(1)编写一个实现Tag接口的java类(标签处理器)
(2)编写标签描述符(tld)文件,在tld文件对标签进行描述
在开发人员编写JSP页面时,还会引入一些逻辑
·控制jsp某一部分是否执行
doStart()方法的根据返回值的不同判断标签体内是否执行
return SKIP_BODY;//忽略标签体的部分
return EVAL_BODY_INCLUDE;//执行标签体的部分
·控制整个jsp页面是否执行
doEnd()方法的返回值判断jsp页面是否执行
return SKIP_PAGE;//不执行页面
return EVAL_PAGE;//执行下面的页面
·修改jsp页面是否重复执行
实现IterationTag接口(是Tag接口的子类,增加了一个doAfter())
标签体一执行完就返回一个值,用这个值来判断该标签体是否继续执行
if(条件)
return IterationTag.EVAL_BODY_AGAIN;
else
return IterationTag.SKIP_BODY;
·jsp页面的输出(实现BodyTag,增加了一个操作标签体的方法)
将标签体内的字符串改成大写
把标签体转成字符串,然后进行自定义的处理
String body=this.getBodyContent().getString();
body.toUpperCase();
try {
this.pageContext.getOut().write(body);
} catch (IOException e) {
e.printStackTrace();
}
return EVAL_PAGE;
Tag接口的默认实现类TagSupport,覆盖它的doStart()方法,然后配tld文件,
简单标签接口()
Void setJspBody(JspFragment jspBody);
Void doTag();简单标签使用该方法完成所有的业务逻辑
·标签体是否执行
JspFragment jf=this.getJspBody();//代表标签体的JspFragment
jf.invoke(this.getJspContext().getOut());
不执行则不调用invoke方法
·标签体循环多次
JspFragment jf=getJspBody();//获取标签体
JspWriter out=this.getJspContext().getOut();
for(int i=0;i<10;i++){
jf.invoke(out);
}
·转换标签体的大小写
JspFragment jf=getJspBody();
StringWriter sw=new StringWriter();
jf.invoke(sw);//jf进入流中
String content=sw.getBuffer().toString();
content=content.toUpperCase();
PageContext page=(PageContext) this.getJspContext();
page.getOut().write(content);
·控制整个jsp页面是否执行
抛出异常就可以不执行下面的jsp页面
简单标签库的执行顺序
1. 执行标签时,先创建标签处理器对象
2. 调用setJspContext()方法,把页面的pageContext传递进去
3. 调用setParent()方法,把父标签传递进去
4. 调用setJspBody()方法,把代表标签体的jspFragment对象传递进去
5. 开始执行标签,调用doTag()方法,实现业务
开发带属性的标签
将标签体循环多少次?可以把次数封装成标签的属性
·在标签处理器中编写每个属性对应的setter方法
·在tld文件中描述标签的属性
private int count;
public void setCount(int count) {
this.count = count;
}
public void doTag() throws JspException, IOException {
for(int i=0;i<count;i++){
this.getJspBody().invoke(null);
}
}
tld文件的配置
<tag>
<name>demo4</name>
<tag-class>javaTag.SimpleTag4</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>count</name>
<!-- 根据反射获取count -->
<required>true</required>
<!—指定该属性是否是必须的 -->
<rtexprvalue>true</rtexprvalue>
<!-- 属性是否可以传递表达式 -->
</attribute>
</tag>
<attribute>标签及其子元素,用来设置标签属性
<description>为该属性提供文本描述
<name>指定属性的名称
<required>指定该属性是否是必须的,默认值是false
<rtexprvalue>在运行时期,属性的值能否是一个表达式
<type>指定属性的类型
<fragment>指定属性是否是JspFragment对象,默认值为false
<dynamic-attributes>指定该标签是否支持动态属性
<example>用于提供一个使用该标签的信息描述
如果属性是8种基本数据类型,那么在JSP页面传递的是字符串,引擎会自动转换成为相应的类型
如果传入的不是基本类型数据,那么用${date},date被封装到了request域里面
简单标签的标签处理器实例不会被缓存,所以不能够重复使用,每当遇到标签时,容器就会创建一个信的标签处理器实例
Example
·防盗链标签
防止别的网站通过超链接指向自己的web资源
private String site;
private String page;
public void setSite(String site) {
this.site = site;
}
public void setPage(String page) {
this.page = page;
}
PageContext pc=(PageContext) this.getJspContext();
HttpServletRequest request=(HttpServletRequest) pc.getRequest();
String refer=request.getHeader("referer");//获取是从那个页面跳转过来的
HttpServletResponse response=(HttpServletResponse) pc.getResponse();//得到响应进行重定向
if(refer==null || !refer.startsWith(site)){
String root=request.getContextPath();//得到webroot的名称
if(page.startsWith(root)){//如果page是以root开头
response.sendRedirect(page);
}else{
response.sendRedirect(root+page);
}
throw new SkipPageException();//控制保护页面不要执行
}
<cc:refe site="http://localhost:8080/" page="/index.jsp"/>
防止从site来的链接盗取web资源,必须经过本地首页访问才能查看
·if-else标签的开发
需要if通知else执行,那么如何获取if发过来的通知
解决方案:定义if,else的父标签,写三个标签处理器
在父标签处理器中维护一个boolean变量,当执行了一个子标签之后,改变boolean的值,就可以不执行另一个子标签
·for-each标签的开发
在标签处理器中维护一个Object对象(items)和String对象(var)
在doTag方法再通过instanceof判断Object属于哪种类型,进行相应的处理(collection、Object[]、Map)
还可以重构代码再维护一个collection引用,在set方法中将全部类型转成collection,对于数组类型的处理
if(items.getClass().isArray()){
collection=new ArrayList();
int len=Array.getLength(items);
for(int i=0;i<len;i++){
Object obj=Array.get(items, i);
collection.add(obj);
}
}
·开发转义标签
public void doTag() throws JspException, IOException {
StringWriter sw=new StringWriter();
getJspBody().invoke(sw);//将标签体的内容交给sw流
String content=sw.getBuffer().toString();//拿到标签体的内容
content=filter(content);//进行转义
getJspContext().getOut().write(content);
}
public String filter(String message) {
if (message == null)
return (null);
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuilder result = new StringBuilder(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}

浙公网安备 33010602011771号