今天还是讲解自定义标签。
首先介绍了IterationTag接口、以及迭代输出标签体内容的标签实现原理和应用。在讲解迭代输出集合中的元素的例子时,先讲解用普通程序代码迭代输出集合的情况:for(String user : users){System.out.println("姓名:" + user)}
上面的程序代码包含三个信息:迭代变量、集合对象、显示格式,用自定义标签进行迭代输出时,其格式如下:
<it315:iterate var="user" collections="<%= users %>">姓名:${user}</it315:iterate>。
jsp页面需要将迭代变量和集合对象作为参数传递给自定义标签,自定义标签内部对集合进行迭代,在每次迭代时,将结果存入page域中并输出标签体内容,JSP页面中的标签体按一定格式引用迭代标签存入到page域中的变量。JSP页面的标签体中怎么知道page域中的变量名称的呢?这就是通过属性传递给自定义标签的那个迭代名称。
接下来讲解了如何通过自定义标签来定义JSP脚本变量。首先帮助学员比较下面这些语句的区别:
<%= abc %>
${abc}
前者是一个脚本变量,要“先定义、后使用”。而后者相当于<%= pageContext./*get*/findAttribute("abc")%>。
要想理解自定义标签是如何定义出JSP脚本变量的,首先就要知道:对于<it315:var />,千万不要认为仅仅只有jsp页面调用这个标签对应的处理类的功能,还要知道这个标签具有告诉JSP引擎生成一段怎样的Servlet Java代码的功能,其中包括定义新的变量。如果在标签定义文件中用了variable,会生成变量定义和变量赋值的代码(这个会自动吗?还是需要标签处理类自己写,我想是要自己写,但没做自己不写的实验)。
接着讲解了如何用TagExtraInfo类定义JSP脚本变量,这由涉及到了VariableInfo和TagData这两个类。如果想让一个标签可以定义出任何类型的java对象,当对象类型不确定时,TLD文件中的<variable-class>元素内容就无法确定,这时候就可以使用TagExtraInfo类。
接着讲解了BodyTag接口和如何让自定义标签控制标签体内容,学员们应记住两个重要的细节:在什么方法之中,BodyCotent对象中才有标签体部分的内容;pageContext.getOut()返回的对象不是永恒不变的,在doStartTage、doInitBody、doAfterBody、doEndTag等方法中获得的对象不相同。
最后讲解了Tomcat下的自定义标签的缓存和线程安全问题,关于自定义标签缓存的实验:先打开原始的JSP文件翻译成的Servlet java源文件和jsp源文件,用ultraedit复制一份Servlet java源文件,保存到另外一个文件中。再修改web.xml文件,让Tomcat不要对自定义标签进行缓存,启动tomcat后访问jsp,ultraedit没有提示servlet java源文件更改的信息,所以,用ultraedit打开并修改jsp文件(增空格后删除空格),重新访问jsp文件,ultraedit提示Servlet java源文件更改,将原来复制的Servlet源文件拖动到它的旁边,然后使用比较工具进行比较,可以发现两者的显著区别。据方立勋和王泽佑的实验,在不对标签进行缓存的情况下生成的Servlet源文件有问题,在_service方法中new了标签对象,而在_jspDestroy方法中才进行释放,这将导致只有最后一次service方法被调用时产生的标签对象才会被释放,他们用的tomcat是tomcat 5.5.9,看来又抓住了tomcat以前的一个bug。要留一个tomcat 5.5.9的版本。
学员王泽佑发现jsp的问题:
<%!
int count = 0;
public void jspInit()
{
System.out.println("jspInit!");
}
%>
<%= ++count %>
如果赶在第一次访问jsp时,快速刷新,刷新到一定时机后,jsp生成的servlet会重新被加载,我想这是在编译jsp时没有处理好并发的问题。另外一个问题,通过运行对话框启动浏览器访问jsp页面,多次重复这一操作,即快速启动多个浏览器进程来访问jsp页面,由于速度很快,其中一些浏览器直接从缓存中抓取数据,而不向服务器发出访问请求。
学员田湘东发现下面例子中的一个问题:
public class IterateMessages extends TagSupport
{
String name;
String [] s;
int i = 1;//缓存问题
public void setName(String name)
{
this.name = name;
}
public void setMessages(String[] s)
{
this.s = s;
}
public int doStartTag() throws JspException
{
if(s.length > 0)
{
pageContext.setAttribute(name,s[0]);
return EVAL_BODY_INCLUDE;
}
else
{
return SKIP_BODY;
}
}
public int doAfterBody() throws JspException
{
if(i < s.length)
{
pageContext.setAttribute(name,s[i]);
i++;
return EVAL_BODY_AGAIN;
}
else
{
return SKIP_BODY;
}
}
}
考虑到标签处理器对象的缓存问题,i成员变量不能在定义时赋值,而应该在doStartTag方法中赋值。
作业:
1.在doStartTag方法中返回EVAL_BODY_INCLUDE的情况下,在doInitBody方法中、doAfterBody方法中、oEndTag方法中分别向PageContext.getOut获得的out对象中写入内容,看看有怎样的区别?
2.对于书稿中的10.6.3的例子,如果在doStartTag方法中不返回EVAL_BODY_INCLUDE,那么嵌套在它里面的子标签向out中输出的内容就应该是直接传递给浏览器,并且原来程序中调用的bodyContent应该无效导致异常,做个实验看看。