今天来看一下自定义标签的内容,自定义标签是JavaWeb的一部分非常重要的核心功能,我们之前就说过,JSP规范说的很清楚,就是Jsp页面中禁止编写一行Java代码,就是最好不要有Java脚本片段,下面就来看一下自定义标签的简介:
自定义标签主要用于移除Jsp页面中的java代码。
移除jsp页面中的java代码,只需要完成两个步骤:
编写一个实现Tag接口的Java类,并覆盖doStartTag方法,把jsp页面中的java代码写到doStartTag方法中。
编写标签库描述符(tld)文件,在tld文件中对自定义标签进行描述。
完成以上操作,即可在JSP页面中导入和使用自定义标签。
快速入门:使用自定义标签输出客户机IP
查看tag接口api文档,分析自定义标签的执行流程。
下面来看一下一个简单的Demo使用自定义标签打印客户机的IP地址
首先我们自定义标签类:ViewIpTag
package com.weijia.traditionaltag; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.TagSupport; /** * 自定义标签,然后将这个标签映射到这个类:mytag:viewIP * 记得将自定义的标签绑定到一个url上面,这个url一般是公司的网址 * */ public class ViewIpTag extends TagSupport{ private static final long serialVersionUID = 1L; @Override public int doStartTag() throws JspException { //内置一个pageContext对象,我们之前说到pageContext对象,它里面是封装了9个隐式对象 HttpServletRequest request = (HttpServletRequest)this.pageContext.getRequest(); JspWriter out = this.pageContext.getOut(); String ip = request.getRemoteAddr(); try { out.print(ip); } catch (IOException e) { throw new RuntimeException(e); } return super.doStartTag(); } }
<?xml version="1.0" encoding="UTF-8" ?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd" version="2.0"> <description>JSTL 1.1 core library</description> <display-name>JSTL core</display-name> <tlib-version>1.1</tlib-version> <short-name>weijia</short-name> <uri>http://www.weijia.cn/mytag</uri> <!-- 显示IP地址 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>viewIP</name> <tag-class>com.weijia.traditionaltag.ViewIpTag</tag-class> <body-content>empty</body-content> </tag> </taglib>这里我们将就自定义的标签类就注册好了,下面解释一下这些字段的含义:
首先看一下:
<short-name>这个标签是指定我们定义标签的简称,这个作用不大
<uri>这个标签是给这个标签文件指定一个访问路径,这个路径我们在Jsp页面中引入这个标签的时候需要用到
<tag-class>这个标签就是指定我们自定义的标签类的全称
<body-content>这个标签表明自定义标签是否有标签体内容(empty:没有,JSP:有)
我们注册之后标签类了,下面就在Jsp页面中进行使用了,这时候就要用到我们之前说到的Jsp的指令中的taglib了,格式如下:
<%@ taglib uri="http://www.weijia.cn/mytag" prefix="mytag" %>这个就将我们定义的标签引入到Jsp页面中了,其中我们uri属性的值就是我们在标签定义文件mytag.tld中指定的那个uri那个标签值,当然这里的uri也可以直接指定mytag.tld文件的路径即:/WEB-INF/mytag.tld 也是可以的,其实我们查看翻译之后的Jsp代码可以看到,不管用那种方式,他其实加载的时候都是去找真是路径中文件:
其中prefix属性的值是标签前缀名,这个名称就是我们在Jsp页面中使用的标签前缀,这个值一般和tld文件的文件名是保持一致的
下面就是在Jsp中使用标签:
客户机的IP地址是:<mytag:viewIP/>这样就是打印了客户机的IP地址,这里我们在Jsp页面中就没有Java代码了
上面我们介绍了一个简单的例子,下面我们来详细看一下这个自定义标签的执行原理:
JSP引擎将遇到自定义标签时,首先创建标签处理器类的实例对象,然后按照JSP规范定义的通信规则依次调用它的方法。
1、public void setPageContext(PageContext pc), JSP引擎实例化标签处理器后,将调用setPageContext方法将JSP页面的pageContext对象传递给标签处理器,标签处理器以后可以通过这个pageContext对象与JSP页面进行通信。
2、public void setParent(Tag t),setPageContext方法执行完后,WEB容器接着调用的setParent方法将当前标签的父标签传递给当前标签处理器,如果当前标签没有父标签,则传递给setParent方法的参数值为null。
3、public int doStartTag(),调用了setPageContext方法和setParent方法之后,WEB容器执行到自定义标签的开始标记时,就会调用标签处理器的doStartTag方法。
4、public int doEndTag(),WEB容器执行完自定义标签的标签体后,就会接着去执行自定义标签的结束标记,此时,WEB容器会去调用标签处理器的doEndTag方法。
5、public void release(),通常WEB容器执行完自定义标签后,标签处理器会驻留在内存中,为其它请求服务器,直至停止web应用时,web容器才会调用release方法。
我可以查看我们上面的例子翻译后的Jsp代码:
out.write("<body> \r\n"); out.write("\t<!-- 显示客户机的IP地址 -->\r\n"); out.write("\t客户机的IP地址是:"); if (_jspx_meth_mytag_005fviewIP_005f0(_jspx_page_context)) return; out.write("\r\n"); out.write("\t\r\n");
再来看一下那个if中的方法的代码:
private boolean _jspx_meth_mytag_005fviewIP_005f0(PageContext _jspx_page_context) throws Throwable { PageContext pageContext = _jspx_page_context; JspWriter out = _jspx_page_context.getOut(); // mytag:viewIP com.weijia.traditionaltag.ViewIpTag _jspx_th_mytag_005fviewIP_005f0 = (com.weijia.traditionaltag.ViewIpTag) _005fjspx_005ftagPoo l_005fmytag_005fviewIP_005fnobody.get(com.weijia.traditionaltag.ViewIpTag.class); _jspx_th_mytag_005fviewIP_005f0.setPageContext(_jspx_page_context); _jspx_th_mytag_005fviewIP_005f0.setParent(null); int _jspx_eval_mytag_005fviewIP_005f0 = _jspx_th_mytag_005fviewIP_005f0.doStartTag(); if (_jspx_th_mytag_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) { _005fjspx_005ftagPool_005fmytag_005fviewIP_005fnobody.reuse(_jspx_th_mytag_005fviewIP_005f0); return true; } _005fjspx_005ftagPool_005fmytag_005fviewIP_005fnobody.reuse(_jspx_th_mytag_005fviewIP_005f0); return false; }我们可以看到,首先这个方法接收的是一个pageContext变量对象,这个和我们之前说的一样,自定义标签类中有一个pageContext变量对象就可以操作其他对象了,下面来看一下那个方法的代码,首先他会去加载那个标签类,同时注意到首先是执行setPageContext()方法的,将pageContext变量传递到标签类中,然后看setParent()方法传递的是null,因为我们打印IP的标签没有父标签的,接下来执行doStartTag()方法,然后再执行doEndTag()方法,这里我们看到是做个判断,如果doEndTag方法返回的值是Tag.SKIP_PAGE的话,就是说余下的jsp页面不执行了,所以返回一个true,那么我们看到上面的if判断代码中,如果这个方法返回true的话,直接return,下面的代码就不执行了。大体流程就是这样的。
下面我们用自定义标签来实现一些特定需求的功能:
1、不执行标签体内容
自定义标签类:
package com.weijia.traditionaltag; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; /** * 是否输出标签体内容 * @author weijiang204321 * */ public class TagDemo1 extends TagSupport{ @Override public int doStartTag() throws JspException { return TagSupport.EVAL_BODY_INCLUDE;//输出标签体内容 //return TagSupport.SKIP_BODY;//不输出标签体内容 } }
下面我们再来注册这个标签类:
<!-- 是否显示标签体 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo1</name> <tag-class>com.weijia.traditionaltag.TagDemo1</tag-class> <body-content>JSP</body-content> </tag>
在Jsp页面中使用:
<!-- 不执行标签体 --> <simpletag:demo1> aaaa </simpletag:demo1>
自定义标签类:
package com.weijia.traditionaltag; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; /** * 控制整个JSP是否输出 * @author weijiang204321 * */ public class TagDemo2 extends TagSupport{ @Override public int doStartTag() throws JspException { return super.doStartTag(); } @Override public int doEndTag() throws JspException { return TagSupport.EVAL_PAGE; //return TagSupport.SKIP_PAGE;不执行余下的jsp内容 } }当doEndTag方法返回的是TagSupport.EVAL_PAGE常量的话就执行jsp余下的内容,如果返回的是TagSupport.SKIP_PAGE常量的话就不执行jsp余下的内容
在tld文件中注册这个自定义标签类:
<!-- 控制是否显示jsp页面 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo2</name> <tag-class>com.weijia.traditionaltag.TagDemo2</tag-class> <body-content>empty</body-content> </tag>
<!-- 不执行余下的页面内容 --> <simpletag:demo2/>这样使用之后,在这个标签之后的内容就不会执行了,我们在上面分析源代码的时候已经解析过了。页面都不会含有余下的内容了,如果我们将这个标签放在页面的第一行,那么这个页面就是一片空白,我们在浏览器中查看页面的源代码,也是发现一片空白的,因为out对象没有进行print了
3、重复执行标签体内容
自定义标签体类:
package com.weijia.traditionaltag; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; /** * 控制标签体重复执行 * @author weijiang204321 * */ public class TagDemo3 extends TagSupport{ private int count = 5; @Override public int doStartTag() throws JspException { return TagSupport.EVAL_BODY_INCLUDE; } @Override public int doAfterBody() throws JspException { count--; if(count > 0){ return TagSupport.EVAL_BODY_AGAIN;//执行完之后接着执行doAfterBody()方法 }else{ return TagSupport.SKIP_BODY; } } @Override public int doEndTag() throws JspException { return TagSupport.SKIP_BODY; } }
注册自定义标签类:
<!-- 控制标签体重复输出 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo3</name> <tag-class>com.weijia.traditionaltag.TagDemo3</tag-class> <body-content>JSP</body-content> </tag>
<!-- 重复执行标签体内容 --> <simpletag:demo3> aaaa </simpletag:demo3>这时候在页面中就会输出5个aaaa
4、修改标签体内容
自定义标签类:
package com.weijia.traditionaltag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.BodyContent; import javax.servlet.jsp.tagext.BodyTagSupport; /** * 修改标签体内容 * @author weijiang204321 * */ public class TagDemo4 extends BodyTagSupport{ @Override public int doEndTag() throws JspException { BodyContent bc = this.getBodyContent();//获取标签体内容对象 String content = bc.getString(); content = content.toUpperCase();//将标签体内容转成大写 try { this.pageContext.getOut().write(content);//在将转化之后的内容输出到浏览器中 } catch (IOException e) { throw new RuntimeException(e); } return BodyTagSupport.EVAL_BODY_INCLUDE; } @Override public int doStartTag() throws JspException { return BodyTagSupport.EVAL_BODY_BUFFERED;//这里返回缓存标签体内容常量 } }
注册我们的自定义标签类:
<!-- 修改标签体内容 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo4</name> <tag-class>com.weijia.traditionaltag.TagDemo4</tag-class> <body-content>JSP</body-content> </tag>
<!-- 修改标签体内容 --> <simpletag:demo4> bbbb </simpletag:demo4>这时候在浏览器中输出的是:BBBB
上面说到的是Jsp2.0以前的自定义标签的方法,从Jsp2.0以后,我们就开始使用了简单标签类SimpleTagSupport,因为我们可以看到Jsp2.0之前的是传统标签类的话,要想实现不同的功能,还需要继承不同的类,比如:TagSupport,BodyTagSupport,这样会增加开发成本,所以Jsp2.0之后引入了简单标签类SimpleTagSupport了,那么下面我们先来看一下简单标签的执行流程:
由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广, SUN公司为降低标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签的功能。实现SimpleTag接口的标签通常称为简单标签。简单标签共定义了5个方法:
setJspContext方法
setParent和getParent方法
setJspBody方法
doTag方法
下面来看一下这些方法的解释
setJspContext方法
用于把JSP页面的pageContext对象传递给标签处理器对象
setParent方法
用于把父标签处理器对象传递给当前标签处理器对象
getParent方法
用于获得当前标签的父标签处理器对象
setJspBody方法
用于把代表标签体的JspFragment对象传递给标签处理器对象
doTag方法
用于完成所有的标签逻辑,包括输出、迭代、修改标签体内容等。在doTag方法中可以抛出javax.servlet.jsp.SkipPageException异常,用于通知WEB容器不再执行JSP页面中位于结束标记后面的内容,这等效于在传统标签的doEndTag方法中返回Tag.SKIP_PAGE常量的情况。
当web容器开始执行标签时,会调用如下方法完成标签的初始化
WEB容器调用标签处理器对象的setJspContext方法,将代表JSP页面的pageContext对象传递给标签处理器对象。
WEB容器调用标签处理器对象的setParent方法,将父标签处理器对象传递给这个标签处理器对象。注意,只有在标签存在父标签的情况下,WEB容器才会调用这个方法。
如果调用标签时设置了属性,容器将调用每个属性对应的setter方法把属性值传递给标签处理器对象。如果标签的属性值是EL表达式或脚本表达式,则WEB容器首先计算表达式的值,然后把值传递给标签处理器对象。
如果简单标签有标签体,容器将调用setJspBody方法把代表标签体的JspFragment对象传递进来。
执行标签时:
容器调用标签处理器的doTag()方法,开发人员在方法体内通过操作JspFragment对象,就可以实现是否执行、迭代、修改标签体的目的。
javax.servlet.jsp.tagext.JspFragment类是在JSP2.0中定义的,它的实例对象代表JSP页面中的一段符合JSP语法规范的JSP片段,这段JSP片段中不能包含JSP脚本元素。
WEB容器在处理简单标签的标签体时,会把标签体内容用一个JspFragment对象表示,并调用标签处理器对象的setJspBody方法把JspFragment对象传递给标签处理器对象。JspFragment类中只定义了两个方法,如下所示:
getJspContext方法
用于返回代表调用页面的JspContext对象.
public abstract void invoke(java.io.Writer out)
用于执行JspFragment对象所代表的JSP代码片段
参数out用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果传递给参数out的值为null,则将执行结果写入到JspContext.getOut()方法返回的输出流对象中。(简而言之,可以理解为写给浏览器)
JspFragment.invoke方法可以说是JspFragment最重要的方法,利用这个方法可以控制是否执行和输出标签体的内容、是否迭代执行标签体的内容或对标签体的执行结果进行修改后再输出。例如:
在标签处理器中如果没有调用JspFragment.invoke方法,其结果就相当于忽略标签体内容;
在标签处理器中重复调用JspFragment.invoke方法,则标签体内容将会被重复执行;
若想在标签处理器中修改标签体内容,只需在调用invoke方法时指定一个可取出结果数据的输出流对象(例如StringWriter),让标签体的执行结果输出到该输出流对象中,然后从该输出流对象中取出数据进行修改后再输出到目标设备,即可达到修改标签体的目的。
下面我们在来使用简单标签来实现上面的四个案例:
1、是否输出标签体内容
自定义标签类:
package com.weijia.sampletag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; /** * 控制标签体是否执行 * @author weijiang204321 * */ public class SimpleTagDemo1 extends SimpleTagSupport{ @Override public void doTag() throws JspException, IOException { JspFragment jf = this.getJspBody(); //相当于jf.invoke(null); jf.invoke(this.getJspContext().getOut()); //这里如果不想输出标签体内容的话,只需要不调用invoke方法即可 } }
注册标签体类:
<!-- 是否显示标签体 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo1</name> <tag-class>com.weijia.simpletag.SimpleTagDemo1</tag-class> <body-content>scriptless</body-content> </tag>这里的注册和传统标签不一样的就是<body-content>标签的值是scriptless而不是JSP了
在JSP页面使用:
<simpletag:demo1> aaa </simpletag:demo1>
自定义标签类:
package com.weijia.sampletag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.SkipPageException; import javax.servlet.jsp.tagext.SimpleTagSupport; /** * 控制不执行余下的jsp内容 * @author weijiang204321 * */ public class SimpleTagDemo2 extends SimpleTagSupport{ @Override public void doTag() throws JspException, IOException { //直接抛出异常就不会执行余下的jsp内容 throw new SkipPageException(); } }我们只要在doTag方法中抛出一个SkipPageException异常就可以实现不执行余下的Jsp内容
注册标签类:
<!-- 控制是否显示jsp页面 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo2</name> <tag-class>com.weijia.simpletag.SimpleTagDemo2</tag-class> <body-content>empty</body-content> </tag>
<simpletag:demo2/>在这个标签之后的jsp页面内容就不会输出了
3、标签体重复执行
自定义标签类:
package com.weijia.sampletag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; /** * 迭代标签体 * @author weijiang204321 * */ public class SimpleTagDemo3 extends SimpleTagSupport{ @Override public void doTag() throws JspException, IOException { JspFragment jf = this.getJspBody(); for(int i=0;i<5;i++){ jf.invoke(null); } } }这里就比传统标签的操作简单了,直接写在for循环中,在循环中调用invoke方法即可
注册标签类:
<!-- 控制标签体重复输出 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo3</name> <tag-class>com.weijia.simpletag.SimpleTagDemo3</tag-class> <body-content>scriptless</body-content> </tag>
<simpletag:demo3> aaaa </simpletag:demo3>
package com.weijia.sampletag; import java.io.IOException; import java.io.StringWriter; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; /** * 修改标签体 * @author weijiang204321 * */ public class SimpleTagDemo3 extends SimpleTagSupport{ @Override public void doTag() throws JspException, IOException { JspFragment jf = this.getJspBody(); StringWriter sw = new StringWriter(); jf.invoke(sw); String content = sw.toString(); content = content.toUpperCase(); this.getJspContext().getOut().write(content); } }我们将StringWriter对象传递到invoke方法中,然后再通过StringWriter对象得到标签体内容,进行操作,然后再通过out对象输出到浏览器中。
注册标签类:
<!-- 修改标签体内容 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo4</name> <tag-class>com.weijia.simpletag.SimpleTagDemo4</tag-class> <body-content>scriptless</body-content> </tag>
<!-- 修改标签体内容 --> <simpletag:demo4> bbbb </simpletag:demo4>
1. JspTag接口
JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属性和方法。JspTag接口有Tag和SimpleTag两个直接子接口,JSP2.0以前的版本中只有Tag接口,所以把实现Tag接口的自定义标签也叫做传统标签,把实现SimpleTag接口的自定义标签叫做简单标签。本书中如果没有特别说明,自定义标签泛指传统标签。
2. Tag接口
图6.5中的Tag接口是所有传统标签的父接口,其中定义了两个重要方法(doStartTag、doEndTag)方法和四个常量(EVAL_BODY_INCLUDE、SKIP_BODY、EVAL_PAGE、SKIP_PAGE),这两个方法和四个常量的作用如下:
(1)WEB容器在解释执行JSP页面的过程中,遇到自定义标签的开始标记就会去调用标签处理器的doStartTag方法,doStartTag方法执行完后可以向WEB容器返回常量EVAL_BODY_INCLUDE或SKIP_BODY。如果doStartTag方法返回EVAL_BODY_INCLUDE,WEB容器就会接着执行自定义标签的标签体;如果doStartTag方法返回SKIP_BODY,WEB容器就会忽略自定义标签的标签体,直接解释执行自定义标签的结束标记。
(2)WEB容器解释执行到自定义标签的结束标记时,就会调用标签处理器的doEndTag方法,doEndTag方法执行完后可以向WEB容器返回常量EVAL_PAGE或SKIP_PAGE。如果doEndTag方法返回常量EVAL_PAGE,WEB容器就会接着执行JSP页面中位于结束标记后面的JSP代码;如果doEndTag方法返回SKIP_PAGE,WEB容器就会忽略JSP页面中位于结束标记后面的所有内容。
从doStartTag和doEndTag方法的作用和返回值的作用可以看出,开发自定义标签时可以在doStartTag方法和doEndTag方法体内编写合适的Java程序代码来实现具体的功能,通过控制doStartTag方法和doEndTag方法的返回值,还可以告诉WEB容器是否执行自定义标签中的标签体内容和JSP页面中位于自定义标签的结束标记后面的内容。
2. IterationTag接口
IterationTag接口继承了Tag接口,并在Tag接口的基础上增加了一个doAfterBody方法和一个EVAL_BODY_AGAIN常量。实现IterationTag接口的标签除了可以完成Tag接口所能完成的功能外,还能够通知WEB容器是否重复执行标签体内容。对于实现了IterationTag接口的自定义标签,WEB容器在执行完自定义标签的标签体后,将调用标签处理器的doAfterBody方法,doAfterBody方法可以向WEB容器返回常量EVAL_BODY_AGAIN或SKIP_BODY。如果doAfterBody方法返回EVAL_BODY_AGAIN,WEB容器就会把标签体内容再重复执行一次,执行完后接着再调用doAfterBody方法,如此往复,直到doAfterBody方法返回常量SKIP_BODY,WEB容器才会开始处理标签的结束标记和调用doEndTag方法。
可见,开发自定义标签时,可以通过控制doAfterBody方法的返回值来告诉WEB容器是否重复执行标签体内容,从而达到循环处理标签体内容的效果。例如,可以通过一个实现IterationTag接口的标签来迭代输出一个集合中的所有元素,在标签体部分指定元素的输出格式。
在JSP API中也提供了IterationTag接口的默认实现类TagSupport,读者在编写自定义标签的标签处理器类时,可以继承和扩展TagSupport类,这相比实现IterationTag接口将简化开发工作。
3. BodyTag接口
BodyTag接口继承了IterationTag接口,并在IterationTag接口的基础上增加了两个方法(setBodyContent、doInitBody)和一个EVAL_BODY_BUFFERED常量。实现BodyTag接口的标签除了可以完成IterationTag接口所能完成的功能,还可以对标签体内容进行修改。对于实现了BodyTag接口的自定义标签,标签处理器的doStartTag方法不仅可以返回前面讲解的常量EVAL_BODY_INCLUDE或SKIP_BODY,还可以返回常量EVAL_BODY_BUFFERED。如果doStartTag方法返回EVAL_BODY_BUFFERED,WEB容器就会创建一个专用于捕获标签体运行结果的BodyContent对象,然后调用标签处理器的setBodyContent方法将BodyContent对象的引用传递给标签处理器,WEB容器接着将标签体的执行结果写入到BodyContent对象中。在标签处理器的后续事件方法中,可以通过先前保存的BodyContent对象的引用来获取标签体的执行结果,然后调用BodyContent对象特有的方法对BodyContent对象中的内容(即标签体的执行结果)进行修改和控制其输出。
在JSP API中也提供了BodyTag接口的实现类BodyTagSupport,读者在编写能够修改标签体内容的自定义标签的标签处理器类时,可以继承和扩展BodyTagSupport类,这相比实现BodyTag接口将简化开发工作。
4. SimpleTag接口
SimpleTag接口是JSP2.0中新增的一个标签接口。由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广,因此,SUN公司为降低标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口。SimpleTag接口与传统标签接口最大的区别在于,SimpleTag接口只定义了一个用于处理标签逻辑的doTag方法,该方法在WEB容器执行自定义标签时调用,并且只被调用一次。那些使用传统标签接口所完成的功能,例如是否执行标签体、迭代标签体、对标签体内容进行修改等功能都可以在doTag方法中完成。关于SimpleTag接口的详细介绍本书将在第7章详细讲解。
在JSP API中也提供了SimpleTag接口的实现类SimpleTagSupport,读者在编写简单标签时,可以继承和扩展SimpleTagSupport类,这相比实现SimpleTag接口将简化开发工作。
为方便读者日后查询传统标签接口中的各个方法可以返回的返回值,笔者在表6.1列举了Tag接口、IterationTag接口和BodyTag接口中的主要方法及它们分别可以返回的返回值的说明。
下面我们来看一下如何开发一个具有属性的自定义标签的内容:
要想让一个自定义标签具有属性,通常需要完成两个任务:
在标签处理器中编写每个属性对应的setter方法
在TLD文件中描术标签的属性
为自定义标签定义属性时,每个属性都必须按照JavaBean的属性命名方式,在标签处理器中定义属性名对应的setter方法,用来接收JSP页面调用自定义标签时传递进来的属性值。 例如属性url,在标签处理器类中就要定义相应的setUrl(String url)方法。
在标签处理器中定义相应的set方法后,JSP引擎在解析执行开始标签前,也就是调用doStartTag方法前,会调用set属性方法,为标签设置属性。
在TLD文件中的描述规格是为:
<tag>元素的<attribute>子元素用于描述自定义
标签的一个属性,自定义标签所具有的每个属性
都要对应一个<attribute>元素 。
<attribute>
<description>description</description>
<name>aaaa</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>ObjectType</type>
</attribute>
其中的各个属性值的含义如下:
那么下面就来看一个实例,通过一个属性值来控制标签体的内容输出的次数:
自定义标签类:
package com.weijia.propertytag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; public class PropertyTag extends SimpleTagSupport{ private int count = 0; public void setCount(int count){ this.count = count; } @Override public void doTag() throws JspException, IOException { JspFragment jf = this.getJspBody(); for(int i=0;i<count;i++){ jf.invoke(null); } } }这里需要定义一个变量来记录执行的次数,同时还需要提供set方法
注册这个标签:
<tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo</name> <tag-class>com.weijia.propertytag.PropertyTag</tag-class> <body-content>scriptless</body-content> <attribute> <description> Name of the exported scoped variable for the exception thrown from a nested action. The type of the scoped variable is the type of the exception thrown. </description> <name>count</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>我们这里设置这个属性的名称是count,而且这个属性在标签中是必须设置的,同时这个标签可以使用表达式
在Jsp页面中使用:
<propertytag:demo count="9"> aaaaa </propertytag:demo>在页面中输出9次aaaaa
虽然我们这里看到了输出的很简单,设置也很简单,但是这里面还是有很多内容的
首先来看一下,我们在Jsp页面中输入的是字符串,但是我们定义的count是int类型,没有报错,所以这里他做了类型转换,当然这个不是能够转换所有的类型的,只能转化8中基本类型,比如我们定义了一个属性是Date类型的,当我们在Jsp页面中传递"1990-08-01"这样就会报错的,当然我们可以使用脚本表达式进行属性的赋值是可以的,比如:
<propertytag:demo count="<%=new Date()%>"> aaaaa </propertytag:demo>
在来看一下,他是怎么定位到属性count的,这个其实在学习Java基础知识的时候就说过,在学习JavaBean的相关知识的时候,我们知道一个Bean对象的属性的概念,比如这里我们定义了一个count变量,同时设置了他的set方法,那么这个count就是一个属性,但是属性的概念不是通过变量名来定义的,而是通过set方法来定义的,比如我们这里可以将count变量名改成counts,但是setCount方法名不变,我们运行程序,仍然不会报错的,但是我们将setCount方法名改成setCounts的时候,运行程序就报错了,原因也很好理解,他在进行变量count进行设置值的时候,会通过set方法来进行设置,这时候就会通过setXXX来找到相对应的set方法,从而能够对每个变量的值设置正确。这个相关内容其实我们在之前介绍<jsp:setProperty>标签的时候讲到过,这个技术在JavaWeb中很常用的,专门用来操作Bean对象的(内省技术BeanUtils)
介绍完自定义标签的属性的相关知识后,接下来我们就来看看JSTL给我们提供的标签库,JSTL标签库可以分为以下几种:
1.核心标签库
2.国际化标签
3.数据库标签
4.XML标签
5.JSTL函数(EL函数)
现在用到最多的就是核心标签库和JSTL函数库了,其他的三种标签不是很常用(几乎抛弃),所以这里就不做太多的介绍。
下面就先来看一下JSTL的核心标签库了,我们在使用JSTL标签库的时候需要导入两个jar:jstl.jar和standard.jar
我们在导入包之后我们可以查看他的tld标签描述文档的:
我们看到他的核心库是c.tld,函数库是fn.tld,这样我们就可以通过这些标签描述文档中查找到有哪些标签可以使用,以及使用的方法
我们看到了c.tld标签说明文件的uri是http://java.sun.com/jsp/jstl/core,所以我们如果要使用这个标签的话就只要在jsp中引入即可:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>前期工作搞定了,下面就来详细看一下每个标签的具体使用方法:
1、c:out标签的使用:
<c:out> 标签用于输出一段文本内容到pageContext对象当前保存的“out”对象中
用法:
<!-- c:out标签 --> <!-- 输出给浏览器,但是没必要这样输出是没意义的,可以直接输出的aaaaaa --> <c:out value="aaaa"></c:out> <!-- 转义之后输出/默认值,这样c:out标签才有意义--> <c:out value="<a href=''>点点</a>" default="aaa" escapeXml="true"></c:out> <% request.setAttribute("data","xxx"); %> <c:out value="${data}" default="aaa"></c:out>这个标签很简单的,就是就是向浏览器中直接输出内容的,那么这个和使用EL表达式输出有什么区别呢,他有什么特别的好处呢?
他的好处就在于default和escapeXml这两个属性的使用,default属性可以设置输出的默认值,我们知道EL表达式在各个域中如果找不到属性值就会输出空字符串,但是我们通过这个属性就可以设置当从所有的域中找不到相应的属性值,就会输出默认值,同时还有一个escapeXml这个属性值,这个属性进行输出内容进行html转义,我们之前都是通过一个方法进行转义的:
private String htmlFilter(String message){ if(message == null){ return null; } char[] content = new char[message.length()]; message.getChars(0, message.length(), content, 0); StringBuffer result = new StringBuffer(message.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(); }只要设置这个属性值为true的话,我们就不需要手动的进行转义了,所以说这个标签还是有他特定的功能的,可不能忘记他呀!
2、c:set标签
<c:set>标签用于把某一个对象存在指定的域范围内,或者设置Web域中的java.util.Map类型的属性对象或JavaBean类型的属性对象的属性
用法:
<!-- c:set标签 --> <!-- 向page域中存入到xxx --> <c:set var="data" value="xxx" scope="page"/> ${data} <!-- 向map中存入数据 --> <% Map map = new HashMap(); request.setAttribute("map",map); %> <c:set property="name" value="uuu" target="${map}"/> ${map.name} <!-- 向javabean中存入数据 --> <% Person p = new Person(); request.setAttribute("p",p); %> <c:set property="name" value="uuu" target="${p}"/> ${p.name}这个标签可以指定在四个域中设置属性值,同时设置的对象不仅只有基本类型,还可以设置对象类型,集合类型
3、c:remove标签
这个标签可以在四个域中删除指定的属性
其语法格式如下:
<c:remove var="varName"
[scope="{page|request|session|application}"] />
用法:
<!-- c:remove标签 --> <!-- 删除属性 --> <% request.setAttribute("data","xxx"); %> <c:remove var="data" scope="request"/>
<c:catch>标签用于捕获嵌套在标签体中的内容抛出的异常,
其语法格式如下:
<c:catch [var="varName"]>nested actions</c:catch>
var属性用于标识<c:catch>标签捕获的异常对象,它将保存在page这个Web域中
用法:
<!-- c:catch异常捕获 --> <!--var是存入异常对象的关键字 --> <c:catch var="myex"> <% int x = 1/0; %> </c:catch> <!-- 异常对象必须要有message属性 --> ${myex.message}
这个标签是控制标签内容的输出
用法:
<!-- c:if --> <!-- 将判断结果以aaa为关键字存入到域中 --> <% request.setAttribute("user",null); %> ${user} <c:if var="aaa" test="${user == null}",scope="page"/> ${aaa}同时可以将判断条件的值使用变量存起来
6、c:choose/c:when/c:other标签
这三个标签是一起使用的,实现效果和if...else是一样的
用法:
<!-- c:choose标签 --> <c:choose> <c:when test="${true}"> aaaa </c:when> <c:otherwise> bbb </c:otherwise> </c:choose>
这个标签是用来迭代数据的,之前用过这个标签,但是他还有很多强大的功能:
用法:
<!-- c:forEach --> <% List list = new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); list.add("ddd"); request.setAttribute("list",list); %> <c:forEach var="str" items="${list}"> ${str} </c:forEach> <!-- 设置步长,分页功能 --> <c:forEach var="num" items="${list}" begin="1" end="9" step="1"> ${num} </c:forEach> <!-- 记录迭代变量的值 --> <c:forEach var="str" items="${list}" varStatus="status"> ${status.count} </c:forEach>
他还有一些属性可以设置迭代的开始位置和结束位置以及迭代的步长信息
同时还有一个属性varStatus可以记录当前迭代信息,他保存的是一个对象,但是这个对象有以下的属性值:
8、c:param标签
这个标签是设置参数值的,这个标签是不能单独使用的,他是结合c:url或者c:redirect标签使用
9、c:url标签
这个标签可以实现url重写(在介绍Session的时候)和url编码
用法:
<!-- c:url标签 --> <!-- url重写 --> <c:url var="url" value="JspDemo/1.jsp"/> <a href='${url}'>购买</a> </c:url> <!-- url标签直接输出url --> <a href='<c:url value="/1.jsp"'>点点</a> <!-- c:url构建参数(自动url编码) --> <c:url var="index" value="/1.jsp"> <c:param name="name" value="中国"/> </c:url>
这个标签是用来实现重定向的
用法:
<c:redirect url="JspDemo/1.jsp"> <c:param name="name" value="jiangwei"></c:param> </c:redirect>我们在之前介绍的jsp标签中只有<jsp:forword>转发标签,而没有重定向的标签,那么这个就有重定向的标签了。
以上我们介绍了JSTL中的核心标签库的相关知识,下面再来看一下JSTL的函数库,其实这部分内容在我们之前的EL表达式一篇文章中的最后部分作了详细讲解:http://blog.csdn.net/jiangwei0910410003/article/details/23748131
下面还有一个重要的内容就是我们要通过我们上面学习到的自定义标签的知识来开发一套类似于JSTL的标签库,并将其进行打包,给其他项目使用,这里我们就是用简单标签了,而不是用传统标签。
1、开发if标签
package com.weijia.iftag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.SimpleTagSupport; public class IfTag extends SimpleTagSupport{ private boolean test; public void setTest(boolean test){ this.test = test; } @Override public void doTag() throws JspException, IOException { if(test){ this.getJspBody().invoke(null); } } }我们定义一个boolean类型的test变量来保存判断值,然后通过这个判断值来控制是否输出标签体内容
2、开发choose/when/otherwise标签,在开发这套标签的时候,我们会发现遇到一个难处就是多个标签之间需要进行通信,比如说一个标签执行了标签体内容,那么其他标签体的内容就不能执行了,所以这里需要给多个标签体外面在套一个父标签(这也是一个父标签开发的案例),通过那么每个子标签可以通过父标签中的一个变量来判断是否执行自己的标签体,原理就是这样的,下面是实现类,其中ChooseTag是父标签了,WhenTag和OtherWiseTag是子标签,同时给WhenTag标签定义一个boolean属性来接收外界的判断条件
ChooseTag:
package com.weijia.choosetag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.SimpleTagSupport; public class ChooseTag extends SimpleTagSupport{ private boolean isDo; public boolean isDo() { return isDo; } public void setDo(boolean isDo) { this.isDo = isDo; } @Override public void doTag() throws JspException, IOException { this.getJspBody().invoke(null); } }
package com.weijia.choosetag; 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 { //获取到父标签 ChooseTag parentTag = (ChooseTag)this.getParent(); if(test && !parentTag.isDo()){ this.getJspBody().invoke(null); parentTag.setDo(true); } } }
package com.weijia.choosetag; 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 parentTag = (ChooseTag)this.getParent(); if(parentTag.isDo()){ this.getJspBody().invoke(null); } } }
同时需要在tld文件中对这三个标签类进行描述:
<tag> <name>choose</name> <tag-class>com.weijia.choosetag.ChooseTag</tag-class> <body-content>scriptless</body-content> </tag> <tag> <name>when</name> <tag-class>com.weijia.choosetag.WhenTag</tag-class> <body-content>scriptless</body-content> <attribute> <name>test</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> <tag> <name>otherwise</name> <tag-class>com.weijia.choosetag.OtherWiseTag</tag-class> <body-content>scriptless</body-content> </tag>
3、for:Each标签:
package com.weijia.foreach; import java.io.IOException; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.SimpleTagSupport; import sun.text.CompactShortArray.Iterator; /** * ForEach标签 * @author weijiang204321 * */ public class ForEachTag extends SimpleTagSupport{ private Object items; private String var; private Collection collection; public void setItems(Object items) { this.items = items; if(items instanceof Collection){ collection = (Collection)items; } if(items instanceof Map){ Map map = (Map) items; collection = map.entrySet(); } //这种判断数组的方式是不适合基本类型数组的, //同时Arrays.asList方法接收的是可变参数Object...所以对于基本类型的数组是没有效果的 /*if(items instanceof Object[]){ Object[] obj = (Object[])items; collection = Arrays.asList(obj); }*/ //使用反射技术可以判断基本类型的数据类型 if(items.getClass().isArray()){ int len = Array.getLength(items); collection = new ArrayList(); 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 { Iterator it = (Iterator) collection.iterator(); while(it.hasNext()){ Object value = it.next(); this.getJspContext().setAttribute(var, value); } this.getJspBody().invoke(null); } }
这个是实现了forEach标签的,在tld文件中进行描述一下:
<tag> <name>forEach</name> <tag-class>com.weijia.foreach.ForEachTag</tag-class> <body-content>scriptless</body-content> <attribute> <name>var</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>items</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
关于这个forEach标签的知识我们要好好的解释一下,因为这里面有很多需要注意的地方, 这里我们定义了一个Object类型的items,这个变量是用来接收迭代对象的,还定义了一个String类型的var,这个是用来将每次迭代之后的值存入到域中的key名称。还定义了一个Collection类型的变量,这个变量只是一个辅助的变量,用来将迭代对象转化成集合类型(map类型可以转换、数组也可以转化)。这样我们就可以统一进行处理了。
我们看到setItems方法中,我们首先判断这个迭代对象是不是集合类型的,是的话,直接赋值到collections变量,如果不是,在判断是不是Map类型的,如果是的话,就将Map类型的变量转化成collections,如果不是,在判断是不是数组对象Object[],是的话,就进行转化,这里使用了Arrays.asList(T...)这个方法,关于这个方法,我们看到他的参数是一个可变的对象类型参数,看着这个样的判断是可以了,涵盖了所有的迭代对象类型,但是我们其实发现了一个问题,那就是在最后一次判断数组的时候,我们发现这个是对象类型的数组,那么我们如果传递基本类型数组的话,会是什么样的情况呢?其实我们知道基本类型数组其实就是一个Object对象,所以不是Object[],那么这里的涵盖的范围就有问题了,我们这里还需要单独的判断基本类型的数组,然后进行操作,那么我们来看一下jstl中的forEach标签的定义吧:
我们将standard.jar进行解压,然后找到forEach标签的定义类:我们可以从c.tld文件中找到forEach对应的类:
org.apache.taglibs.standard.tag.rt.core.ForEachTag,这时候我们需要去下载一个反编译工具,能够查看class文件的,叫做:jd-gui.exe
然后通过这个工具打开ForEachTag.class:
package org.apache.taglibs.standard.tag.rt.core; import java.util.ArrayList; import javax.servlet.jsp.JspTagException; import javax.servlet.jsp.jstl.core.LoopTag; import javax.servlet.jsp.tagext.IterationTag; import org.apache.taglibs.standard.tag.common.core.ForEachSupport; public class ForEachTag extends ForEachSupport implements LoopTag, IterationTag { public void setBegin(int paramInt) throws JspTagException { this.beginSpecified = true; this.begin = paramInt; validateBegin(); } public void setEnd(int paramInt) throws JspTagException { this.endSpecified = true; this.end = paramInt; validateEnd(); } public void setStep(int paramInt) throws JspTagException { this.stepSpecified = true; this.step = paramInt; validateStep(); } public void setItems(Object paramObject) throws JspTagException { if (paramObject == null) this.rawItems = new ArrayList(); else this.rawItems = paramObject; } }
protected ForEachIterator supportedTypeForEachIterator(Object paramObject) throws JspTagException { ForEachIterator localForEachIterator; if ((paramObject instanceof Object[])) localForEachIterator = toForEachIterator((Object[])paramObject); else if ((paramObject instanceof boolean[])) localForEachIterator = toForEachIterator((boolean[])paramObject); else if ((paramObject instanceof byte[])) localForEachIterator = toForEachIterator((byte[])paramObject); else if ((paramObject instanceof char[])) localForEachIterator = toForEachIterator((char[])paramObject); else if ((paramObject instanceof short[])) localForEachIterator = toForEachIterator((short[])paramObject); else if ((paramObject instanceof int[])) localForEachIterator = toForEachIterator((int[])paramObject); else if ((paramObject instanceof long[])) localForEachIterator = toForEachIterator((long[])paramObject); else if ((paramObject instanceof float[])) localForEachIterator = toForEachIterator((float[])paramObject); else if ((paramObject instanceof double[])) localForEachIterator = toForEachIterator((double[])paramObject); else if ((paramObject instanceof Collection)) localForEachIterator = toForEachIterator((Collection)paramObject); else if ((paramObject instanceof Iterator)) localForEachIterator = toForEachIterator((Iterator)paramObject); else if ((paramObject instanceof Enumeration)) localForEachIterator = toForEachIterator((Enumeration)paramObject); else if ((paramObject instanceof Map)) localForEachIterator = toForEachIterator((Map)paramObject); else if ((paramObject instanceof String)) localForEachIterator = toForEachIterator((String)paramObject); else localForEachIterator = toForEachIterator(paramObject); return localForEachIterator; }
//使用反射技术可以判断基本类型的数据类型 if(items.getClass().isArray()){ int len = Array.getLength(items); collection = new ArrayList(); for(int i=0;i<len;i++){ collection.add(Array.get(items, i)); } }这里我们使用反射技术来判断是不是数组类型,这里可以判断是基本类型数组还是对象类型数组,然后再使用Array这个工具类进行操作数组中的元素,这样我们看到这样的代码就比jstl中的代码简介明了,而且技术上也体现出一点高超。
下面我们在tld文件中进行描述一下:
<tag> <name>forEach</name> <tag-class>com.weijia.foreach.ForEachTag</tag-class> <body-content>scriptless</body-content> <attribute> <name>var</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>items</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute>
在Jsp页面中使用:
<%
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
String[] strAry = new String[]{"aaa","bbb","ccc"};
int[] intAry = new int[]{1,2,3,6};
%>
<c:forEach var="item" items="<%=list%>">
${item}
</c:forEach>
<br>
<c:forEach var="item" items="<%=strAry%>">
${item}
</c:forEach>
<br>
<c:forEach var="item" items="<%=intAry%>">
${item}
</c:forEach>
在这里额外的插一句:我在做这个实验的时候犯了一个很低级的错误,就是在使用标签的时候,给items赋值的时候我已开始使用的是EL表达式(${list}),然后总是报空指针异常,纠结了好长时间,发现items是null,那么就是没有传递对象给他,后来发现EL表达式是从域中取数据的,我们没有将list存入到任何域中,所以肯定拿不到了,这时候改用脚本表达式就可以了,因为脚本表达式就是可以去取页面中脚本片段中定义的变量值的,所以最后发现这个错误真的很低级的!!!
以上就是我们实现了类似于jstl中的标签库的一些标签的功能,这里我们可以联系一下我们之前在开始介绍自定标签的时候实现的四个案例:
1、控制标签体是否输出
2、控制标签体重复输出
3、控制余下的Jsp页面是否显示
4、修改标签体内容
其实我们会发现,上面实现的If标签其实就是第一个案例的体现,choose/when/otherwise标签就是第一个案例的体现,但是这里面还有一个功能就是父标签的编写,forEach标签是第二个案例的实现以及第四个案例的实现。所以说我们为什么一开始要介绍那四个案例,其实是为这部分内容做铺垫的。
下面我们来进行打包操作了,我们需要将我们定义的tld文件一起打包,这个打包也是很简单的,我们只需要新建一个Java项目,将我们定义好的标签类都拷贝过去,同时在项目中新建一个META-INF文件夹,在将我们定义的的tld文件拷贝进去,虽然会提示很多错误(因为是Java项目,不是Web项目很多类是找不到的)但是我们不理会,因为我们知道我们的类的逻辑和语法是没有错误的,只是找不到相应的类,这时候我们进行打包,一定要将META-INF文件夹一起打包进去,这时候我们就可以使用这个我们自己定义的标签库包了。
总结:好了,JSTL和自定义标签的相关知识就介绍到这里的,同时我们JavaWeb学习篇也到这里就结束了,这一系列的文章写了半个月吧,从中学习到了很多的知识,在此将每个知识点整理出来供大家分享,如果有什么不正确的地方还请提出,我立即做出修改。谢谢!