SimpleTag接口
- 首先我们来看一下它的源码吧:
public interface SimpleTag extends JspTag { void doTag() throws JspException, IOException; void setParent(JspTag var1); JspTag getParent(); void setJspContext(JspContext var1); void setJspBody(JspFragment var1); }
- setParent()和getParent()方法就不多说了,我们来看一下剩下的3个方法:
void doTag() throws JspException, IOException; void setJspContext(JspContext var1); void setJspBody(JspFragment var1);
- 明显地:
- doTag()就是我们要写代码处理逻辑地方
- setJspContext(JspContext var1)是将PageContext对象传递给标签处理器类(PageContext是JspContext的子类)
- setJspBody(JspFragment var1)把代表标签体的JspFragment对象传递给标签处理器对象
快速入门
- 一般地,我们做开发都是继承SimpleTagSupport类(该类实现了SimpleTag)来编写自定义标签
- 下面我们就来个快速入门吧:
- 目标:传入字符串格式就可以显示想要的格式日期,对比之前传统标签的,看有什么不同之处
- 标签处理器类:
public class Demo1 extends SimpleTagSupport { String format = null; @Override public void doTag() throws JspException, IOException { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format); this.getJspContext().getOut().write(simpleDateFormat.format(new Date())); } public String getFormat() { return format; } public void setFormat(String format) { this.format = format; } }
- tld文件:
<tag> <name>formatDate</name> <tag-class>tag.Demo1</tag-class> <body-content>tagdependent</body-content> <attribute> <name>format</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
- 效果:
- 简单标签的好处就是不用去理会doStartTag、doEndTag、SKIP_BODY以及一系列的方法和属性!
- 简单标签一个doTag()方法走天下!
SimpleTagSupport类的执行顺序:
- ①WEB容器调用标签处理器对象的setJspContext方法,将代表JSP页面的pageContext对象传递给标签处理器对象
- ②WEB容器调用标签处理器对象的setParent方法,将父标签处理器对象传递给这个标签处理器对象。【注意,只有在标签存在父标签的情况下,WEB容器才会调用这个方法】
- ③如果调用标签时设置了属性,容器将调用每个属性对应的setter方法把属性值传递给标签处理器对象。如果标签的属性值是EL表达式或脚本表达式,则WEB容器首先计算表达式的值,然后把值传递给标签处理器对象。
- ④如果简单标签有标签体,容器将调用setJspBody方法把代表标签体的JspFragment对象传递进来
- ⑤执行标签时:容器调用标签处理器的doTag()方法,开发人员在方法体内通过操作JspFragment对象,就可以实现是否执行、迭代、修改标签体的目的。
深入简单标签
在我们讲解传统标签的时候,配合着SKIP_BODY、SKIP_PAGE等变量可以实现如下的功能:
- 控制jsp页面某一部分内容(标签体)是否执行。
- 控制整个jsp页面是否执行。
- 控制jsp页面内容重复执行。
- 修改jsp页面内容输出。
- 简单标签可没有这些变量呀,那它怎么才能实现上面那些功能呢?
- 在doTag方法中可以抛出javax.servlet.jsp.SkipPageException异常,用于通知WEB容器不再执行JSP页面中位于结束标记后面的内容,这等效于在传统标签的doEndTag方法中返回Tag.SKIP_PAGE常量的情况,我们来测试一下,在上面例子的代码中添加:
throw new SkipPageException();
- 效果:
- 至于其他的功能下面会讲到!
带标签体的简单标签
- SimpleTagSupport也可以带标签体,但是处理方法和传统标签完全不同。
- 传统标签是这样子的:将标签体的内容通过setBodyContent()注入到BodyContent对象中。
- 简单标签是这样子的:通过JspFragment对象实现!
- 我们来看一下JspFragment对象的源码吧:
public abstract class JspFragment { public JspFragment() { } public abstract void invoke(Writer var1) throws JspException, IOException; public abstract JspContext getJspContext(); }
JspFragment对象十分简单,重要的只有invoke(Writer var1)方法(获取JspContext对象并不重要,在标签描述器上就可以获取到了)
public abstract void invoke(java.io.Writer out) :
- 用于执行JspFragment对象所代表的JSP代码片段
- 参数out用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果传递给参数out的值为null,则将执行结果写入到JspContext.getOut()方法返回的输出流对象中。(简而言之,可以理解为写给浏览器)
- 下面是标签处理器类的代码:
public class Demo1 extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { //得到代表标签体的对象 JspFragment jspFragment = getJspBody(); //invoke方法接收的是一个Writer,如果为null,就代表着JspWriter(),将标签体的数据写给浏览器! jspFragment.invoke(null); } }
- 效果:
- 既然标签体的内容是通过JspFragment对象的invoke()方法写给浏览器的,那么那么那么,我只要控制好invoke()方法,我想干什么就干什么!
- 也就是说:
- 不调用invoke()方法,标签体内容就不会输出到浏览器上
- 重复调用invoke()方法,标签体内容就会被重复执行
- 若想在标签处理器中修改标签体内容,只需在调用invoke方法时指定一个可取出结果数据的输出流对象(例如StringWriter),让标签体的执行结果输出到该输出流对象中,然后从该输出流对象中取出数据进行修改后再输出到目标设备,即可达到修改标签体的目的
- 来来来,我们来试验一下:
- 不调用invoke()方法
public void doTag() throws JspException, IOException { //得到代表标签体的对象 JspFragment jspFragment = getJspBody(); //jspFragment.invoke(null); }
- 标签体的内容没有输出
- 调用两次invoke()方法
public void doTag() throws JspException, IOException { //得到代表标签体的对象 JspFragment jspFragment = getJspBody(); jspFragment.invoke(null); jspFragment.invoke(null); }
- 标签体的内容被输出了两次!
- invoke()方法指定别的输出流(StringWriter),将标签体的内容写到流对象中,再通过流对象把数据取出来,达到修改的目的。
//得到代表标签体的对象 JspFragment jspFragment = getJspBody(); //创建可以存储字符串的Writer对象 StringWriter stringWriter = new StringWriter(); //invoke()方法把标签体的数据都写给流对象中 jspFragment.invoke(stringWriter); //把流对象的数据取出来,流对象的数据就是标签体的内容 String value = stringWriter.toString(); //将数据改成是大写的,写到浏览器中 getJspContext().getOut().write(value.toUpperCase());
- 标签体的内容被改成了大写!
我们可以发现,传统标签能完成的功能,简单标签都可以完成,并且更为简单!
自定义标签的应用
既然我们学了简单标签,我们就用简单标签来做开发吧!
防盗链
在讲解request对象的时候,我们讲解过怎么实现防盗链的功能。现在我们使用标签来进行防盗链!
模拟下场景:1.jsp页面是海贼王资源,2.jsp页面提示非法盗链,index1.jsp是我的首页。别人想要看我的海贼王资源,就必须通过我的首页点进去看,否则就是非法盗链!
- 标签处理器的代码:
@Override public void doTag() throws JspException, IOException { //如果想要做成更加灵活的,就把站点设置和资源设置成标签属性传递进来! //等会我们要获取得到request对象,需要使用到JspContext的子类PageContext PageContext pageContext = (PageContext) this.getJspContext(); //获取request对象 HttpServletRequest httpServletRequest = (HttpServletRequest) pageContext.getRequest(); //获取到referer String referer = httpServletRequest.getHeader("Referer"); //获取到response对象,等会如果是非法盗链,就重定向别的页面上 HttpServletResponse httpServletResponse = (HttpServletResponse) pageContext.getResponse(); //非法盗链! if (referer == null || !referer.startsWith("http://localhost:8080/zhongfucheng")) { //2.jsp提示了非法盗链! httpServletResponse.sendRedirect("/zhongfucheng/2.jsp"); //不执行页面下面的内容了,保护页面 throw new SkipPageException(); } }
- 1.jsp代码:
<zhongfucheng:Referer/> <html> <head> <title></title> </head> <body> 海贼王最新资源 </body> </html>
- index1.jsp代码:
<h1>这是首页!</h1> <a href="${pageContext.request.contextPath}/1.jsp"> 海贼王最新资源</a>
- 2.jsp代码:
<body> 你是非法盗链的!!!!!! </body>
- 第一次我是直接访问1.jsp,Referer是为空的,所以是非法盗链。第二次我是通过从首页点进去看的,所以可以访问1.jsp。效果图:
if标签
在JSTL中,我们已经使用过了<c:if/>
标签了,现在我们学习了自定义标签,可以开发类似于JSTL的if标签了!
既然是if标签,那么就需要编写带属性和带标签体的标签(需要判断是true还是false呀!,通过判断是否为真值来决定是否执行标签体的内容)
- 标签处理器代码:
public class Demo1 extends SimpleTagSupport { //定义一个Boolean类型的变量 boolean test ; @Override public void doTag() throws JspException, IOException { //获取到代表标签体内容的对象 JspFragment jspFragment = this.getJspBody(); //如果为真值才执行标签体的内容 if (test == true) { jspFragment.invoke(null); } } public boolean isTest() { return test; } public void setTest(boolean test) { this.test = test; } }
- tld文件的代码:
<tag> <name>if</name> <tag-class> tag.Demo1</tag-class> <body-content>scriptless</body-content> <attribute> <name>test</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
- 本来就没有user这个域对象属性,所以user就是为null:
- 将user改成不为null,浏览器就没有输出了