首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

JSP第七篇简单标签、应用、DynamicAttribute接口

为什么要用到简单标签?上一篇博客中我已经讲解了传统标签,想要开发自定义标签,大多数情况下都要重写doStartTag(),doAfterBody()和doEndTag()方法,并且还要知道SKIP_BODY,EVAL_BODY等等的变量代表着什么,在什么方法中使用。这样实在是太麻烦了!因此,为了简化标签开发的复杂度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签的功能。。一般来说,实现了SimpeTag接口的标签称之为简单标签SimpleTag接口

首先我们来看一下它的源码吧

publicinterfaceSimpleTagextendsJspTag{

voiddoTag()throwsJspException,IOException;

voidsetParent(JspTagvar1);

JspTaggetParent();

voidsetJspContext(JspContextvar1);

voidsetJspBody(JspFragmentvar1);

}

setParent()和getParent()方法就不多说了,我们来看一下剩下的3个方法

voiddoTag()throwsJspException,IOException;

voidsetJspContext(JspContextvar1);

voidsetJspBody(JspFragmentvar1);

明显地:

doTag()就是我们要写代码处理逻辑地方

setJspContext(JspContext var1)是将PageContext对象传递给标签处理器类(PageContext是JspContext的子类)

setJspBody(JspFragment var1)把代表标签体的JspFragment对象传递给标签处理器对象

快速入门

一般地,我们做开发都是继承SimpleTagSupport类(该类实现了SimpleTag)来编写自定义标签

下面我们就来个快速入门吧

目标:传入字符串格式就可以显示想要的格式日期,对比之前传统标签的,看有什么不同之处标签处理器类:

publicclassDemo1extendsSimpleTagSupport{

Stringformat=null;

@Override

publicvoiddoTag()throwsJspException,IOException{

SimpleDateFormatsimpleDateFormat=newSimpleDateFormat(format);

this.getJspContext().getOut().write(simpleDateFormat.format(newDate()));

}

publicStringgetFormat(){

returnformat;

}

publicvoidsetFormat(Stringformat){

this.format=format;

}

}

tld文件:

formatDate

tag.Demo1

tagdependent

format

true

true

效果:

简单标签的好处就是不用去理会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页面内容输出

thrownewSkipPageException();

效果:

至于其他的功能下面会讲到

带标签体的简单标签SimpleTagSupport也可以带标签体,但是处理方法和传统标签完全不同

传统标签是这样子的:将标签体的内容通过setBodyContent()注入到BodyContent对象中。

简单标签是这样子的:通过JspFragment对象实现!

我们来看一下JspFragment对象的源码吧

publicabstractclassJspFragment{

publicJspFragment(){

}

publicabstractvoidinvoke(Writervar1)throwsJspException,IOException;

publicabstractJspContextgetJspContext();

}

JspFragment对象十分简单,重要的只有invoke(Writer var1)方法(获取JspContext对象并不重要,在标签描述器上就可以获取到了)public abstract void invoke(java.io.Writer out) :

用于执行JspFragment对象所代表的JSP代码片段

参数out用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果传递给参数out的值为null,则将执行结果写入到JspContext.getOut()方法返回的输出流对象中。(简而言之,可以理解为写给浏览器)

下面是标签处理器类的代码

publicclassDemo1extendsSimpleTagSupport{

@Override

publicvoiddoTag()throwsJspException,IOException{

//得到代表标签体的对象

JspFragmentjspFragment=getJspBody();

//invoke方法接收的是一个Writer,如果为null,就代表着JspWriter(),将标签体的数据写给浏览器!

jspFragment.invoke(null);

}

}

效果:

既然标签体的内容是通过JspFragment对象的invoke()方法写给浏览器的,那么那么那么,我只要控制好invoke()方法,我想干什么就干什么!

也就是说:

不调用invoke()方法,标签体内容就不会输出到浏览器上

重复调用invoke()方法,标签体内容就会被重复执行

若想在标签处理器中修改标签体内容,只需在调用invoke方法时指定一个可取出结果数据的输出流对象(例如StringWriter),让标签体的执行结果输出到该输出流对象中,然后从该输出流对象中取出数据进行修改后再输出到目标设备,即可达到修改标签体的目的

来来来,我们来试验一下:

publicvoiddoTag()throwsJspException,IOException{

//得到代表标签体的对象

JspFragmentjspFragment=getJspBody();

//jspFragment.invoke(null);

}

publicvoiddoTag()throwsJspException,IOException{

//得到代表标签体的对象

JspFragmentjspFragment=getJspBody();

jspFragment.invoke(null);

jspFragment.invoke(null);

}

//得到代表标签体的对象

JspFragmentjspFragment=getJspBody();

//创建可以存储字符串的Writer对象

StringWriterstringWriter=newStringWriter();

//invoke()方法把标签体的数据都写给流对象中

jspFragment.invoke(stringWriter);

//把流对象的数据取出来,流对象的数据就是标签体的内容

Stringvalue=stringWriter.toString();

//将数据改成是大写的,写到浏览器中

getJspContext().getOut().write(value.toUpperCase());

标签体的内容被改成了大写!

invoke()方法指定别的输出流(StringWriter),将标签体的内容写到流对象中,再通过流对象把数据取出来,达到修改的目的。

标签体的内容被输出了两次

调用两次invoke()方法

标签体的内容没有输出

不调用invoke()方法

我们可以发现,传统标签能完成的功能,简单标签都可以完成,并且更为简单!自定义标签的应用既然我们学了简单标签,我们就用简单标签来做开发吧!防盗链在讲解request对象的时候,我们讲解过怎么实现防盗链的功能。现在我们使用标签来进行防盗链!模拟下场景:1.jsp页面是海贼王资源,2.jsp页面提示非法盗链,index1.jsp是我的首页。别人想要看我的海贼王资源,就必须通过我的首页点进去看,否则就是非法盗链!

标签处理器的代码

@Override

publicvoiddoTag()throwsJspException,IOException{

//如果想要做成更加灵活的,就把站点设置和资源设置成标签属性传递进来!

//等会我们要获取得到request对象,需要使用到JspContext的子类PageContext

PageContextpageContext=(PageContext)this.getJspContext();

//获取request对象

HttpServletRequesthttpServletRequest=(HttpServletRequest)pageContext.getRequest();

//获取到referer

Stringreferer=httpServletRequest.getHeader("Referer");

//获取到response对象,等会如果是非法盗链,就重定向别的页面上

HttpServletResponsehttpServletResponse=(HttpServletResponse)pageContext.getResponse();

//非法盗链!

if(referer==null!referer.startsWith("http://localhost:8080/zhongfucheng")){

//2.jsp提示了非法盗链!

httpServletResponse.sendRedirect("/zhongfucheng/2.jsp");

//不执行页面下面的内容了,保护页面

thrownewSkipPageException();

}

}

1.jsp代码:

海贼王最新资源

index1.jsp代码

这是首页!

海贼王最新资源

2.jsp代码

你是非法盗链的!!!!!!

第一次我是直接访问1.jsp,Referer是为空的,所以是非法盗链。第二次我是通过从首页点进去看的,所以可以访问1.jsp。效果图:

if标签在JSTL中,我们已经使用过了标签了,现在我们学习了自定义标签,可以开发类似于JSTL的if标签了!既然是if标签,那么就需要编写带属性和带标签体的标签(需要判断是true还是false呀!,通过判断是否为真值来决定是否执行标签体的内容)

标签处理器代码

publicclassDemo1extendsSimpleTagSupport{

//定义一个Boolean类型的变量

booleantest;

@Override

publicvoiddoTag()throwsJspException,IOException{

//获取到代表标签体内容的对象

JspFragmentjspFragment=this.getJspBody();

//如果为真值才执行标签体的内容

if(test==true){

jspFragment.invoke(null);

}

}

publicbooleanisTest(){

returntest;

}

publicvoidsetTest(booleantest){

this.test=test;

}

}

tld文件的代码

if

tag.Demo1

scriptless

test

true

true

本来就没有user这个域对象属性,所以user就是为null

将user改成不为null,浏览器就没有输出了

forEach标签forEach标签最基本的功能:遍历集合、数组

首先,我先写一个可以遍历List集合的标签,可能我们会这样设计

publicclassDemo2extendsSimpleTagSupport{

//遍历的是List集合,于是标签的属性就为List

privateListitems;

//遍历出来的对象就用Object存着,因为我们不知道List集合保存的是什么元素

privateObjectvar;

@Override

publicvoiddoTag()throwsJspException,IOException{

//获取到迭代器

Iteratoriterator=items.iterator();

//遍历集合

while(iterator.hasNext()){

//获取到集合的元素

var=iterator.next();

//.....var属性代表的就是集合的元素,现在问题来了,好像在标签体内无法获取到这个对象....

//做到这里完成不下去了....

}

}

publicvoidsetItems(Listitems){

this.items=items;

}

publicvoidsetVar(Objectvar){

this.var=var;

}

}

上面的思路是正常的,但是做不下去!我们换一个思路呗。上面的问题主要是在标签体获取不到被遍历出来的对象!我们这样做:把var定义成String类型的,如果遍历得到对象了,就设置PageContext的属性,var为关键字,对象为值。在标签体用EL表达式搜索以var为关键字的对象!每遍历出一个对象,就执行一次标签体!

于是就有了以下的代码!

publicclassDemo1extendsSimpleTagSupport{

//遍历的是List集合,定义List集合成员变量

privateListitems;

//以var为关键字存储到PageContext

privateStringvar;

@Override

publicvoiddoTag()throwsJspException,IOException{

//获取到集合的迭代器

Iteratoriterator=items.iterator();

//获取到代表标签体内容的对象

JspFragmentjspFragment=this.getJspBody();

//遍历集合

while(iterator.hasNext()){

Objecto=iterator.next();

//把遍历出来的对象存储到page范围中,关键字为标签的属性var(在标签体中使用EL表达式$,就能够获取到集合的对象了!)

this.getJspContext().setAttribute(var,o);

//每设置了一个属性,我就执行标签体

jspFragment.invoke(null);

}

}

publicvoidsetItems(Listitems){

this.items=items;

}

publicvoidsetVar(Stringvar){

this.var=var;

}

}

tld文件如下

forEach

tag.Demo1

scriptless

var

true

false

items

true

true

测试的jsp代码如下

Listlist=newArrayList();

list.add("zhongfucneng");

list.add("1");

list.add("2");

list.add("3");

request.setAttribute("list",list);

%>

$

效果:

上面写的仅仅能够遍历List集合,做一个通用的forEach标签麻烦的是在:不知道传进来的是什么类型的数组、什么类型集合!,需要逐一去判断我们的实现思路就是将所有的集合或数组都转成是Collection类型的!我们来尝试着写一下

//如果items是Collection类型的,就强转为Colletion

if(itemsinstanceofCollection){

collection=(Collection)items;

}

//如果itmes是Map类型的,那么就强转为Map,再获取到

if(itemsinstanceofMap){

Mapmap=(Map)items;

collection=(Collection)map.entrySet();

}

//对象数组

if(itemsinstanceofObject[]){

Object[]objects=(Object[])items;

collection=Arrays.asList(objects);

}

//int[],Byte[],char[]等八大基本数据类型.....

还有int[],byte[],char[]等八大基本数据类型,这八大基本数据类型就不能用Arrays.asList()把引用传进去了。因为JDK5以后会把引用自动装箱成Interger[]、Byte[]等等,而不是获取到数组的元素数据。

测试代码如下:

效果:

对于八大基本类型数据我们就可以这样干

if(itemsinstanceofint[]){

int[]ints=(int[])items;

collection=newArrayList();

for(intanInt:ints){

collection.add(anInt);

}

}

//......这里还要写7个

JSTL的forEach标签类似就是这样干的

由于JDK5的新特性,我们又有另外的解决方案,Class对象能够判断是否为数组类,reflect反射包下Array类

其实,无论Map集合、还是任何类型的数组、都可以使用Colletion进行遍历!。

如果是八大基本数据类型的数组,我们就使用反射来进行构建出Collection集合。标签处理器的代码

publicclassDemo1extendsSimpleTagSupport{

//遍历的是未知的集合或数组,定义成Object

privateObjectitems;

//每次被遍历的对象存储关键字

privateStringvar;

//Colletion

privateCollectioncollection;

//在WEB容器设置标签的属性的时候,判断是什么类型的数组和集合

publicvoidsetItems(Objectitems){

this.items=items;

//如果items是Collection类型的,就强转为Colletion

if(itemsinstanceofCollection){

collection=(Collection)items;

}

//如果itmes是Map类型的,那么就强转为Map,再获取到

if(itemsinstanceofMap){

Mapmap=(Map)items;

collection=(Collection)map.entrySet();

}

//可以这样解决,Class对象判断是否是一个数组类

if(items.getClass().isArray()){

//创建Collection集合添加数组的元素!

collection=newArrayList();

//再利用reflect包下的Array类获取到该数组类的长度

intlen=Array.getLength(items);

//遍历并添加到集合中

for(inti=;i

collection.add(Array.get(items,i));

}

}

}

publicvoidsetVar(Stringvar){

this.var=var;

}

@Override

publicvoiddoTag()throwsJspException,IOException{

//获取到代表标签体内容的对象

JspFragmentjspFragment=this.getJspBody();

Iteratoriterator=collection.iterator();

//遍历集合

while(iterator.hasNext()){

Objecto=iterator.next();

//把遍历出来的对象存储到page范围中(在标签体中使用EL表达式$,就能够获取到集合的对象了!)

this.getJspContext().setAttribute(var,o);

jspFragment.invoke(null);

}

}

}

tld文件和上面是一样的,下面是测试代码

/*list集合*/

Listlist=newArrayList();

list.add("zhongfucneng");

list.add("1");

list.add("2");

list.add("3");

request.setAttribute("list",list);

/*基本数据类型数组*/

int[]ints=newint[]{1,2,3,4,5};

request.setAttribute("ints",ints);

/*对象数组*/

Object[]objects=newObject[]{2,3,4,5,6};

request.setAttribute("objects",objects);

/*map集合*/

Mapmap=newHashMap();

map.put("aa","aa");

map.put("bb","bb");

map.put("cc","cc");

request.setAttribute("map",map);

%>

List集合:

$

基本数据类型数组:

$

对象数组:

$

map集合:

$ = $

效果:

HTML转义标签要开发这个标签就很简单了,只要获取到标签体的内容,再通过经过方法转义下标签体内容,输出给浏览器即可

标签处理器代码:

publicclassDemo1extendsSimpleTagSupport{

@Override

publicvoiddoTag()throwsJspException,IOException{

//获取到标签体的内容再修改

StringWriterstringWriter=newStringWriter();

JspFragmentjspFragment=this.getJspBody();

jspFragment.invoke(stringWriter);

Stringcontent=stringWriter.toString();

//经过filter()转义,该方法在Tomcat可以找到

content=filter(content);

//再把转义后的内容输出给浏览器

this.getJspContext().getOut().write(content);

}

privateStringfilter(Stringmessage){

if(message==null)

return(null);

charcontent[]=newchar[message.length()];

message.getChars(,message.length(),content,);

StringBufferresult=newStringBuffer(content.length+50);

for(inti=;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());

}

}

测试代码

你好啊

你好啊

效果:

if else标签在JSTL中并没有if else的标签,JSTL给予我们的是choose,when,otherwise标签,现在我们模仿choose,when,otherwise开发标签思路:when标签有个test属性,但otherwise怎么判断标签体是执行还是不执行呢?这时就需要choose标签的支持了!choose标签默认定义一个Boolean值为false,。当when标签体被执行了,就把Boolean值变成true,只要Boolean值为false就执行otherwise标签体的内容。看程序就容易理解上面那句话了:

choose标签处理器

publicclassChooseextendsSimpleTagSupport{

privatebooleanflag;

@Override

publicvoiddoTag()throwsJspException,IOException{

this.getJspBody().invoke(null);

}

publicbooleanisFlag(){

returnflag;

}

publicvoidsetFlag(booleanflag){

this.flag=flag;

}

}

When标签处理器

publicclassWhenextendsSimpleTagSupport{

privatebooleantest;

@Override

publicvoiddoTag()throwsJspException,IOException{

Choosechoose=(Choose)this.getParent();

//如果test为true和flag为false,那么执行该标签体

if(test==true&&choose.isFlag()==false){

this.getJspBody().invoke(null);

//修改父标签的flag

choose.setFlag(true);

}

}

publicvoidsetTest(booleantest){

this.test=test;

}

}

OtherWise标签处理器

publicclassOtherWiseextendsSimpleTagSupport{

@Override

publicvoiddoTag()throwsJspException,IOException{

Choosechoose=(Choose)this.getParent();

//如果父标签的flag为false,就执行标签体(如果when标签没执行,flag值就不会被修改!when标签没执行,就应该执行otherwise标签!)

if(choose.isFlag()==false){

getJspBody().invoke(null);

//改父标签的flag为false

choose.setFlag(true);

}

}

}

测试代码:

user为空

user不为空

效果:

DynamicAttribute接口此接口的主要功能是用于完成动态属性的设置!前面我们讲解属性标签的时候,属性都是写多少个,用多少个的。现在如果我希望属性可以动态的增加,只需要在标签处理器类中实现DynamicAttribute接口即可!现在我要开发一个动态加法的标签

标签处理器

publicclassDemo1extendsSimpleTagSupportimplementsDynamicAttributes{

//既然有动态属性和动态的值,那么我们就用一个Map集合存储(1-1对应的关系),做的加法运算,值为Double类型的。

Mapmap=newHashMap();

@Override

publicvoiddoTag()throwsJspException,IOException{

//定义一个sum变量用于计算总值

doublesum=0.0;

//获取到Map集合的数据

Iteratoriterator=map.entrySet().iterator();

while(iterator.hasNext()){

Map.Entryentry=(Map.Entry)iterator.next();

sum+=entry.getValue();

}

//向浏览器输出总和是多少

this.getJspContext().getOut().write(String.valueOf(sum));

}

//对于这个要实现的方法,我们只要关注第2个参数和第3个参数即可

//第二个参数表示的是动态属性的名称,第三个参数表示的是动态属性的值

@Override

publicvoidsetDynamicAttribute(Strings,StringlocalName,Objectvalue)throwsJspException{

//将动态属性的名字和值加到Map集合中

map.put(localName,Double.valueOf(Float.valueOf(value.toString())));

}

}

tld文件,注意要把dynamic-attribute设置为true

dynamicAttribute

tag.Demo1

empty

true

测试代码

效果,double在运算的时候会丢失精度的,现在只是测验下动态属性,这里就不详细说了!

开发自定义函数至于怎么开发自定义函数,在EL表达式的博客中有!如果文章有错的地方欢迎指正,大家互相交流。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180205G0FBLH00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券