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

为何要用到简单标签?

上一篇博客中我已经讲解了传统标签,想要开发自定义标签,大多数状况下都要重写doStartTag(),doAfterBody()和doEndTag()方法,而且还要知道SKIP_BODY,EVAL_BODY等等的变量表明着什么,在什么方法中使用。这样实在是太麻烦了!javascript

所以,为了简化标签开发的复杂度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签的功能。php

通常来讲,实现了SimpeTag接口的标签称之为简单标签html

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)来编写自定义标签
  • 下面咱们就来个快速入门吧
  • 目标:传入字符串格式就能够显示想要的格式日期,对比以前传统标签的,看有什么不一样之处java

  • 标签处理器类:数组

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等变量能够实现以下的功能:浏览器

  1. 控制jsp页面某一部份内容(标签体)是否执行
  2. 控制整个jsp页面是否执行
  3. 控制jsp页面内容重复执行
  4. 修改jsp页面内容输出

  • 简单标签可没有这些变量呀,那它怎么才能实现上面那些功能呢?markdown

  • 在doTag方法中能够抛出javax.servlet.jsp.SkipPageException异常,用于通知WEB容器再也不执行JSP页面中位于结束标记后面的内容,这等效于在传统标签的doEndTag方法中返回Tag.SKIP_PAGE常量的状况,咱们来测试一下,在上面例子的代码中添加app

throw new SkipPageException();
  • 效果:

  • 至于其余的功能下面会讲到

带标签体的简单标签

  • SimpleTagSupport也能够带标签体,可是处理方法和传统标签彻底不一样less

    • 传统标签是这样子的:将标签体的内容经过setBodyContent()注入到BodyContent对象中。
    • 简单标签是这样子的:经过JspFragment对象实现!
  • 咱们来看一下JspFragment对象的源码吧jsp

 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,浏览器就没有输出了


forEach标签

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

  • 首先,我先写一个能够遍历List集合的标签,可能咱们会这样设计
public class Demo2 extends SimpleTagSupport {

    //遍历的是List集合,因而标签的属性就为List
    private List items;

    //遍历出来的对象就用Object存着,由于咱们不知道List集合保存的是什么元素
    private Object var;


    @Override
    public void doTag() throws JspException, IOException {

        //获取到迭代器
        Iterator iterator = items.iterator();

        //遍历集合
        while (iterator.hasNext()) {

            //获取到集合的元素
            var = iterator.next();

            //.....var属性表明的就是集合的元素,如今问题来了,好像在标签体内没法获取到这个对象....
            //作到这里完成不下去了....
        }
    }

    public void setItems(List items) {
        this.items = items;
    }

    public void setVar(Object var) {
        this.var = var;
    }
}

上面的思路是正常的,可是作不下去!咱们换一个思路呗。上面的问题主要是在标签体获取不到被遍历出来的对象

咱们这样作:把var定义成String类型的,若是遍历获得对象了,就设置PageContext的属性,var为关键字,对象为值。在标签体用EL表达式搜索以var为关键字的对象!每遍历出一个对象,就执行一次标签体!

  • 因而就有了如下的代码!
public class Demo1 extends SimpleTagSupport {

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

    //以var为关键字存储到PageContext
    private String var;



    @Override
    public void doTag() throws JspException, IOException {

        //获取到集合的迭代器
        Iterator iterator = items.iterator();

        //获取到表明标签体内容的对象
        JspFragment jspFragment = this.getJspBody();

        //遍历集合
        while (iterator.hasNext()) {
            Object o = iterator.next();

            //把遍历出来的对象存储到page范围中,关键字为标签的属性var(在标签体中使用EL表达式${var},就可以获取到集合的对象了!)
            this.getJspContext().setAttribute(var, o);

            //每设置了一个属性,我就执行标签体
            jspFragment.invoke(null);
        }
    }

    public void setItems(List items) {
        this.items = items;
    }

    public void setVar(String var) {
        this.var = var;
    }
}
  • tld文件以下
<tag>
        <name>forEach</name>
        <tag-class>tag.Demo1</tag-class>
        <body-content>scriptless</body-content>
        <attribute>
            <name>var</name>
            <rtexprvalue>true</rtexprvalue>
            <!--字符串便可,不须要EL表达式的支持-->
            <required>false</required>
        </attribute>

        <attribute>
            <name>items</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
    </tag>
  • 测试的jsp代码以下
<%
        List list = new ArrayList();
        list.add("zhongfucneng");
        list.add("1");
        list.add("2");
        list.add("3");

        request.setAttribute("list",list);
    %>
    <zhongfucheng:forEach items="${list}" var="str">

        ${str}

    </zhongfucheng:forEach>
  • 效果:

上面写的仅仅可以遍历List集合,作一个通用的forEach标签麻烦的是在:不知道传进来的是什么类型的数组、什么类型集合!,须要逐一去判断

  • 咱们来尝试着写一下
//若是items是Collection类型的,就强转为Colletion
        if (items instanceof Collection) {
            collection = (Collection) items;
        }

        //若是itmes是Map类型的,那么就强转为Map,再获取到<Map.Entry<K, V>,这个是Set集合的!
        if (items instanceof Map) {
            Map map = (Map) items;
            collection = (Collection) map.entrySet();
        }

        //对象数组
        if (items instanceof Object[]) {
            Object[] objects = (Object[]) items;
            collection = Arrays.asList(objects);
        }

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

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

  • 测试代码以下:
public static void main(String[] args) {

        int[] ints = new int[]{1, 2, 3};
        Object[] objects = new Object[]{"1", "2", "3"};

        if (objects instanceof Object[]) {
            Collection collection = Arrays.asList(objects);
            System.out.println(collection);
        }
        if (ints instanceof int[]) {

            Collection collection1 = Arrays.asList(ints);
            System.out.println(collection1);
        }
    }
  • 效果:

  • 对于八大基本类型数据咱们就能够这样干
 if (items instanceof int[]) { int[] ints = (int[]) items; collection = new ArrayList(); for (int anInt : ints) { collection.add(anInt); } } //......这里还要写7个 
  • JSTL的forEach标签相似就是这样干的

因为JDK5的新特性,咱们又有另外的解决方案,Class对象可以判断是否为数组类,reflect反射包下Array类

  • 其实,不管Map集合、仍是任何类型的数组、均可以使用Colletion进行遍历!。

  • 标签处理器的代码

public class Demo1 extends SimpleTagSupport {

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

    //每次被遍历的对象存储关键字
    private String var;

    //Colletion
    private Collection collection;

    //在WEB容器设置标签的属性的时候,判断是什么类型的数组和集合
    public void setItems(Object items) {
        this.items = items;

        //若是items是Collection类型的,就强转为Colletion
        if (items instanceof Collection) {
            collection = (Collection) items;
        }

        //若是itmes是Map类型的,那么就强转为Map,再获取到<Map.Entry<K, V>,这个是Set集合的!
        if (items instanceof Map) {
            Map map = (Map) items;
            collection = (Collection) map.entrySet();
        }

        //能够这样解决,Class对象判断是不是一个数组类
        if (items.getClass().isArray()) {

            //建立Collection集合添加数组的元素!
            collection = new ArrayList();

            //再利用reflect包下的Array类获取到该数组类的长度
            int len = Array.getLength(items);

            //遍历并添加到集合中
            for (int i = 0; i < len; i++) {
                collection.add(Array.get(items, i));
            }
        }
    }

    public void setVar(String var) {
        this.var = var;
    }

   @Override
    public void doTag() throws JspException, IOException {

        //获取到表明标签体内容的对象
        JspFragment jspFragment = this.getJspBody();
        Iterator iterator = collection.iterator();


        //遍历集合
        while (iterator.hasNext()) {
            Object o = iterator.next();

            //把遍历出来的对象存储到page范围中(在标签体中使用EL表达式${var},就可以获取到集合的对象了!)
            this.getJspContext().setAttribute(var, o);

            jspFragment.invoke(null);
        }
    }
}
  • tld文件和上面是同样的,下面是测试代码
<% /*list集合*/ List list = new ArrayList(); list.add("zhongfucneng"); list.add("1"); list.add("2"); list.add("3"); request.setAttribute("list",list); /*基本数据类型数组*/ int[] ints = new int[]{1, 2, 3, 4, 5}; request.setAttribute("ints", ints); /*对象数组*/ Object[] objects = new Object[]{2, 3, 4, 5, 6}; request.setAttribute("objects", objects); /*map集合*/ Map map = new HashMap(); map.put("aa", "aa"); map.put("bb", "bb"); map.put("cc", "cc"); request.setAttribute("map",map); %> List集合: <zhongfucheng:forEach items="${list}" var="str"> ${str} </zhongfucheng:forEach> <hr> <hr> 基本数据类型数组: <zhongfucheng:forEach items="${ints}" var="i"> ${i} </zhongfucheng:forEach> <hr> <hr> 对象数组: <zhongfucheng:forEach items="${objects}" var="o"> ${o} </zhongfucheng:forEach> <hr> <hr> map集合: <zhongfucheng:forEach items="${map}" var="me"> ${me.key} = ${me.value} </zhongfucheng:forEach> 
  • 效果:


HTML转义标签

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

  • 标签处理器代码:
public class Demo1 extends SimpleTagSupport {



   @Override
    public void doTag() throws JspException, IOException {

       //获取到标签体的内容再修改
       StringWriter stringWriter = new StringWriter();
       JspFragment jspFragment = this.getJspBody();
       jspFragment.invoke(stringWriter);
       String content = stringWriter.toString();

       //通过filter()转义,该方法在Tomcat能够找到
       content = filter(content);

       //再把转义后的内容输出给浏览器
       this.getJspContext().getOut().write(content);


    }

    private String filter(String message) {

        if (message == null)
            return (null);

        char content[] = new char[message.length()];
        message.getChars(0, message.length(), content, 0);
        StringBuffer result = new StringBuffer(content.length + 50);
        for (int i = 0; i < content.length; i++) {
            switch (content[i]) {
                case '<':
                    result.append("&lt;");
                    break;
                case '>':
                    result.append("&gt;");
                    break;
                case '&':
                    result.append("&amp;");
                    break;
                case '"':
                    result.append("&quot;");
                    break;
                default:
                    result.append(content[i]);
            }
        }

        return (result.toString());
    }
}
  • 测试代码
<zhongfucheng:filter><a href="2.jsp">你好啊</a> </zhongfucheng:filter>

    <br>
    <a href="2.jsp">你好啊
  • 效果:


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标签处理器
public class Choose extends SimpleTagSupport {

        private boolean flag;

        @Override
        public void doTag() throws JspException, IOException {

            this.getJspBody().invoke(null);
        }

        public boolean isFlag() {
            return flag;
        }

        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    }
  • When标签处理器
public class When extends SimpleTagSupport {

        private boolean test ;

        @Override
        public void doTag() throws JspException, IOException {


            Choose choose = (Choose) this.getParent();

            //若是test为true和flag为false,那么执行该标签体
            if (test == true && choose.isFlag() == false) {

                this.getJspBody().invoke(null);

                //修改父标签的flag
                choose.setFlag(true);
            }

        }
        public void setTest(boolean test) {
            this.test = test;
        }
    }
  • OtherWise标签处理器
 public class OtherWise extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { Choose choose = (Choose) this.getParent(); //若是父标签的flag为false,就执行标签体(若是when标签没执行,flag值就不会被修改!when标签没执行,就应该执行otherwise标签!) if (choose.isFlag() == false) { getJspBody().invoke(null); //改父标签的flag为false choose.setFlag(true); } } } 
  • 测试代码:
<zhongfucheng:choose>
        <zhongfucheng:when test="${user!=null}">
            user为空
        </zhongfucheng:when>

        <zhongfucheng:otherwise>
            user不为空
        </zhongfucheng:otherwise>

    </zhongfucheng:choose>
  • 效果:


DynamicAttribute接口

此接口的主要功能是用于完成动态属性的设置!前面咱们讲解属性标签的时候,属性都是写多少个,用多少个的。如今若是我但愿属性能够动态的增长,只须要在标签处理器类中实现DynamicAttribute接口便可!

如今我要开发一个动态加法的标签

  • 标签处理器
public class Demo1 extends SimpleTagSupport implements DynamicAttributes {

        //既然有动态属性和动态的值,那么咱们就用一个Map集合存储(1-1对应的关系),作的加法运算,值为Double类型的。
        Map<String, Double> map = new HashMap<>();

        @Override
        public void doTag() throws JspException, IOException {

            //定义一个sum变量用于计算总值
            double sum = 0.0;

            //获取到Map集合的数据
            Iterator iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Double> entry = (Map.Entry<String, Double>) iterator.next();

                sum += entry.getValue();
            }

            //向浏览器输出总和是多少
            this.getJspContext().getOut().write(String.valueOf(sum));
        }


        //对于这个要实现的方法,咱们只要关注第2个参数和第3个参数便可
        //第二个参数表示的是动态属性的名称,第三个参数表示的是动态属性的值
        @Override
        public void setDynamicAttribute(String s, String localName, Object value) throws JspException {

            //将动态属性的名字和值加到Map集合中
            map.put(localName, Double.valueOf(Float.valueOf(value.toString())));
        }
    }
  • tld文件,注意要把dynamic-attribute设置为true
 <tag> <name>dynamicAttribute</name> <tag-class> tag.Demo1</tag-class> <body-content> empty</body-content> <!--这个必需要设置为true--> <dynamic-attributes>true</dynamic-attributes> </tag> 
  • 测试代码
<zhongfucheng:dynamicAttribute num="1.1" num2="2.2" num3="1"/>
  • 效果,double在运算的时候会丢失精度的,如今只是测验下动态属性,这里就不详细说了!

开发自定义函数

至于怎么开发自定义函数,在EL表达式的博客中有

相关文章
相关标签/搜索