﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-kapok-随笔分类-J2SE</title><link>http://www.blogjava.net/kapok/category/721.html</link><description>垃圾桶,嘿嘿，我藏的这么深你们还能找到啊，真牛！</description><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 09:03:57 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 09:03:57 GMT</pubDate><ttl>60</ttl><item><title>主动对象</title><link>http://www.blogjava.net/kapok/archive/2005/09/20/13572.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 20 Sep 2005 13:08:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/20/13572.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/13572.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/20/13572.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/13572.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/13572.html</trackback:ping><description><![CDATA[<STRONG><FONT size=5>&nbsp;</FONT></STRONG>
<P align=justify><A name=_Toc532649025></A><A name=_Toc532649683></A><A name=_Toc532650467></A><A name=_Toc535899856></A><A name=_Toc88053>5.1 </A><FONT lang=ZH-CN face=楷体_GB2312 size=5><A name=_Toc88053>主动对象</A></P></FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>
<P align=justify>　　那么到底什么是主动对象呢？传统上，所有的对象都是被动的代码段，对象中的代码是在对它发出方法调用的线程中执行的。也就是，调用线程（</FONT><FONT size=3>calling threads</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>）被“借出”，以执行被动对象的方法。</P>
<P align=justify>　　而主动对象却不一样。这些对象持有它们自己的线程（甚或多个线程），并将这个线程用于执行对它们的任何方法的调用。因而，如果你想象一个传统对象，在里面封装了一个线程（或多个线程），你就得到了一个主动对象。</P>
<P align=justify>　　例如，设想对象</FONT><FONT size=3>“A”</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>已在你的程序的</FONT><FONT size=3>main()</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>函数中被实例化。当你的程序启动时，</FONT><FONT size=3>OS</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>创建一个线程，以从</FONT><FONT size=3>main()</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>函数开始执行。如果你调用对象</FONT><FONT size=3>A</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>的任何方法，该线程将“流过”那个方法，并执行其中的代码。一旦执行完成，该线程返回调用该方法的点并继续它的执行。但是，如果</FONT><FONT size=3>”A”</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>是主动对象，事情就不是这样了。在这种情况下，主线程不会被主动对象借用。相反，当</FONT><FONT size=3>”A”</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>的方法被调用时，方法的执行发生在主动对象持有的线程中。另一种思考方法：如果调用的是被动对象的方法（常规对象），调用会阻塞（同步的）；而另一方面，如果调用的是主动对象的方法，调用不会阻塞（异步的）。</P></FONT><FONT size=3>
<P align=justify>　</P></FONT><img src ="http://www.blogjava.net/kapok/aggbug/13572.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-20 21:08 <a href="http://www.blogjava.net/kapok/archive/2005/09/20/13572.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>When Runtime.exec() won't </title><link>http://www.blogjava.net/kapok/archive/2005/09/08/12455.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 08 Sep 2005 10:35:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/08/12455.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/12455.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/08/12455.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/12455.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/12455.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.htmlWhen Runtime.exec() won't Navigate yourself around pitfalls related to the Runtime.exec() methodSummary-->SummaryIn this install...&nbsp;&nbsp;<a href='http://www.blogjava.net/kapok/archive/2005/09/08/12455.html'>阅读全文</a><img src ="http://www.blogjava.net/kapok/aggbug/12455.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-08 18:35 <a href="http://www.blogjava.net/kapok/archive/2005/09/08/12455.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用DynaBean减除不必要的VO和FormBean     </title><link>http://www.blogjava.net/kapok/archive/2005/05/10/4138.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 10 May 2005 05:01:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/10/4138.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4138.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/10/4138.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4138.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4138.html</trackback:ping><description><![CDATA[<A href="http://dev.csdn.net/article/62/62098.shtm">http://dev.csdn.net/article/62/62098.shtm</A><BR><BR><A href="http://www.chinaunix.net/jh/26/138463.html">http://www.chinaunix.net/jh/26/138463.html</A><BR><BR>&nbsp;&nbsp; DynaBean是BeanUtils里的宝贝之一。有了动态Bean，就不用写那么多VO出来了，特别那些只是为了和View交互而存在的bean，i hate Form Bean。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DynaBean的用法很简单，特别是有了LazyBynaBean之后.<BR><PRE>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DynaBean car = new LazyDynaBean();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; car.set("carNo",1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; car.set("owner","张三");
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out.print(car.get("carNo"));</P></PRE>
<P></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 看着像一个map,只不过多了反射的功能，所以支持那些使用反射来获取属性的场合。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 但是因为他没有car.getCarNo()这样的函数，只能用car.get("carNo")，所以也就不支持JSTL里面&lt;c: out value="{car.carNo}"&gt;这样的语法，因为JSTL是默认转回car.getCarNo()的。<BR>而且几经讨论,JSTL不支持 car.userFunction(),理由是不希望代码里有java代码:(<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 因此，还是写&lt;%=car.get("carNo")&gt;好了，如果是陷在JSTL的一个循环结构里，就要用从pageContext里面把bean拿出来，<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 又或者自己写的tag，幸亏也很简单，用BeanUtils.getProperty(bean,property)函数就可以了，<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 又或者，用Velocity和Freemarker。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 最后，如果car.set("carNo",null)，会得到一个莫名其妙的Object对象，如果要使它为Null,需要先定义他的类型。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 因此，对于可能为Null的列，需要用LazyClass定义类型，幸亏也很Lazy, 不可能为Null的值可以不管。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LazyDynaClass lc = new LazyDynaClass();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lc.add("owner",String.class); // 指定null converter String<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DynaBean&nbsp;car = new LazyDynaBean(lc);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; car.set("carNo",1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; car.set("owner",null);</P><img src ="http://www.blogjava.net/kapok/aggbug/4138.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-10 13:01 <a href="http://www.blogjava.net/kapok/archive/2005/05/10/4138.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Development with JSP and XML-- Part III: Developing JSP Custom Tags</title><link>http://www.blogjava.net/kapok/archive/2005/05/10/4133.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 10 May 2005 02:55:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/10/4133.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4133.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/10/4133.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4133.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4133.html</trackback:ping><description><![CDATA[<SPAN class=byline><B><A href="http://java.sun.com./developer/technicalArticles/xml/WebAppDev3/">http://java.sun.com./developer/technicalArticles/xml/WebAppDev3/</A><BR><BR>Part III: Developing JSP Custom Tags</B><BR>By Qusay H. Mahmoud<BR>August 2001 </SPAN>
<P>JavaServer Pages (JSP) technology makes it easy to embed bits of Java code (or scriptlets) in HTML documents. This solution, however, may not be suitable for all HTML content developers, perhaps because they do not know Java and they do not care to learn its syntax. While JavaBeans can be used to encapsulate much of the Java code, using them in JSP pages still requires content developers to have some knowledge of Java syntax. </P>
<P>JSP technology allows you to introduce new custom tags through the tag library facility. As a Java developer, you can extend JSP pages by introducing custom tags that can be deployed and used in an HTML-like syntax. Custom tags also allow you to provide better packaging by improving the separation between business logic and presentation logic. </P>
<P>This article presents a brief overview of custom tags, then it shows: </P>
<UL>
<LI>How to develop and deploy simple tags 
<LI>How to develop and deploy advanced tags: parameterized tags and tags with a body 
<LI>How to describe tags with the Tag Library Descriptor (TLD) </LI></UL>
<P>Finally, some programming guidelines are also provided. </P>
<H3>Overview of Tags</H3>
<P>If you have experience with HTML, you already know about the types of tags that can be used. Basically there are two types of tags, and both can have attributes (information about how the tag should do its job): </P>
<UL>
<LI><I>Bodyless Tags</I>: A bodyless tag is a tag that has a start tag but does not have a matching end tag. It has the syntax: 
<P><PRE>&lt;tagName attributeName="value" 
anotherAttributeName="anotherValue"/&gt;
</PRE>
<P></P>
<P>Bodyless tags are used to represent certain functions, such as presenting an input field, or displaying an image. Here is an example of a bodyless tag in HTML: </P>
<P><CODE>&lt;IMG SRC="/developer/technicalArticles/xml/WebAppDev3/fig10.gif"&gt;</CODE> </P>
<P></P>
<LI><I>Tags with a Body</I>: A tag with a body has a start tag and a matching end tag. It has the syntax: 
<P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>  
&lt;tagName attributeName="value" 
anotherAttributeName="anotherValue"&gt;
...tag body...
&lt;/tagName&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  --></P>
<P>Tags with a body are used to perform operations on the body content, such as formatting. Here is an example of a tag with a body in HTML: </P>
<P><CODE>&lt;H2&gt;Custom Tags&lt;/H2&gt;</CODE> </P></LI></UL>
<H3>JSP Custom Tags</H3>
<P>JSP custom tags are merely Java classes that implement special interfaces. Once they are developed and deployed, their actions can be called from your HTML using XML syntax. They have a start tag and an end tag. They may or may not have a body. A bodyless tag can be expressed as: </P>
<P><CODE>&lt;tagLibrary:tagName /&gt;</CODE></P>
<P>And, a tag with a body can be expressed as: </P><PRE>&lt;tagLibrary:tagName&gt;
   body
&lt;/tagLibrary:tagName&gt;
</PRE>
<P>Again, both types may have attributes that serve to customize the behavior of a tag. The following tag has an attribute called <CODE>name</CODE>, which accepts a String value obtained by evaluating the variable <CODE>yourName</CODE>: </P>
<P><CODE>&lt;mylib:hello name="&lt;%= yourName %&gt;" /&gt; </CODE></P>
<P>Or, it can be written as a tag with a body as: </P><PRE>&lt;mylib:hello&gt;
  &lt;%= yourName %&gt;
&lt;/mylib:hello&gt;</PRE>
<BLOCKQUOTE><!-- BEGIN FRAGMENT | HR TAG -->
<DIV class=contentdivider>
<TABLE class=grey4 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=4 alt=" " src="http://java.sun.com./im/a.gif" width=1 border=0></TD></TR></TBODY></TABLE></DIV><!-- END FRAGMENT | HR TAG --><B>Note</B>: Any data that is a simple string, or can be generated by evaluating a simple expression, should be passed as an attribute and not as body content. <!-- BEGIN FRAGMENT | HR TAG -->
<DIV class=contentdivider>
<TABLE class=grey4 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=4 alt=" " src="http://java.sun.com./im/a.gif" width=1 border=0></TD></TR></TBODY></TABLE></DIV><!-- END FRAGMENT | HR TAG --></BLOCKQUOTE>
<H4>Benefits of Custom Tags</H4>
<P>A very important thing to note about JSP custom tags is that they do not offer more functionality than scriptlets, they simply provide better packaging, by helping you improve the separation of business logic and presentation logic. Some of the benefits of custom tags are: </P>
<UL>
<LI>They can reduce or eliminate scriptlets in your JSP applications. Any necessary parameters to the tag can be passed as attributes or body content, and therefore no Java code is needed to initialize or set component properties. 
<LI>They have simpler syntax. Scriptlets are written in Java, but custom tags can be used in an HTML-like syntax. 
<LI>They can improve the productivity of nonprogrammer content developers, by allowing them to perform tasks that cannot be done with HTML. 
<LI>They are reusable. They save development and testing time. Scritplets are not reusable, unless you call cut-and-paste reuse. </LI></UL>
<P>In short, you can use custom tags to accomplish complex tasks the same way you use HTML to create a presentation. </P>
<H3>Defining a Tag</H3>
<P>A tag is a Java class that implements a specialized interface. It is used to encapsulate the functionality from a JSP page. As we mentioned earlier, a tag can be bodyless or with a body. To define a simple bodyless tag, your class must implement the <CODE>Tag</CODE> interface. Developing tags with a body is discussed later. Sample 1 shows the source code for the <CODE>Tag</CODE> interface that you must implement: </P>
<P><B>Sample 1:</B> Tag.java </P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>public interface Tag {
   public final static int SKIP_BODY = 0;
   public final static int EVAL_BODY_INCLUDE = 1;
   public final static int SKIP_PAGE = 5;
   public final static int EVAL_PAGE = 6;

   void setPageContext(PageContext pageContext);
   void setParent(Tag parent);
   Tag getParent();
   int doStartTag() throws JspException;
   int doEndTag() throws JspException;
   void release();
}
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>All tags must implement the <CODE>Tag</CODE> interface (or one of its subinterfaces) as it defines all the methods the JSP runtime engine calls to execute a tag. Table 1 provides a description of the methods in the <CODE>Tag</CODE> interface. </P>
<CENTER>
<TABLE width="75%" border=2>
<CAPTION align=top><I>Table 1: Description of methods in the Tag interface</I> </CAPTION>
<TBODY>
<TR>
<TH>Method</TH>
<TH>Description</TH></TR>
<TR>
<TD><CODE>setPageContext(PageContext pc)</CODE></TD>
<TD>This method is invoked by the JSP runtime, prior to <CODE>doStartTag</CODE>, to set the page context.</TD></TR>
<TR>
<TD><CODE>setParent(Tag parent)</CODE></TD>
<TD>Invoked by the JSP runtime, prior to <CODE>doStartTag</CODE>, to pass a tag handler a reference to its parent tag.</TD></TR>
<TR>
<TD><CODE>getParent</CODE></TD>
<TD>Returns a <CODE>Tag</CODE> instance that is the parent of this tag.</TD></TR>
<TR>
<TD><CODE>doStartTag</CODE></TD>
<TD>Invoked by the JSP runtime to prompt the tag handler to process the start tag for this instance.</TD></TR>
<TR>
<TD><CODE>doEndTag</CODE></TD>
<TD>Invoked by the JSP runtime after returning from <CODE>doStartTag</CODE>. The body of the action may or may not have been evaluated, depending on the return value of <CODE>doStartTag</CODE>.</TD></TR>
<TR>
<TD><CODE>release</CODE></TD>
<TD>Invoked by the JSP runtime to indicate to the tag handler to perform any cleanup necessary.</TD></TR></TBODY></TABLE></CENTER>
<H3>My First Tag</H3>
<P>Now, let's look at a sample tag that when invoked prints a message to the client. </P>
<P>There are a few steps involved in developing a custom tag. These steps can be summarized as follows: </P>
<OL>
<LI>Develop the tag handler 
<LI>Create a tag library descriptor 
<LI>Test the tag </LI></OL>
<H4>1. Develop the Tag Handler</H4>
<P>A <I>tag handler</I> is an object invoked by the JSP runtime to evaluate a custom tag during the execution of a JSP page that references the tag. The methods of the tag handler are called by the implementation class at various points during the evaluation of the tag. Every tag handler must implement a specialized interface. In this example, the simple tag implements the <CODE>Tag</CODE> interface as shown in Sample 2.</P>
<P><B>Sample 2</B>: HelloTag.java</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>package tags;

import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class HelloTag implements Tag {
   private PageContext pageContext;
   private Tag parent;

   public HelloTag() {
      super();
   }

   public int doStartTag() throws JspException {
      try {
         pageContext.getOut().print(
         "This is my first tag!");
      } catch (IOException ioe) {
         throw new JspException("Error: 
         IOException while writing to client" 
         + ioe.getMessage());
      }
      return SKIP_BODY;
   }

   public int doEndTag() throws JspException {
      return SKIP_PAGE;
   }

   public void release() {
   }

   public void setPageContext(PageContext 
   pageContext) {
      this.pageContext = pageContext;
   }

   public void setParent(Tag parent) {
      this.parent = parent;
   }

   public Tag getParent() {
      return parent;
   }
}
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>The two important methods to note in <CODE>HelloTag</CODE> are <CODE>doStartTag</CODE> and <CODE>doEndTag</CODE>. The <CODE>doStartTag</CODE> method is invoked when the start tag is encountered. In this example, this method returns <CODE>SKIP_BODY</CODE> because a simple tag has no body. The <CODE>doEndTag</CODE> method is invoked when the end tag is encountered. In this example, this method returns <CODE>SKIP_PAGE</CODE> because we do not want to evaluate the rest of the page; otherwise it should return <CODE>EVAL_PAGE</CODE>. </P>
<P>To compile the <CODE>HelloTag</CODE> class, assuming that Tomcat is installed at: <I>c:\tomcat</I>: </P>
<UL>
<LI>Create a new subdirectory called <I>tags</I>, which is the name of the package containing the <CODE>HelloTag</CODE> class. This should be created at: <I>c:\tomcat\webapps\examples\web-inf\classes</I>. 
<LI>Save <I>HelloTag.java</I> in the <I>tags</I> subdirectory. 
<LI>Compile with the command: 
<P><PRE>c:\tomcat\webapps\examples\web-inf\classes\tags&gt; 
javac -classpath c:\tomcat\lib\servlet.jar 
HelloTag.java
</PRE>
<P></P></LI></UL>
<H4>2. Create a Tag Library Descriptor</H4>
<P>The next step is to specify how the tag will be used by the JSP runtime that executes it. This can be done by creating a Tag Library Descriptor (TLD), which is an XML document. Sample 3 shows a sample TLD: </P>
<P><B>Sample 3</B>: mytaglib.tld</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>&lt;?xml version="1.0" encoding="ISO-8859-1" ?&gt;
&lt;!DOCTYPE taglib
        PUBLIC "-//Sun Microsystems, Inc.//
        DTD JSP Tag Library 1.1//EN"
        "http://java.sun.com/j2ee/dtds/
        web-jsptaglibrary_1_1.dtd"&gt;

&lt;!-- a tag library descriptor --&gt;

&lt;taglib&gt;
   &lt;tlibversion&gt;1.0&lt;/tlibversion&gt;
   &lt;jspversion&gt;1.1&lt;/jspversion&gt;
   &lt;shortname&gt;first&lt;/shortname&gt;
   &lt;uri&gt;&lt;/uri&gt;
   &lt;info&gt;A simple tab library for the 
   examples&lt;/info&gt;

  &lt;tag&gt;
    &lt;name&gt;hello&lt;/name&gt;
    &lt;tagclass&gt;tags.HelloTag&lt;/tagclass&gt;
    &lt;bodycontent&gt;empty&lt;/bodycontent&gt;
    &lt;info&gt;Say Hi&lt;/info&gt;
  &lt;/tag&gt;         
&lt;/taglib&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>First we specify the tag library version and JSP version. The <CODE>&lt;shortname&gt;</CODE> tag specifies how we are going to reference the tag library from the JSP page. The <CODE>&lt;uri&gt;</CODE> tag can be used as a unique identifier for your tag library. </P>
<P>In this TLD, we only have one tag named <CODE>hello</CODE> whose class is specified using the <CODE>&lt;tagclass&gt;</CODE> tag. However, a tag library can have as many tags as you like. The <CODE>&lt;bodycontent&gt;</CODE> tells us that this tag will not have a body; otherwise an error will be produced. On the other hand, if you like to evaluate the body of the tag, that value would be: </P>
<UL>
<LI><CODE>tagdependent</CODE>: meaning that any body of the tag would be handled by the tag itself, and it can be empty. 
<LI><CODE>JSP</CODE>: meaning that the JSP container should evaluate any body of the tag, but it can also be empty. </LI></UL>
<P>Save <I>mytaglib.tld</I> in the directory: <I>c:\tomcat\webapps\examples\web-inf\jsp</I>. </P>
<H4>3. Test the Tag</H4>
<P>The final step is to test the tag we have developed. In order to use the tag, we have to reference it, and this can be done in three ways: </P>
<OL>
<LI>Reference the tag library descriptor of an unpacked tag library. For example: 
<P><PRE>&lt;@ taglib uri="/WEB-INF/jsp/mytaglib.tld" 
prefix="first" %&gt;
</PRE>
<P></P>
<LI>Reference a JAR file containing a tag library. For example: 
<P><PRE>&lt;@ taglib uri="/WEB-INF/myJARfile.jar" 
prefix='first" %&gt;
</PRE>
<P></P>
<LI>Define a reference to the tag library descriptor from the web-application descriptor (web.xml) and define a short name to reference the tag library from the JSP. To do this, open the file: <I>c:\tomcat\webapps\examples\web-inf\web.xml</I> and add the following lines before the end line, which is <CODE>&lt;web-app&gt;</CODE>: 
<P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>    &lt;taglib&gt;
       &lt;taglib-uri&gt;mytags&lt;/taglib-uri&gt;
       &lt;taglib-location&gt;/WEB-INF/jsp/
       mytaglib.tld&lt;/taglib-location&gt;
    &lt;/taglib&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  --></P></LI></OL>
<P>Now, write a JSP and use the first syntax. Sample 4 shows an example: </P>
<P><B>Sample 4</B>: Hello.jsp</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>&lt;%@ taglib uri="/WEB-INF/jsp/mytaglib.tld"
 prefix="first" %&gt;
&lt;HTML&gt;
&lt;HEAD&gt;
&lt;TITLE&gt;Hello Tag&lt;/TITLE&gt;
&lt;/HEAD&gt;

&lt;BODY bgcolor="#ffffcc"&gt;

&lt;B&gt;My first tag prints&lt;/B&gt;:

&lt;first:hello/&gt;

&lt;/BODY&gt;
&lt;/HTML&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>The <CODE>taglib</CODE> is used to tell the JSP runtime where to find the descriptor for our tag library, and the <CODE>prefix</CODE> specifies how we will refer to tags in this library. With this in place, the JSP runtime will recognize any usage of our tag throughout the JSP, as long as we precede our tag name with the prefix <CODE>first</CODE> as in <CODE>&lt;first:hello/&gt;</CODE>. </P>
<P>Alternatively, you can use the second reference option by creating a JAR file. Or, you can use the third reference option simply by replacing the first line in Sample 4 with the following line: </P><PRE>&lt;%@ taglib uri="mytags" prefix="first" %&gt;
</PRE>
<P>Basically, we have used the <CODE>mytags</CODE> name, which was added to web.xml, to reference the tag library. For the rest of the examples in this article, this reference will be used. </P>
<P>Now, if you request Hello.jsp from a browser, you would see something similar to Figure 1. </P>
<P>
<CENTER><IMG src="http://java.sun.com./developer/technicalArticles/xml/WebAppDev3/fig1.gif"><BR><I>Figure 1: First Custom Tag</I> </CENTER>
<P></P>
<P>The custom tag developed in Sample 4 is a simple tag, the goal of it was just to give you a flavor of the effort involved in developing custom tags. You may have noticed that even this simple tag required us to implement a number of methods, some of which have very simple implementations. To minimize the effort involved, the JSP designers provided a template to be used for implementing simple tags. The template is the <CODE>TagSupport</CODE> abstract class. It is a convenience class that provides default implementations for all the methods in the <CODE>Tag</CODE> interface. </P>
<P>Therefore, an easier way to write simple tags is to <I>extend</I> the <CODE>TagSupport</CODE> class rather than <I>implementing</I> the <CODE>Tag</CODE> interface. You can think of the <CODE>TagSupport</CODE> abstract class as an adapter. Having said that, the <CODE>HelloTag</CODE> class in Sample 4 can be easily written as shown in Sample 5. </P>
<P><B>Sample 5</B>: Extending the TagSupport class</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>package tags;

import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class HelloTag extends TagSupport {

   public int doStartTag() throws JspException {
      try {
         pageContext.getOut().print("This is my 
         first tag!");
      } catch (IOException ioe) {
         throw new JspException("Error: 
         IOException while writing 
        to client" + ioe.getMessage());
      }
      return SKIP_BODY;
   }

   public int doEndTag() throws JspException {
      return SKIP_PAGE;
   }
}
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<H3>Parameterized Tags</H3>
<P>We have seen how to develop simple tags. Now, let's see how to develop parameterized tags--tags that have attributes. There are two new things that that need to be added to the previous example to handle attributes: </P>
<OL>
<LI>Add a set method 
<LI>Add a new tag to <I>mytagslib.tld</I> </LI></OL>
<P>Adding a set method and changing the output message results in Sample 5. </P>
<P><B>Sample 5</B>: A tag with an attribute</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>package tags;

import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class HelloTagParam extends TagSupport {
   private String name;

   <B>
   public void setName(String name) {
      this.name = name;
   }
   </B>

   public int doStartTag() throws JspException {
      try {
         pageContext.getOut().print("Welcome to 
         JSP Tag Programming, "<B> +name</B>);
      } catch (IOException ioe) {
         throw new JspException("Error: 
         IOException 
         while writing to client");
      }
      return SKIP_BODY;
   }

   public int doEndTag() throws JspException {
      return SKIP_PAGE;
   }
}
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>The next thing we need to do is add a new tag to <I>mytaglib.tld</I>. The new tag is shown in Sample 6. This snippet of code should be added to <I>mytaglib.tld</I> right before the last line, <CODE>&lt;/taglib&gt;</CODE>: </P>
<P><B>Sample 6</B>: revising mytaglib.tld</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>&lt;tag&gt;
  &lt;name&gt;helloparam&lt;/name&gt;
  &lt;tagclass&gt;tags.HelloTagParam&lt;/tagclass&gt;
  &lt;bodycontent&gt;empty&lt;/bodycontent&gt;
  &lt;info&gt;Tag with Parameter&lt;/info&gt;
  &lt;attribute&gt;
     &lt;name&gt;name&lt;/name&gt;
     &lt;required&gt;false&lt;/required&gt;
     &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
         &lt;/attribute&gt;
&lt;/tag&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>We have added a new tag named <CODE>helloparam</CODE>. Notice the new <CODE>&lt;attribute&gt;</CODE> tag, which specifies that the <CODE>helloparam</CODE> tag accepts an attribute whose name is <CODE>name</CODE>. The <CODE>&lt;required&gt;</CODE> tag is set to <CODE>false</CODE>, meaning that the attribute is optional; the <CODE>&lt;rtexprvalue&gt;</CODE> tag is set to <CODE>false</CODE> specifying that no run time evaluation will be done. </P>
<P>Nothing needs to be added to the web.xml web-application descriptor file since we are using the same tag library: <I>mytaglib.tld</I>. </P>
<P>Now, we can test the new tag. The source code in Sample 7 shows how to test it using a name attribute "JavaDuke". </P>
<P><B>Sample 7</B>: HelloTagParam.jsp</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>&lt;%@ taglib uri="mytags" prefix="first" %&gt;
&lt;HTML&gt;
&lt;HEAD&gt;
&lt;TITLE&gt;Hello Tag with Parameter&lt;/TITLE&gt;
&lt;/HEAD&gt;

&lt;BODY bgcolor="#ffffcc"&gt;
&lt;B&gt;My parameterized tag prints&lt;/B&gt;:

&lt;P&gt;

&lt;first:helloparam name="JavaDuke"/&gt;

&lt;/BODY&gt;
&lt;/HTML&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>If you request <I>HelloTagParam.jsp</I> from a web browser, you would see an output similar to that in Figure 2. </P>
<P>
<CENTER><IMG src="http://java.sun.com./developer/technicalArticles/xml/WebAppDev3/fig2.gif"><BR><I>Figure 2: Testing a parameterized tag</I> </CENTER>
<P></P>
<H3>Tag Libraries</H3>
<P>A tag library is a collection of JSP custom tags. The <A href="http://jakarta.apache.org/taglibs">Jakarta Taglibs Project</A> provides several useful tag libraries for XML parsing, transformations, email, databases, and other uses. They can be easily downloaded and used. </P>
<P>Here we develop our tag library. As an example, we develop a simple math library that provides two tags, one for adding two numbers, and the other for subtracting one number from another number. Each tag is represented by one class. The source code for the two classes, <CODE>Add.java</CODE> and <CODE>Subtract.java</CODE>, is shown in Sample 8. </P>
<P><B>Sample 8</B>: Add.java and Subtract.java</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>package tags;

import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class Add extends TagSupport {
   private int num1, num2;

   public void setNum1(int num1) {
      this.num1 = num1;
   }
   
   public void setNum2(int num2) {
      this.num2 = num2;
   }


   public int doStartTag() throws JspException {
      try {
         pageContext.getOut().print("Welcome
         to First
          Grade Math! ");
         pageContext.getOut().print("The sum of: " +
         num1 + " and " + num2 + " is: " + (
         num1+num2));
      } catch (IOException ioe) {
         throw new JspException("Error:
         IOException
          while writing to client");
      }
      return SKIP_BODY;
   }
}
      
// Subtract.java

package tags;

import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class Subtract extends TagSupport {
   private int num1, num2;

   public void setNum1(int num1) {
      this.num1 = num1;
   }
   
   public void setNum2(int num2) {
      this.num2 = num2;
   }


   public int doStartTag() throws JspException {
      try {
         pageContext.getOut().print("Welcome to First
          Grade Math! ");
         pageContext.getOut().print("If you 
         subtract:
          " + num2 + " from " + num1 +
          ", you get: "+ (num1 - num2));
      } catch (IOException ioe) {
         throw new JspException("Error:
          IOException 
         while writing to client");
      }
      return SKIP_BODY;
   }
}
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>The source code is easy to understand. Notice one thing we have repeated in <CODE>Add.java</CODE> and <CODE>Subract.java</CODE> is the call to <CODE>pageContext.getOut.print</CODE>. A better way to do this would be to get a <CODE>JspWriter</CODE> object then then use it to print to the client: </P><PRE>JspWriter out = pageContext.getOut();
out.print("first line");
out.print("second line");
</PRE>
<P>The next step is to revise the tag library descriptor file, <I>mytaglib.tld</I>, and add descriptions to the two new tags. Sample 9 shows the description for the new tags. Add the following snippet of XML to <CODE>mytaglib.tld</CODE>, right before the last line. </P>
<P><B>Sample 9</B>: revising mytaglib.tld</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>  &lt;tag&gt;
    &lt;name&gt;add&lt;/name&gt;
    &lt;tagclass&gt;tags.Add&lt;/tagclass&gt;
    &lt;bodycontent&gt;empty&lt;/bodycontent&gt;
    &lt;info&gt;Tag with Parameter&lt;/info&gt;
    &lt;attribute&gt;
       &lt;name&gt;num1&lt;/name&gt;
       &lt;required&gt;true&lt;/required&gt;
       &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
    &lt;/attribute&gt;
    &lt;attribute&gt;
      &lt;name&gt;num2&lt;/name&gt;
      &lt;required&gt;true&lt;/required&gt;
      &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
    &lt;/attribute&gt;
  &lt;/tag&gt;

  &lt;tag&gt;
    &lt;name&gt;sub&lt;/name&gt;
    &lt;tagclass&gt;tags.Subtract&lt;/tagclass&gt;
    &lt;bodycontent&gt;empty&lt;/bodycontent&gt;
    &lt;info&gt;Tag with Parameter&lt;/info&gt;
    &lt;attribute&gt;
       &lt;name&gt;num1&lt;/name&gt;
       &lt;required&gt;true&lt;/required&gt;
       &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
    &lt;/attribute&gt;
    &lt;attribute&gt;
      &lt;name&gt;num2&lt;/name&gt;
      &lt;required&gt;true&lt;/required&gt;
      &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
    &lt;/attribute&gt;
  &lt;/tag&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>As you can see, each tag requires two attributes that must be named <CODE>num1</CODE> and <CODE>num2</CODE>. </P>
<P>Now, we can test our math tag library using the test driver shown in Sample 10. </P>
<P><B>Sample 10</B>: math.jsp</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>&lt;%@ taglib uri="mytags" prefix="math" %&gt;
&lt;HTML&gt;
&lt;HEAD&gt;
&lt;TITLE&gt;Hello Tag with Parameter&lt;/TITLE&gt;
&lt;/HEAD&gt;

&lt;BODY bgcolor="#ffffcc"&gt;
&lt;B&gt;Calling first tag&lt;/B&gt;
&lt;P&gt;
&lt;math:add num1="1212" num2="121"/&gt;
&lt;P&gt;
&lt;B&gt;Calling second tag&lt;/B&gt;
&lt;P&gt;
&lt;math:sub num1="2123" num2="3"/&gt;

&lt;/BODY&gt;
&lt;/HTML&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>If you request <I>math.jsp</I> from a web browser, you would see an output that is similar to Figure 3. </P>
<P>
<CENTER><A href="http://java.sun.com./developer/technicalArticles/xml/WebAppDev3/fig3new.gif"><IMG height=181 alt="fig 3" src="http://java.sun.com./developer/technicalArticles/xml/WebAppDev3/fig3bnew.gif" width=450> </A><BR><I>Figure 3: Testing the math tag library </I></CENTER>
<P></P>
<H3>Tags with a Body</H3>
<P>A tag handler for a tag with a body is implemented differently depending on whether the body needs to be evaluated once or multiple times. </P>
<UL>
<LI><I>Single Evaluation</I>: if the body needs to be evaluated once, the tag handler should implement the <CODE>Tag</CODE> interface, or extend the <CODE>TagSupport</CODE> abstract class; the <CODE>doStartTag</CODE> method needs to return <CODE>EVAL_BODY_INCLUDE</CODE>, and if it does not need to be evaluated at all then it should return <CODE>BODY_SKIP</CODE>. 
<LI><I>Multiple Evaluation</I>: if the body needs to be evaluated multiple times, the <CODE>BodyTag</CODE> interface should be implemented. The <CODE>BodyTag</CODE> interface extends the <CODE>Tag</CODE> interface and defines additional methods (<CODE>setBodyContent</CODE>, <CODE>doInitBody</CODE>, and <CODE>doAfterBody</CODE>) that enable a tag handler to inspect and possibly change its body. Alternatively, and similarly to the <CODE>TagSupport</CODE> class, you can extend the <CODE>BodyTagSupport</CODE> class, which provides default implementations for the methods in the <CODE>BodyTag</CODE> interface. Typically, you need to implement <CODE>doInitBody</CODE> and <CODE>doAfterBody</CODE> methods. The <CODE>doInitBody</CODE> is called after the body content is set but before it is evaluated, and the <CODE>doAfterBody</CODE> is called after the body content is evaluated. </LI></UL>
<H4>Single Evaluation</H4>
<P>Here is an example of single evaluation where we extend the <CODE>BodyTagSupport</CODE> class. This example reads the body content, converts it to lowercase, then writes the output back to the client. Sample 11 shows the source code. The body content is retrieved as a string, converted to lowercase, then written back to the client. </P>
<P><B>Sample 11</B>: ToLowerCaseTag.java</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>package tags;

import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class ToLowerCaseTag extends BodyTagSupport {

   public int doAfterBody() throws JspException {
      try {
         BodyContent bc = getBodyContent();
         // get the bodycontent as string
         String body = bc.getString();
         // getJspWriter to output content
         JspWriter out = bc.getEnclosingWriter();
         if(body != null) {
            out.print(body.toLowerCase());
         }
      } catch(IOException ioe) {
         throw new JspException("Error:
          "+ioe.getMessage());   
      }
      return SKIP_BODY;
   }
}

</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>The next step is to add a tag to the tag library descriptor file, <I>mytaglib.tld</I>. The new tag descriptor is: </P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>&lt;tag&gt;
  &lt;name&gt;tolowercase&lt;/name&gt;
  &lt;tagclass&gt;tags.ToLowerCaseTag&lt;/tagclass&gt;
  &lt;bodycontent&gt;JSP&lt;/bodycontent&gt;
  &lt;info&gt;To lower case tag&lt;/info&gt;
&lt;/tag&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>Note that when you write a tag with a body, the <CODE>&lt;bodycontent&gt;</CODE> tag's value must be either <CODE>JSP</CODE> or <CODE>jspcontent</CODE>, as discussed earlier. </P>
<P>A test driver for this example is shown in Sample 12. </P>
<P><B>Sample 12</B>: lowercase.jsp</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>&lt;%@ taglib uri="mytags" prefix="first" %&gt;
&lt;HTML&gt;
&lt;HEAD&gt;
&lt;TITLE&gt;Body Tag&lt;/TITLE&gt;
&lt;/HEAD&gt;

&lt;BODY bgcolor="#ffffcc"&gt;

&lt;first:tolowercase&gt;
Welcome to JSP Custom Tags Programming.
&lt;/first:tolowercase&gt;

&lt;/BODY&gt;
&lt;/HTML&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>If you request <CODE>lowercase.jsp</CODE> from a web browser, you would see something similar to Figure 4. </P>
<P>
<CENTER><IMG src="http://java.sun.com./developer/technicalArticles/xml/WebAppDev3/fig4.gif"><BR><I>Figure 4: Testing the lowercase tag</I> </CENTER>
<P></P>
<H4>Multiple Evaluations</H4>
<P>Let's now see an example of a body tag evaluated multiple times. The example accepts a string and prints the string as many times as indicated in the JSP. The source code is shown in Sample 13: </P>
<P><B>Sample 13</B>: LoopTag.java</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>package tags;

import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class LoopTag extends BodyTagSupport {

   int times = 0;

   BodyContent bodyContent;

   public void setTimes(int times) {

      this.times = times;
   }

   public int doStartTag() throws JspException {
      if (times&gt;0) {
        return EVAL_BODY_TAG;
      } else {
         return SKIP_BODY;
      }
   }

   public void setBodyContent(BodyContent 
   bodyContent) {
      this.bodyContent = bodyContent;
   }

   public int doAfterBody() throws JspException {
      if (times &gt;1) {
        times--;
        return EVAL_BODY_TAG;
      } else {
         return SKIP_BODY;
      }
   }

   public int doEndTag() throws JspException {
      try {
         if(bodyContent != null) {
           bodyContent.writeOut(
           bodyContent.getEnclosingWriter());
         }
      } catch(IOException e) {
        throw new JspException(
        "Error: "+e.getMessage());
      }
      return EVAL_PAGE;
   }
}
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>In this example, the methods implemented play the following roles: </P>
<UL>
<LI>The <CODE>doStartTag</CODE> method gets called at the start of the tag. It checks if the loop needs to be performed. 
<LI>The <CODE>setBodyContent</CODE> is called by the JSP container to check for more than one loop. 
<LI>The <CODE>doAfterBody</CODE> method is called after each evaluation; the number of times the loop needs to be performed is decreased by one, then it returns <CODE>SKIP_BODY</CODE> when the number of times is not greater than one. 
<LI>The <CODE>doEndTag</CODE> method is called at the end of the tag, and the content (if any) is written to the enclosing writer. </LI></UL>
<P>Similarly to previous examples, the next step is to add a new tag descriptor to <I>mytaglib.tld</I>. The following lines show what needs to be added: </P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>&lt;tag&gt;
  &lt;name&gt;loop&lt;/name&gt;
  &lt;tagclass&gt;tags.LoopTag&lt;/tagclass&gt;
  &lt;bodycontent&gt;JSP&lt;/bodycontent&gt;
  &lt;info&gt;Tag with body and parameter&lt;/info&gt;
  &lt;attribute&gt;
     &lt;name&gt;times&lt;/name&gt;
     &lt;required&gt;true&lt;/required&gt;
     &lt;rtexprvalue&gt;true&lt;/rtexprvalue&gt;
  &lt;/attribute&gt;
&lt;/tag&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>Note that the <CODE>&lt;rtexprvalue&gt;</CODE> tag specifies that evaluations will be performed at runtime. </P>
<P>A test driver is shown in Sample 14. </P>
<P><B>Sample 14</B>: loops.jsp</P><!-- BEGIN VCD7 CODE SAMPLE COMPONENT  -->
<TABLE class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>&lt;%@ taglib uri="mytags" prefix="first" %&gt;
&lt;HTML&gt;
&lt;HEAD&gt;
&lt;TITLE&gt;Body Tag&lt;/TITLE&gt;
&lt;/HEAD&gt;

&lt;BODY bgcolor="#ffffcc"&gt;

&lt;first:loop times="4"&gt;
Welcome to Custom Tags Programming.&lt;BR&gt;
&lt;/first:loop&gt;

&lt;/BODY&gt;
&lt;/HTML&gt;
</PRE></TD></TR></TBODY></TABLE><SPAN class=sp20></SPAN><BR><!-- END VCD7 CODE SAMPLE COMPONENT  -->
<P>Finally, if you request <I>loops.jsp</I> from a browser, you would see output similar to that in Figure 5. </P>
<P>
<CENTER><IMG src="http://java.sun.com./developer/technicalArticles/xml/WebAppDev3/fig5.gif"><BR><I>Figure 5: Testing loops.jsp</I> </CENTER>
<P></P>
<H4>Programming Guidelines</H4>
<P>Here are a few guidelines to keep in mind when developing JSP tag libraries: </P>
<UL>
<LI><I>Keep it simple</I>: if a tag requires several attributes, try to break it up into several tags. 
<LI><I>Make it usable</I>: consult the users of the tags (HTML developers) to achieve a high degree of usability. 
<LI><I>Do not invent a programming language in JSP</I>: do not develop custom tags that allow users to write explicit programs. 
<LI><I>Try not to reinvent the wheel</I>: there are several JSP tag libraries available, such as the <A href="http://jakarta.apache.org/taglibs">Jakarta Taglibs Project</A>. Check to see if what you want is already available so you do not have to reinvent the wheel. </LI></UL>
<H4>Conclusion</H4>
<P>In <A href="http://java.sun.com./developer/technicalArticles/xml/WebAppDev2/">Web Application Development with JSP and XML Part II: JSP with XML in mind</A>, we have seen how to parse XML documents. But even the untrained eye would have noticed that we have embedded lots of the parsing (or logic) code in JSP. Even though we have used JavaBeans to encapsulate much of the Java code, we still ended up with JavaServer Pages mixing program logic with presentation. </P>
<P>Custom tags help you improve the separation of program logic (parsing and iteration in Part II), and presentation. The various examples in this article show how to develop and deploy simple and advanced custom tags. As an exercise, you may want to rewrite the SAX and DOM examples in Part II as tag libraries. Also, you may wish to look at what the <A href="http://jakarta.apache.org/taglibs">Jakarta Taglibs Project</A> has to offer for XML parsing and XSL transformations. It provides tag libraries for other things as well. </P>
<H4>For more information</H4>
<P><BR><A href="http://java.sun.com./products/jsp/index.jsp">Javaserver Pages, Dynamically Generated Web Content</A> <BR><A href="http://java.sun.com./products/jsp/taglibraries/index.jsp">Javaserver Pages Tag Libraries</A> <BR><A href="http://jakarta.apache.org/taglibs/">Jakarta Taglibs Project</A> <BR><A href="http://java.sun.com./developer/technicalArticles/javaserverpages/JakartaTaglibs">The Jakarta Taglibs Project--Part I</A> <BR><A href="http://jcp.org/jsr/detail/052.jsp">A Standard Tag Library for JavaServer Pages</A> </P>
<H4>About the author</H4>
<P><A href='mailto:"qmahmoud@javacourses.com"'>Qusay H. Mahmoud</A> provides Java consulting and training services. He has published dozens of articles on Java, and is the author of <I>Distributed Programming with Java</I> (Manning Publications, 1999). </P><img src ="http://www.blogjava.net/kapok/aggbug/4133.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-10 10:55 <a href="http://www.blogjava.net/kapok/archive/2005/05/10/4133.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>instanceof, isAssignable, isInstance</title><link>http://www.blogjava.net/kapok/archive/2005/05/09/4107.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 09 May 2005 02:12:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/09/4107.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4107.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/09/4107.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4107.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4107.html</trackback:ping><description><![CDATA[<P>public class TestClass {</P>
<P>&nbsp;public static void main(String[] args) {<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;Parent parent = new Parent();<BR>&nbsp;&nbsp;Child child = new Child();<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;if (child instanceof Child){<BR>&nbsp;&nbsp;&nbsp;System.out.println("child instanceof Child is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("child instanceof Child is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;if (child instanceof Parent){<BR>&nbsp;&nbsp;&nbsp;System.out.println("child instanceof Parent is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("child instanceof Parent is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;if (child instanceof Person){<BR>&nbsp;&nbsp;&nbsp;System.out.println("child instanceof Person is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("child instanceof Person is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;if (Child.class.isInstance(child)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Child.class.isInstance(child) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Child.class.isInstance(child) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;if (Parent.class.isInstance(child)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Parent.class.isInstance(child) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Parent.class.isInstance(child) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;if (Person.class.isInstance(child)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Person.class.isInstance(child) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Person.class.isInstance(child) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;if (Child.class.isAssignableFrom(Child.class)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Child.class.isAssignableFrom(Child.class) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Child.class.isAssignableFrom(Child.class) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;if (Child.class.isAssignableFrom(Parent.class)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Child.class.isAssignableFrom(Parent.class) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Child.class.isAssignableFrom(Parent.class) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;if (Child.class.isAssignableFrom(Person.class)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Child.class.isAssignableFrom(Person.class) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Child.class.isAssignableFrom(Person.class) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;//Parent class implemented the Interface 'Person'<BR>&nbsp;&nbsp;if (Parent.class.isAssignableFrom(Person.class)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Parent.class.isAssignableFrom(Person.class) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Parent.class.isAssignableFrom(Person.class) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;if (Child.class.isAssignableFrom(Child.class)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Child.class.isAssignableFrom(Child.class) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Child.class.isAssignableFrom(Child.class) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;if (Parent.class.isAssignableFrom(Child.class)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Parent.class.isAssignableFrom(Child.class) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Parent.class.isAssignableFrom(Child.class) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;if (Person.class.isAssignableFrom(Child.class)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Person.class.isAssignableFrom(Child.class) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Person.class.isAssignableFrom(Child.class) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;//Parent class implemented the Interface 'Person'<BR>&nbsp;&nbsp;if (Person.class.isAssignableFrom(Parent.class)){<BR>&nbsp;&nbsp;&nbsp;System.out.println("Person.class.isAssignableFrom(Parent.class) is true!");<BR>&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;System.out.println("Person.class.isAssignableFrom(Parent.class) is false!");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;<BR>&nbsp;}<BR>}<BR><BR><BR><BR><BR><BR>public class Child extends Parent {</P>
<P>&nbsp;public static void main(String[] args) {<BR>&nbsp;}<BR>}<BR><BR><BR><BR>public class Parent implements Person {</P>
<P>&nbsp;public static void main(String[] args) {<BR>&nbsp;}<BR>}<BR><BR><BR><BR>public interface Person {</P>
<P>}<BR><BR><BR></P><img src ="http://www.blogjava.net/kapok/aggbug/4107.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-09 10:12 <a href="http://www.blogjava.net/kapok/archive/2005/05/09/4107.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>构造函数</title><link>http://www.blogjava.net/kapok/archive/2005/05/09/4097.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 08 May 2005 16:15:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/09/4097.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4097.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/09/4097.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4097.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4097.html</trackback:ping><description><![CDATA[<A href="http://www.pconline.com.cn/pcedu/empolder/life/0405/385800.html">http://www.pconline.com.cn/pcedu/empolder/life/0405/385800.html</A><img src ="http://www.blogjava.net/kapok/aggbug/4097.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-09 00:15 <a href="http://www.blogjava.net/kapok/archive/2005/05/09/4097.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>正确使用String类的几点注意</title><link>http://www.blogjava.net/kapok/archive/2005/05/09/4096.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 08 May 2005 16:06:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/09/4096.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4096.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/09/4096.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4096.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4096.html</trackback:ping><description><![CDATA[<P style="FONT-WEIGHT: bolder; COLOR: #525252" align=center><A href="http://www.softhouse.com.cn/html/200410/2004102714063700001427.html">http://www.softhouse.com.cn/html/200410/2004102714063700001427.html</A><BR><BR>正确使用String类的几点注意</P>
<P>&nbsp;&nbsp;&nbsp; java.lang.String类对大家来说最熟悉不过了，我们写java程序很少能不用String的。本文讲述如何正确的使用String，内容主要涉及初始化、串联和比较等操作。&nbsp;</P>
<P>&nbsp;&nbsp;&nbsp; 首先我们必须清楚的一点是String类是final类型的，因此你不可以继承这个类、不能修改这个类。我们使用String的时候非常简单，通常都是String s = "hello"，但是Java API中同时提供了一个构造函数为String(String s)，因此你也可以这样使用String s = new String("hello")，对于后面这样初始化一个String的方式是不推荐的，因为new操作符意味着将会在heap上生成一个新的对象，如果这样的操作发生在一个循环中，那么代价是惨重的。比如<BR>for(int i = 0;i&lt;1000;i++)<BR>{<BR>&nbsp;&nbsp;&nbsp; String s = new String("hello");<BR>}<BR>&nbsp;&nbsp;&nbsp; 这将会创建1000个String类型的对象，由于String类是final的，因此这样的操作事实上是每次都生成了一个新的String对象的。如果你使用String s = "hello";那么就可以实现复用了，为什么可以复用呢，下面会有解释。</P>
<P>&nbsp;&nbsp;&nbsp; 当我们使用"+"实现串联操作的时候，比如String s = "hello"+"world";其实是通过StringBuffer类的append()方法实现的，最后返回String给s。如果有兴趣的话，你可以写一个简单的例子，然后用javap看看虚拟机是如何工作的。在使用串联的时候我们同样应该注意String是final类，如果你需要多次串联比如：<BR>String sql = "xxx";<BR>sql = "xxxx";<BR>sql = "ssssss";<BR>那么为了提高效率节省空间，我们应该自己用StringBuffer来替代"+";</P>
<P>&nbsp;&nbsp;&nbsp; 通常对String的比较有两种情况，一个是使用==，另一个是使用equals()方法，注意==是对对象的地址进行比较的，而String中的equals()方法是覆盖了Object类的方法，并且实现为对String对象的内容的比较。所以String s1 = new String("hello");String s2 = new String("hello")，我们对s1和s2进行上述比较的时候，前者应该返回false,因为使用new生成的是两个不同的对象。后者应该返回true因为他们的内容是一样的，都是"hello"。那么如果我们还有一个String s3 = "hello";他和s1的比较应该是什么样子的呢，答案是s1==s3为false，equals的比较位true。<STRONG><FONT color=#ff1493 size=5>事实上String类是维持着一个String池的，这个池初始化为空的，当我们String x = "hello"的时候，hello就会被放入这个池中，当我们再次String y = "hello"的时候，他首先去检查池中是否存在一个和hello内容一样的对象，如果存在的话就会把这个引用返回给y,如果不存在的话，就会创建一个并放入到池中。</FONT></STRONG>这样就实现了复用。在String有一个方法intern()他可以把String的对象放入到池冲并返回池中的对象。如果我们对s1(String s1 = new String("hello"))调用intern,s1 = s1.intern()这时候，我们再把s1和s3进行“==”的判断，你会发现结果返回true!<BR>&nbsp;&nbsp;&nbsp; 看下面的例子</P>
<P>public class StringTest<BR>{</P>
<P>&nbsp;&nbsp;&nbsp; public static void main(String[] args)<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String s1 = "hello";<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String s2 = new String("hello");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String s3 = new String("hello");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; testString(s1,s2,s3);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s2 = s2.intern();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("after s2.intern");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; testString(s1,s2,s3);&nbsp;&nbsp;&nbsp;</P>
<P>&nbsp;&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private static void testString(String s1,String s2,String s3)<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("s1 = s2 is "+(s1==s2));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("s2 = s3 is "+(s2==s3));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("s1.equals(s2) is "+s1.equals(s2));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("s2.equals(s3) is "+s2.equals(s3));<BR>&nbsp;&nbsp;&nbsp; }<BR>}<BR>输出结果为<BR>s1 = s2 is false<BR>s2 = s3 is false<BR>s1.equals(s2) is true<BR>s2.equals(s3) is true<BR>after s2.intern<BR>s1 = s2 is true<BR>s2 = s3 is false<BR>s1.equals(s2) is true<BR>s2.equals(s3) is true</P><img src ="http://www.blogjava.net/kapok/aggbug/4096.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-09 00:06 <a href="http://www.blogjava.net/kapok/archive/2005/05/09/4096.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在JSP中处理虚拟路径 </title><link>http://www.blogjava.net/kapok/archive/2005/04/27/3853.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Wed, 27 Apr 2005 08:14:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/27/3853.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3853.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/27/3853.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3853.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3853.html</trackback:ping><description><![CDATA[<SPAN id=ContentLabel style="PADDING-RIGHT: 10px; DISPLAY: block; PADDING-LEFT: 10px; PADDING-BOTTOM: 0px; PADDING-TOP: 0px"><A href="http://www.ziyuan8.com/down/Article/Catalog4/12.html">http://www.ziyuan8.com/down/Article/Catalog4/12.html</A><BR><BR>摘要<BR>　　在为服务器端组件编程时，你很可能要从相对于web根的路径来取得某个文件的真实路径，但此文件实际上在站点的一个虚拟路径上。<BR>什么是虚拟路径？<BR>　　在一个web服务器上，虚拟路径将物理上分离的各文件组合在一起，放在同一个站点路径上，在应用服务器上，每个应用定位于其自己的虚拟路径上，实际上相互之间有着完美地分离。<BR>getRealPath()方法<BR>　　JSP servlet API提供了getRealPath(path)方法，返回给定虚拟路径的真实路径，如果转换错误，则返回null。<BR>getRealPath语法定义：<BR>　　public java.lang.String getRealPath(java.lang.String path)<BR>　　返回一个字符串，包含一个给定虚拟路径的真实路径。例如，虚拟路径 "/index.html"<BR>不管在服务器文件系统上具有怎样的真实路径，使用"/index.html"总可以找到它。返回的真实路径使用了相近于servlet容器(srvlet container)所在计算机或操作系统的格式，包含了适当的路径分隔符。如果servlet容器无法转换则这个方法将返回null。<BR>　　参数：<BR>　 　　 path -一个描述了虚拟路径的字符串<BR>　　返回值：<BR>　 　　 描述真实路径的字符串或者null<BR>遗憾的是，getRealPath常常返回不同的东西，这取决于服务器或jsp文件调用此方法的路径位置。<BR>一个example站点<BR>假设我们的站点组织如下：<BR>根路径包含了我们的站点的根： http://address/<BR>a_virtual目录包含了我们站点提供的虚拟路径的文件，例如：<BR>http://addess/virtual_dir/<BR>我们查找file1.txt和file2.txt的真实路径，它们一个在站点根路径下，一个在虚拟路径下。<BR>getRealPath("/file1.txt") 应该返回“C:\site\site_root\file1.txt"，<BR>getRealPath("/virtual_dir/file2.txt")应该返回"C:\site\a_virtual\file2.txt"<BR>getRealPath("/file3.txt")应该返回null，因为这个文件不存在。<BR>但getRealPath()并不总是返回同样的结果，这还取决与你使用的js引擎。<BR>JSP引擎<BR>Tomcat 3.1<BR>Tomcat返回的结果具有应用的独立性（application dependant）：<BR>　　它取决与调用getRealPath方法的那个jsp文件所在的位置。<BR>　　实际上，当page1.jsp (位于站点根处)对file1.txt和file2.txt调用txtgetRealPath(), 它返回正确的结果。(这是在tomcat 3.1, 3.0版则对file2.txt返回错误的路径)<BR>但是当page2.jsp(位于另一个应用,在一个虚拟路径中)调用getRealPath，它返回了错误的路径：它连接了该jsp文件所在的路径和请求的虚拟路径。<BR>　　例如，从page2.jsp中调用getRealPath(/file1.txt)将返回 C:\site\a_virtual\file1.txt。<BR>　　这一行为其实是使不同的应用相互独立的典型的处理方法。<BR>JRun 2.3.3和INPRISE APPLICATION SERVER 4.0 (IAS)<BR>JRun和IAS对file1.txt和file2.txt都返回正确的结果。<BR>　　然而所有这些引擎有一个共同的行为： 当getRealPath处理不存在的文件时，它们都不返回null！<BR>解决之道<BR>　　既然getRealPath总是返回一个路径，我们怎么知道它是否正确呢？最简单的方法是检查这个返回的路径是否存在。<BR>　　这就是isVirtual方法要做的：在对一个给定的文件调用getRealPath以后，它使用了java.io来<BR>存取这个文件，于是就可以知道它是否存在。<BR>/**<BR>* isVirtual<BR>*<BR>* Check if the path name is a virtual or not.<BR>*<BR>* @param pathName The name of the path to check.<BR>*/<BR>private boolean isVirtual(String pathName) {<BR>　 // Check if it is a virtual path<BR>　 if (m_application.getRealPath(pathName)!=null) {<BR>　 　 java.io.File virtualFile = new java.io.File(m_application.getRealPath(pathName));<BR>　 　 if (virtualFile.exists()) {return true;}<BR>　 　 else {return false;}<BR>　 }<BR>　 else {return false;}<BR>}</SPAN><img src ="http://www.blogjava.net/kapok/aggbug/3853.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-27 16:14 <a href="http://www.blogjava.net/kapok/archive/2005/04/27/3853.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>webwork 阅读备忘录</title><link>http://www.blogjava.net/kapok/archive/2005/04/19/3442.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 19 Apr 2005 02:19:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/19/3442.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3442.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/19/3442.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3442.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3442.html</trackback:ping><description><![CDATA[<A href="http://wiki.opensymphony.com/display/WW/Templates">http://wiki.opensymphony.com/display/WW/Templates</A><img src ="http://www.blogjava.net/kapok/aggbug/3442.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-19 10:19 <a href="http://www.blogjava.net/kapok/archive/2005/04/19/3442.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java模式设计之单例模式（四）</title><link>http://www.blogjava.net/kapok/archive/2005/04/18/3414.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 18 Apr 2005 06:41:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/18/3414.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3414.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/18/3414.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3414.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3414.html</trackback:ping><description><![CDATA[<TABLE height=105 cellSpacing=0 cellPadding=0 width=760 align=center border=0>
<TBODY>
<TR>
<TD class=title vAlign=center align=middle height=55><FONT class=f22><B><A href="http://www.uml.org.cn/j2ee/j2ee122404.htm">http://www.uml.org.cn/j2ee/j2ee122404.htm</A><BR><BR>Java模式设计之单例模式（四）</B></FONT><FONT color=#ff0000 size=3><B><!-- #EndEditable --></B></FONT></TD></TR>
<TR>
<TD class=formtitle align=middle height=50><!-- #BeginEditable "2" --><FONT color=#000080><SPAN style="FONT-FAMILY: 新細明體; mso-bidi-font-family: SimSun; mso-font-kerning: 0pt; mso-ascii-font-family: Comic Sans MS; mso-hansi-font-family: Comic Sans MS">阎宏 作者授权</SPAN></FONT><SPAN lang=EN-US style="COLOR: black; FONT-FAMILY: 'Comic Sans MS'; mso-bidi-font-family: SimSun; mso-font-kerning: 0pt"><?XML:NAMESPACE PREFIX = O /><O:P> </O:P></SPAN><!-- #EndEditable --></TD></TR></TBODY></TABLE>
<TABLE height=65 cellSpacing=0 cellPadding=0 width=760 align=center border=0>
<TBODY>
<TR>
<TD class=content height=65><!-- #BeginEditable "3" -->
<TABLE width="85%" align=center border=0>
<TBODY>
<TR>
<TD class=content>　　不完全的单例类&nbsp;<BR><BR>　　什么是不完全的单例类<BR><BR>　　估计有些读者见过下面这样的“不完全”的单例类。<BR><BR>　　代码清单10：“不完全”单例类<BR><BR>package com.javapatterns.singleton.demos;&nbsp;<BR>public class LazySingleton&nbsp;<BR>{&nbsp;<BR>private static LazySingleton&nbsp;<BR>m_instance = null;&nbsp;<BR>/**&nbsp;<BR>* 公开的构造子，外界可以直接实例化&nbsp;<BR>*/&nbsp;<BR>public LazySingleton() { }&nbsp;<BR>/**&nbsp;<BR>* 静态工厂方法&nbsp;<BR>* @return 返还LazySingleton 类的惟一实例&nbsp;<BR>*/&nbsp;<BR>synchronized public static&nbsp;<BR>LazySingleton getInstance()&nbsp;<BR>{&nbsp;<BR>if (m_instance == null)&nbsp;<BR>{&nbsp;<BR>m_instance = new LazySingleton();&nbsp;<BR>}&nbsp;<BR>return m_instance;&nbsp;<BR>}<BR>}&nbsp;<BR><BR>　　上面的代码乍看起来是一个“懒汉”式单例类，仔细一看，发现有一个公开的构造子。由于外界可以使用构造子创建出任意多个此类的实例，这违背了单例类只能有一个（或有限个）实例的特性，因此这个类不是完全的单例类。这种情况有时会出现，比如javax.swing.TimerQueue 便是一例，关于这个类，请参见《Java与模式》一书中的“观察者模式与Swing 定时器” 一章。<BR><BR>　　造成这种情况出现的原因有以下几种可能：<BR><BR>　　（1） 初学者的错误。许多初学者没有认识到单例类的构造子不能是公开的，因此犯下这个错误。有些初学Java 语言的学员甚至不知道一个Java 类的构造子可以不是公开的。在 这种情况下，设计师可能会通过自我约束，也就是说不去调用构造子的办法，将这个不完全的单例类在使用中作为一个单例类使用。<BR><BR>　　在这种情况下，一个简单的矫正办法，就是将公开的构造子改为私有的构造子。<BR><BR>　　（2） 当初出于考虑不周，将一个类设计成为单例类，后来发现此类应当有多于一个的实例。为了弥补错误， 干脆将构造子改为公开的，以便在需要多于一个的实例时， 可以随时调用构造子创建新的实例。要纠正这种情况较为困难，必须根据具体情况做出改进的决定。如果一个类在最初被设计成为单例类，但后来发现实际上此类应当有有限多个实例，这时候应当考虑是否将单例类改为多例类（Multiton）。<BR><BR>　　（3）设计师的Java 知识很好，而且也知道单例模式的正确使用方法，但是还是有意使用这种不完全的单例模式，因为他意在使用一种“改良”的单例模式。这时候， 除去共有的构造子不符合单例模式的要求之外，这个类必须是很好的单例模式。<BR><BR>　　默认实例模式<BR><BR>　　有些设计师将这种不完全的单例模式叫做“默认实例模式”（Default Instance Pattern）。在所谓的“ 默认实例模式”里面， 一个类提供静态的方法，如同单例模式一样， 同时又提供一个公开的构造子，如同普通的类一样。<BR><BR>　　这样做的惟一好处是，这种模式允许客户端选择如何将类实例化：创建新的自己独有的实例，或者使用共享的实例。这样一来，由于没有任何的强制性措施，客户端的选择不一定是合理的选择。其结果是设计师往往不会花费时间在如何提供最好的选择上，而是不恰当地将这种选择交给客户端的程序员，这样必然会导致不理想的设计和欠考虑的实现。<BR><BR>　　本文建议读者不要这样做。 
<P><SPAN class=f14>　　<B>相关模式</B><BR><BR>　　有一些模式可以使用单例模式，如抽象工厂模式可以使用单例模式，将具体工厂类设计成单例类；建造模式可以使用单例模式，将具体建造类设计成单例类。<BR><BR>　　<B>多例（Multiton）模式</B><BR><BR>　　正如同本章所说的，单例模式的精神可以推广到多于一个实例的情况。这时候这种类叫做多例类，这种模式叫做多例模式。单例类（左）和多例类（右）的类图如下所示。<BR><BR><IMG onerror="this.src='http://www.yesky.com/image20010518/61675.jpg';" hspace=3 src="http://www.yesky.com/image20010518/61675.jpg" align=center vspace=1 border=1><BR><BR>　　关于多例模式，请见《Java与模式》一书中的“专题：多例（Multiton）模式与多语言支持”一章。<BR><BR>　　<STRONG>简单工厂（Simple Factory）模式</STRONG><BR><BR>　　单例模式使用了简单工厂模式（又称为静态工厂方法模式）来提供自己的实例。在上面ConfigManager 例子的代码中， 静态工厂方法getInstance() 就是静态工厂方法。在java.awt.Toolkit 类中，getDefaultToolkit() 方法就是静态工厂方法。简单工厂模式的简略类图如下所示。<BR><BR><IMG onerror="this.src='http://www.yesky.com/image20010518/61674.jpg';" hspace=3 src="http://www.yesky.com/image20010518/61674.jpg" align=center vspace=1 border=1><BR><BR>　　本章讨论了单例模式的结构和实现方法。<BR><BR>　　单例模式是一个看上去很简单的模式，很多设计师最先学会的往往是单例模式。然而，随着Java 系统日益变得复杂化和分散化，单例模式的使用变得比过去困难。本书提醒读者在分散式的Java 系统中使用单例模式时，尽量不要使用有状态的。<BR><BR>　　<B>问答题</B><BR><BR>　　1. 为什么不使用一个静态的“全程”原始变量，而要建一个类？一个静态的原始变量当然只能有一个值，自然而然不就是“单例”的吗？<BR><BR>　　2. 举例说明如何调用EagerSingleton 类。<BR><BR>　　3. 举例说明如何调用RegSingleton 类和RegSingletonChild 类。<BR><BR>　　4. 请问java.lang.Math 类和java.lang.StrictMath 类是否是单例模式？<BR><BR>　　5. 我们公司只购买了一个JDBC 驱动软件的单用户使用许可，可否使用单例模式管理通过JDBC 驱动软件连接的数据库？<BR><BR></SPAN><BR></P><SPAN class=f14>　　<B>问答题答案</B><BR><BR>　　1. 单例模式可以提供很复杂的逻辑，而一个原始变量不能自已初始化，不可能有继承的关系，没有内部结构。因此单例模式有很多优越之处。<BR><BR>　　在Java 语言里并没有真正的“全程”变量，一个变量必须属于某一个类或者某一个实例。而在复杂的程序当中，一个静态变量的初始化发生在哪里常常是一个不易确定的问题。当然，使用“全程”原始变量并没有什么错误，就好像选择使用Fortran 语言而非Java语言编程并不是一种对错的问题一样。<BR><BR>　　2. 几种单例类的使用方法如下。<BR><BR>　　代码清单11：几种单例类的使用方法<BR><BR>public class RegSingletonTest<BR>{<BR>public static void main(String[] args)<BR>{<BR>//(1) Test eager<BR>System.out.println( EagerSingleton.getInstance());<BR>//(2) Test reg<BR>System.out.println(<BR>RegSingleton.getInstance(<BR>"com.javapatterns.singleton.demos.RegSingleton").about());<BR>System.out.println( RegSingleton.getInstance(null).about() );<BR>System.out.println(<BR>RegSingleton.getInstance(<BR>"com.javapatterns.singleton.demos.RegSingletonChild").about());<BR>System.out.println( RegSingletonChild.getInstance().about());<BR>}<BR>}<BR>　　3. 见上题答案。<BR><BR>　　4. 它们都不是单例类。原因如下：<BR><BR>　　这两个类均有一个私有的构造子。但是这仅仅是单例模式的必要条件，而不是充分条件。回顾在本章开始提出的单例模式的三个特性可以看出，无论是Math 还是StrictMath 都没有为外界提供任何自身的实例。实际上，这两个类都是被设计来提供静态工厂方法和常量的，因此从来就不需要它们的实例，这才是它们的构造子是私有的原因。Math和StrictMath 类的类图如下所示。<BR><BR><IMG onerror="this.src='http://www.yesky.com/image20010518/61673.jpg';" hspace=3 src="http://www.yesky.com/image20010518/61673.jpg" align=center vspace=1 border=1><BR><BR>　　5. 这样做是可行的，只是必须注意当使用在分散式系统中的时候，不一定能保证单例类实例的惟一性。</SPAN> 
<P><SPAN class=f14>　　<B>附录：双重检查成例的研究</B><BR><BR>　　成例是一种代码层次上的模式，是在比设计模式的层次更具体的层次上的代码技巧。成例往往与编程语言密切相关。双重检查成例（Double Check Idiom ）是从C 语言移植过来的一种代码模式。在C 语言里，双重检查成例常常用在多线程环境中类的晚实例化（Late Instantiation）里。<BR><BR>　　本节之所以要介绍这个成例（严格来讲，是介绍为什么这个成例不成立）， 是因为有很多人认为双重检查成例可以使用在“懒汉”单例模式里面。<BR><BR>　　<B>什么是双重检查成例</B><BR><BR>　　为了解释什么是双重检查成例，请首先看看下面没有使用任何线程安全考虑的错误例子。<BR><BR>　　从单线程的程序谈起<BR><BR>　　首先考虑一个单线程的版本。<BR><BR>　　代码清单13：没有使用任何线程安全措施的一个例子<BR><BR>// Single threaded version<BR>class Foo<BR>{<BR>　private Helper helper = null;<BR>　public Helper getHelper()<BR>　{<BR>　　if (helper == null)<BR>　　{<BR>　　　helper = new Helper();<BR>　　}<BR>　　return helper;<BR>　}<BR>　// other functions and members...<BR>}<BR>　　这是一个错误的例子，详情请见下面的说明。<BR><BR>　　写出这样的代码，本意显然是要保持在整个JVM 中只有一个Helper 的实例；因此，才会有if (helper == null) 的检查。非常明显的是，如果在多线程的环境中运行，上面的代码会有两个甚至两个以上的Helper 对象被创建出来，从而造成错误。<BR><BR>　　但是，想像一下在多线程环境中的情形就会发现，如果有两个线程A 和B 几乎同时到达if (helper == null)语句的外面的话，假设线程A 比线程B 早一点点，那么：<BR><BR>　　（1）A 会首先进入if (helper == null) 块的内部，并开始执行new Helper() 语句。此时，helper 变量仍然是null，直到线程A 的new Helper() 语句返回并给helper 变量赋值为止。<BR><BR>　　（2） 但是，线程B 并不会在if (helper == null)语句的外面等待，因为此时helper == null 是成立的，它会马上进入if (helper == null)语句块的内部。这样，线程B 会不可避免地执行helper = new Helper();语句，从而创建出第二个实例来。<BR><BR>　　（3）线程A 的helper = new Helper();语句执行完毕后，helper 变量得到了真实的对象引用，(helper == null)不再为真。第三个线程不会再进入if (helper == null) 语句块的内部了。<BR><BR>　　（4）线程B 的helper = new Helper(); 语句也执行完毕后，helper 变量的值被覆盖。但是第一个Helper 对象被线程A 引用的事实不会改变。<BR><BR>　　这时，线程A 和B 各自拥有一个独立的Helper 对象，而这是错误的。<BR><BR>　　<B>线程安全的版本</B><BR><BR>　　为了克服没有线程安全的缺点，下面给出一个线程安全的例子。<BR><BR>　　代码清单14：这是一个正确的答案<BR><BR>// Correct multithreaded version<BR>class Foo<BR>{<BR>　private Helper helper = null;<BR>　public synchronized Helper getHelper()<BR>　{<BR>　　if (helper == null)<BR>　　{<BR>　　　helper = new Helper();<BR>　　　return helper;<BR>　　}<BR>　}<BR>　// other functions and members...<BR>}<BR>　　显然，由于整个静态工厂方法都是同步化的，因此，不会有两个线程同时进入这个方法。因此，当线程A 和B 作为第一批调用者同时或几乎同时调用此方法时：<BR><BR>　　（1）早到一点的线程A 会率先进入此方法，同时线程B 会在方法外部等待。<BR><BR>　　（2） 对线程A 来说，helper 变量的值是null ，因此helper = new Helper(); 语句会被执行。<BR><BR>　　（3）线程A 结束对方法的执行，helper 变量的值不再是null。<BR><BR>　　（4）线程B 进入此方法，helper 变量的值不再是null ，因此helper = new Helper(); 语句不会被执行。线程B 取到的是helper 变量所含有的引用，也就是对线程A 所创立的Helper 实例的引用。<BR><BR>　　显然，线程A 和B 持有同一个Helper 实例，这是正确的。</SPAN> 
<P><SPAN class=f14>　　<B>画蛇添足的“双重检查”</B><BR><BR>　　但是，仔细审察上面的正确答案会发现，同步化实际上只在helper 变量第一次被赋值之前才有用。在helper 变量有了值以后，同步化实际上变成了一个不必要的瓶颈。如果能有一个方法去掉这个小小的额外开销，不是更加完美了吗？因此，就有了下面这个设计“巧妙”的双重检查成例。在读者向下继续读之前，有必要提醒一句：正如本小节的标题所标明的那样，这是一个反面教材，因为双重检查成例在Java 编译器里无法实现。<BR><BR>　　代码清单15：使用双重检查成例的懒汉式单例模式<BR><BR>
<TABLE width="100%" bgColor=#ffffff>
<TBODY>
<TR>
<TD>// Broken multithreaded version<BR>// "Double-Checked Locking" idiom<BR>class Foo<BR>{<BR>　private Helper helper = null;<BR>　public Helper getHelper()<BR>　{<BR>　　if (helper == null) //第一次检查(位置1)<BR>　　{<BR>　　　//这里会有多于一个的线程同时到达 (位置2)<BR>　　　synchronized(this)<BR>　　　{<BR>　　　　//这里在每个时刻只能有一个线程 (位置3)<BR>　　　　if (helper == null) //第二次检查 (位置4)<BR>　　　　{<BR>　　　　　helper = new Helper();<BR>　　　　}<BR>　　　}<BR>　　}<BR>　　return helper;<BR>　}<BR>　// other functions and members...<BR>}</TD></TR></TBODY></TABLE><BR>　　这是一个错误的例子，详情请见下面的解释。<BR><BR>　　对于初次接触双重检查成例的读者来说，这个技巧的思路并不明显易懂。因此，本节在这里给出一个详尽的解释。同样，这里假设线程A 和B 作为第一批调用者同时或几乎同时调用静态工厂方法。<BR><BR>　　（1） 因为线程A 和B 是第一批调用者，因此，当它们进入此静态工厂方法时，helper 变量是null。因此，线程A 和B 会同时或几乎同时到达位置1。<BR><BR>　　（2）假设线程A 会首先到达位置2，并进入synchronized(this) 到达位置3。这时，由于synchronized(this) 的同步化限制，线程B 无法到达位置3，而只能在位置2 等候。<BR><BR>　　（3）线程A 执行helper = new Helper() 语句，使得helper 变量得到一个值，即对一个Helper 对象的引用。此时，线程B 只能继续在位置2 等候。<BR><BR>　　（4）线程A 退出synchronized(this) ，返回Helper 对象，退出静态工厂方法。<BR><BR>　　（5）线程B 进入synchronized(this) 块，达到位置3，进而达到位置4。由于helper 变量已经不是null 了，因此线程B 退出synchronized(this)，返回helper 所引用的Helper 对象(也就是线程A 所创建的Helper 对象)，退出静态工厂方法。<BR><BR>　　到此为止，线程A 和线程B 得到了同一个Helper 对象。可以看到，在上面的方法<BR><BR>　　getInstance() 中，同步化仅用来避免多个线程同时初始化这个类，而不是同时调用这个静态工厂方法。如果这是正确的，那么使用这一个成例之后，“ 懒汉式”单例类就可以摆脱掉同步化瓶颈，达到一个很妙的境界。<BR><BR>　　代码清单16：使用了双重检查成例的懒汉式单例类<BR><BR><SPAN class=f14>public class LazySingleton<BR>{<BR>　private static LazySingleton m_instance = null;<BR>　private LazySingleton() { }<BR>　/**<BR>　* 静态工厂方法<BR>　*/<BR>　public static LazySingleton getInstance()<BR>　{<BR>　　if (m_instance == null)<BR>　　{<BR>　　//More than one threads might be here!!!<BR>　　synchronized(LazySingleton.class)<BR>　　{<BR>　　　if (m_instance == null)<BR>　　　{<BR>　　　　m_instance = new LazySingleton();<BR>　　　}<BR>　　}<BR>　}<BR>　return m_instance;<BR>}<BR>}</SPAN><BR>　　 这是一个错误的例子，请见下面的解释。<BR><BR>　　第一次接触到这个技巧的读者必定会有很多问题，诸如第一次检查或者第二次检查可不可以省掉等。回答是：按照多线程的原理和双重检查成例的预想方案，它们是不可以省掉的。本节不打算讲解的原因在于双重检查成例在Java 编译器中根本不能成立。<BR><BR>　　<B>双重检查成例对Java 语言编译器不成立</B><BR><BR>　　令人吃惊的是，在C 语言里得到普遍应用的双重检查成例在多数的Java 语言编译器里面并不成立[BLOCH01, GOETZ01, DCL01] 。上面使用了双重检查成例的“懒汉式”单例类，不能工作的基本原因在于，在Java 编译器中，LazySingleton 类的初始化与m_instance 变量赋值的顺序不可预料。如果一个线程在没有同步化的条件下读取m_instance 引用，并调用这个对象的方法的话，可能会发现对象的初始化过程尚未完成，从而造成崩溃。<BR><BR>　　文献[BLOCH01] 指出：一般而言，双重检查成立对Java 语言来说是不成立的。<BR><BR>　　<B>给读者的一点建议</B><BR><BR>　　有很多非常聪明的人在这个成例的Java 版本上花费了非常多的时间，到现在为止人们得出的结论是：一般而言，双重检查成例无法在现有的Java 语言编译器里工作[BLOCH01, GOETZ01, DCL01] 。<BR><BR>　　读者可能会问，是否有可能通过某种技巧对上面的双重检查的实现代码加以修改，从而使某种形式的双重检查成例能在Java 编译器下工作呢？这种可能性当然不能排除，但是除非读者对此有特别的兴趣，建议不要在这上面花费太多的时间。<BR><BR>　　在一般情况下使用饿汉式单例模式或者对整个静态工厂方法同步化的懒汉式单例模式足以解决在实际设计工作中遇到的问题。</SPAN> 
<P><BR></P></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/3414.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-18 14:41 <a href="http://www.blogjava.net/kapok/archive/2005/04/18/3414.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>单例模式完全剖析(3)---- 探究简单却又使人迷惑的单例模式</title><link>http://www.blogjava.net/kapok/archive/2005/04/18/3410.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 18 Apr 2005 06:33:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/18/3410.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3410.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/18/3410.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3410.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3410.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width=760 align=center border=0>
<TBODY>
<TR>
<TD class=title vAlign=center align=middle height=56><B><FONT color=#ff0000 size=3><A href="http://www.uml.org.cn/j2ee/200501241.htm">http://www.uml.org.cn/j2ee/200501241.htm</A><BR><BR>单例模式完全剖析(3)---- 探究简单却又使人迷惑的单例模式<BR><!-- #EndEditable --></FONT></B></TD></TR>
<TR>
<TD class=formtitle align=middle height=40><!-- #BeginEditable "2" -->kevin 翻译 选自：java研究组织<!-- #EndEditable --></TD></TR></TBODY></TABLE>
<TABLE height=65 cellSpacing=0 cellPadding=0 width=760 align=center border=0>
<TBODY>
<TR>
<TD class=content height=65><!-- #BeginEditable "3" -->
<TABLE class=content width="85%" align=center border=0>
<TBODY>
<TR>
<TD class=content vAlign=top><SPAN class=myp11><FONT id=zoom></FONT></SPAN><SPAN class=p11b></SPAN><B>使用注册表 <BR><BR>使用一个单例类注册表可以：<BR>
<UL>
<LI>在运行期指定单例类<BR>
<LI>防止产生多个单例类子类的实例<BR>在例8的单例类中，保持了一个通过类名进行注册的单例类注册表：<BR>例8&nbsp;带注册表的单例类<BR>
<DIV class=codeStyle>
<OL>
<LI><FONT color=#0000ff>import</FONT>&nbsp;java.util.<FONT class=classLink><U>HashMap</U></FONT>; 
<LI><FONT color=#0000ff>import</FONT>&nbsp;org.apache.log4j.<FONT color=#ff0000>Logger</FONT>; 
<LI>　 
<LI><FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>class</FONT>&nbsp;Singleton&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT class=classLink><U>HashMap</U></FONT>&nbsp;map&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;<FONT class=classLink><U>HashMap</U></FONT>(); 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT color=#ff0000>Logger</FONT>&nbsp;logger&nbsp;=&nbsp;<FONT color=#ff0000>Logger</FONT>.getRootLogger(); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>protected</FONT>&nbsp;Singleton()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<I><FONT color=#339900>//&nbsp;Exists&nbsp;only&nbsp;to&nbsp;thwart&nbsp;instantiation</FONT></I> 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT color=#0000ff>synchronized</FONT>&nbsp;Singleton&nbsp;getInstance(<FONT class=classLink><U>String</U></FONT>&nbsp;classname)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>if</FONT>(classname&nbsp;==&nbsp;<FONT color=#0000ff>null</FONT>)&nbsp;<FONT color=#0000ff>throw</FONT>&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;<FONT class=classLink><U>IllegalArgumentException</U></FONT>(<FONT color=#ff33ff>"Illegal&nbsp;classname"</FONT>); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Singleton&nbsp;singleton&nbsp;=&nbsp;(Singleton)map.get(classname); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>if</FONT>(singleton&nbsp;!=&nbsp;<FONT color=#0000ff>null</FONT>)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info(<FONT color=#ff33ff>"got&nbsp;singleton&nbsp;from&nbsp;map:&nbsp;"</FONT>&nbsp;+&nbsp;singleton); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>return</FONT>&nbsp;singleton; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>if</FONT>(classname.equals(<FONT color=#ff33ff>"SingeltonSubclass_One"</FONT>)) 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;singleton&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;SingletonSubclass_One();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>else</FONT>&nbsp;<FONT color=#0000ff>if</FONT>(classname.equals(<FONT color=#ff33ff>"SingeltonSubclass_Two"</FONT>)) 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;singleton&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;SingletonSubclass_Two(); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map.put(classname,&nbsp;singleton); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info(<FONT color=#ff33ff>"created&nbsp;singleton:&nbsp;"</FONT>&nbsp;+&nbsp;singleton); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>return</FONT>&nbsp;singleton; 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;<I><FONT color=#339900>//&nbsp;Assume&nbsp;functionality&nbsp;follows&nbsp;that's&nbsp;attractive&nbsp;to&nbsp;inherit</FONT></I> 
<LI>} </LI></OL></DIV><BR>这段代码的基类首先创建出子类的实例，然后把它们存储在一个Map中。但是基类却得付出很高的代价因为你必须为每一个子类替换它的getInstance()方法。幸运的是我们可以使用反射处理这个问题。<BR>
<H3>使用反射</H3><BR>在例9的带注册表的单例类中，使用反射来实例化一个特殊的类的对象。与例8相对的是通过这种实现，Singleton.getInstance()方法不需要在每个被实现的子类中重写了。<BR>例9&nbsp;使用反射实例化单例类<BR>
<DIV class=codeStyle>
<OL>
<LI><FONT color=#0000ff>import</FONT>&nbsp;java.util.<FONT class=classLink><U>HashMap</U></FONT>; 
<LI><FONT color=#0000ff>import</FONT>&nbsp;org.apache.log4j.<FONT color=#ff0000>Logger</FONT>; 
<LI>　 
<LI><FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>class</FONT>&nbsp;Singleton&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT class=classLink><U>HashMap</U></FONT>&nbsp;map&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;<FONT class=classLink><U>HashMap</U></FONT>(); 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT color=#ff0000>Logger</FONT>&nbsp;logger&nbsp;=&nbsp;<FONT color=#ff0000>Logger</FONT>.getRootLogger(); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>protected</FONT>&nbsp;Singleton()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<I><FONT color=#339900>//&nbsp;Exists&nbsp;only&nbsp;to&nbsp;thwart&nbsp;instantiation</FONT></I> 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT color=#0000ff>synchronized</FONT>&nbsp;Singleton&nbsp;getInstance(<FONT class=classLink><U>String</U></FONT>&nbsp;classname)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Singleton&nbsp;singleton&nbsp;=&nbsp;(Singleton)map.get(classname); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>if</FONT>(singleton&nbsp;!=&nbsp;<FONT color=#0000ff>null</FONT>)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info(<FONT color=#ff33ff>"got&nbsp;singleton&nbsp;from&nbsp;map:&nbsp;"</FONT>&nbsp;+&nbsp;singleton); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>return</FONT>&nbsp;singleton; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>try</FONT>&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;singleton&nbsp;=&nbsp;(Singleton)<FONT class=classLink><U>Class</U></FONT>.forName(classname).newInstance(); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>ClassNotFoundException</U></FONT>&nbsp;cnf)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"Couldn't&nbsp;find&nbsp;class&nbsp;"</FONT>&nbsp;+&nbsp;classname);&nbsp;&nbsp;&nbsp;&nbsp; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>InstantiationException</U></FONT>&nbsp;ie)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"Couldn't&nbsp;instantiate&nbsp;an&nbsp;object&nbsp;of&nbsp;type&nbsp;"</FONT>&nbsp;+&nbsp;classname);&nbsp;&nbsp;&nbsp;&nbsp; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>IllegalAccessException</U></FONT>&nbsp;ia)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"Couldn't&nbsp;access&nbsp;class&nbsp;"</FONT>&nbsp;+&nbsp;classname);&nbsp;&nbsp;&nbsp;&nbsp; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map.put(classname,&nbsp;singleton); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info(<FONT color=#ff33ff>"created&nbsp;singleton:&nbsp;"</FONT>&nbsp;+&nbsp;singleton); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>return</FONT>&nbsp;singleton; 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>} </LI></OL></DIV><BR><BR>关于单例类的注册表应该说明的是：它们应该被封装在它们自己的类中以便最大限度的进行复用。<BR><BR>
<H3>封装注册表</H3><BR>例10列出了一个单例注册表类。<BR>例10&nbsp;一个SingletonRegistry类<BR>
<DIV class=codeStyle>
<OL>
<LI><FONT color=#0000ff>import</FONT>&nbsp;java.util.<FONT class=classLink><U>HashMap</U></FONT>; 
<LI><FONT color=#0000ff>import</FONT>&nbsp;org.apache.log4j.<FONT color=#ff0000>Logger</FONT>; 
<LI>　 
<LI><FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>class</FONT>&nbsp;SingletonRegistry&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;SingletonRegistry&nbsp;REGISTRY&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;SingletonRegistry(); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT class=classLink><U>HashMap</U></FONT>&nbsp;map&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;<FONT class=classLink><U>HashMap</U></FONT>(); 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT color=#ff0000>Logger</FONT>&nbsp;logger&nbsp;=&nbsp;<FONT color=#ff0000>Logger</FONT>.getRootLogger(); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>protected</FONT>&nbsp;SingletonRegistry()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<I><FONT color=#339900>//&nbsp;Exists&nbsp;to&nbsp;defeat&nbsp;instantiation</FONT></I> 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT color=#0000ff>synchronized</FONT>&nbsp;<FONT class=classLink><U>Object</U></FONT>&nbsp;getInstance(<FONT class=classLink><U>String</U></FONT>&nbsp;classname)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT class=classLink><U>Object</U></FONT>&nbsp;singleton&nbsp;=&nbsp;map.get(classname); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>if</FONT>(singleton&nbsp;!=&nbsp;<FONT color=#0000ff>null</FONT>)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>return</FONT>&nbsp;singleton; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>try</FONT>&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;singleton&nbsp;=&nbsp;<FONT class=classLink><U>Class</U></FONT>.forName(classname).newInstance(); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info(<FONT color=#ff33ff>"created&nbsp;singleton:&nbsp;"</FONT>&nbsp;+&nbsp;singleton); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>ClassNotFoundException</U></FONT>&nbsp;cnf)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"Couldn't&nbsp;find&nbsp;class&nbsp;"</FONT>&nbsp;+&nbsp;classname);&nbsp;&nbsp;&nbsp;&nbsp; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>InstantiationException</U></FONT>&nbsp;ie)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"Couldn't&nbsp;instantiate&nbsp;an&nbsp;object&nbsp;of&nbsp;type&nbsp;"</FONT>&nbsp;+&nbsp; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;classname);&nbsp;&nbsp;&nbsp;&nbsp; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>IllegalAccessException</U></FONT>&nbsp;ia)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"Couldn't&nbsp;access&nbsp;class&nbsp;"</FONT>&nbsp;+&nbsp;classname);&nbsp;&nbsp;&nbsp;&nbsp; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map.put(classname,&nbsp;singleton); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>return</FONT>&nbsp;singleton; 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>} </LI></OL></DIV><BR>注意我是把SingletonRegistry类作为一个单例模式实现的。我也通用化了这个注册表以便它能存储和取回任何类型的对象。例11显示了的Singleton类使用了这个注册表。<BR>例11&nbsp;使用了一个封装的注册表的Singleton类<BR>
<DIV class=codeStyle>
<OL>
<LI><FONT color=#0000ff>import</FONT>&nbsp;java.util.<FONT class=classLink><U>HashMap</U></FONT>; 
<LI><FONT color=#0000ff>import</FONT>&nbsp;org.apache.log4j.<FONT color=#ff0000>Logger</FONT>; 
<LI>　 
<LI><FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>class</FONT>&nbsp;Singleton&nbsp;{ 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>protected</FONT>&nbsp;Singleton()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<I><FONT color=#339900>//&nbsp;Exists&nbsp;only&nbsp;to&nbsp;thwart&nbsp;instantiation.</FONT></I> 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;Singleton&nbsp;getInstance()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>return</FONT>&nbsp;(Singleton)SingletonRegistry.REGISTRY.getInstance(classname); 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>} </LI></OL></DIV><BR>上面的Singleton类使用那个注册表的唯一实例通过类名取得单例对象。<BR>现在我们已经知道如何实现线程安全的单例类和如何使用一个注册表去在运行期指定单例类名，接着让我们考查一下如何安排类载入器和处理序列化。<BR>
<H3>Classloaders</H3><BR>在许多情况下，使用多个类载入器是很普通的--包括servlet容器--所以不管你在实现你的单例类时是多么小心你都最终可以得到多个单例类的实例。如果你想要确保你的单例类只被同一个的类载入器装入，那你就必须自己指定这个类载入器;例如：<BR>
<DIV class=codeStyle>
<OL>
<LI><FONT color=#0000ff>private</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT class=classLink><U>Class</U></FONT>&nbsp;getClass(<FONT class=classLink><U>String</U></FONT>&nbsp;classname)&nbsp; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>throws</FONT>&nbsp;<FONT class=classLink><U>ClassNotFoundException</U></FONT>&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT class=classLink><U>ClassLoader</U></FONT>&nbsp;classLoader&nbsp;=&nbsp;<FONT class=classLink><U>Thread</U></FONT>.currentThread().getContextClassLoader(); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>if</FONT>(classLoader&nbsp;==&nbsp;<FONT color=#0000ff>null</FONT>) 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;classLoader&nbsp;=&nbsp;Singleton.<FONT color=#0000ff>class</FONT>.getClassLoader(); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>return</FONT>&nbsp;(classLoader.loadClass(classname)); 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>} </LI></OL></DIV><BR>这个方法会尝试把当前的线程与那个类载入器相关联;如果classloader为null，这个方法会使用与装入单例类基类的那个类载入器。这个方法可以用Class.forName()代替。<BR>
<H3>序列化</H3><BR>如果你序列化一个单例类，然后两次重构它，那么你就会得到那个单例类的两个实例，除非你实现readResolve()方法，像下面这样：<BR>例12&nbsp;一个可序列化的单例类<BR>
<DIV class=codeStyle>
<OL>
<LI><FONT color=#0000ff>import</FONT>&nbsp;org.apache.log4j.<FONT color=#ff0000>Logger</FONT>; 
<LI>　 
<LI><FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>class</FONT>&nbsp;Singleton&nbsp;<FONT color=#0000ff>implements</FONT>&nbsp;java.io.<FONT class=classLink><U>Serializable</U></FONT>&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;Singleton&nbsp;INSTANCE&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;Singleton(); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>protected</FONT>&nbsp;Singleton()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<I><FONT color=#339900>//&nbsp;Exists&nbsp;only&nbsp;to&nbsp;thwart&nbsp;instantiation.</FONT></I> 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>[b]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;<FONT class=classLink><U>Object</U></FONT>&nbsp;readResolve()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>return</FONT>&nbsp;INSTANCE; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}[/b]} </LI></OL></DIV><BR>上面的单例类实现从readResolve()方法中返回一个唯一的实例;这样无论Singleton类何时被重构，它都只会返回那个相同的单例类实例。<BR>例13测试了例12的单例类：<BR>例13&nbsp;测试一个可序列化的单例类<BR>
<DIV class=codeStyle>
<OL>
<LI><FONT color=#0000ff>import</FONT>&nbsp;java.io.*; 
<LI><FONT color=#0000ff>import</FONT>&nbsp;org.apache.log4j.<FONT color=#ff0000>Logger</FONT>; 
<LI><FONT color=#0000ff>import</FONT>&nbsp;junit.framework.<FONT class=classLink><U>Assert</U></FONT>; 
<LI><FONT color=#0000ff>import</FONT>&nbsp;junit.framework.<FONT class=classLink><U>TestCase</U></FONT>; 
<LI>　 
<LI><FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>class</FONT>&nbsp;SingletonTest&nbsp;<FONT color=#0000ff>extends</FONT>&nbsp;<FONT class=classLink><U>TestCase</U></FONT>&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;Singleton&nbsp;sone&nbsp;=&nbsp;<FONT color=#0000ff>null</FONT>,&nbsp;stwo&nbsp;=&nbsp;<FONT color=#0000ff>null</FONT>; 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;<FONT color=#0000ff>static</FONT>&nbsp;<FONT color=#ff0000>Logger</FONT>&nbsp;logger&nbsp;=&nbsp;<FONT color=#ff0000>Logger</FONT>.getRootLogger(); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>public</FONT>&nbsp;SingletonTest(<FONT class=classLink><U>String</U></FONT>&nbsp;name)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>super</FONT>(name); 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>void</FONT>&nbsp;setUp()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sone&nbsp;=&nbsp;Singleton.INSTANCE; 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stwo&nbsp;=&nbsp;Singleton.INSTANCE; 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>void</FONT>&nbsp;testSerialize()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info(<FONT color=#ff33ff>"testing&nbsp;singleton&nbsp;serialization..."</FONT>); 
<LI>[b]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writeSingleton(); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Singleton&nbsp;s1&nbsp;=&nbsp;readSingleton(); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Singleton&nbsp;s2&nbsp;=&nbsp;readSingleton(); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT class=classLink><U>Assert</U></FONT>.assertEquals(<FONT color=#0000ff>true</FONT>,&nbsp;s1&nbsp;==&nbsp;s2);[/b]&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;<FONT color=#0000ff>void</FONT>&nbsp;writeSingleton()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>try</FONT>&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT class=classLink><U>FileOutputStream</U></FONT>&nbsp;fos&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;<FONT class=classLink><U>FileOutputStream</U></FONT>(<FONT color=#ff33ff>"serializedSingleton"</FONT>); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT class=classLink><U>ObjectOutputStream</U></FONT>&nbsp;oos&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;<FONT class=classLink><U>ObjectOutputStream</U></FONT>(fos); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Singleton&nbsp;s&nbsp;=&nbsp;Singleton.INSTANCE; 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oos.writeObject(Singleton.INSTANCE); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oos.flush(); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>NotSerializableException</U></FONT>&nbsp;se)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"Not&nbsp;Serializable&nbsp;Exception:&nbsp;"</FONT>&nbsp;+&nbsp;se.getMessage()); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>IOException</U></FONT>&nbsp;iox)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"IO&nbsp;Exception:&nbsp;"</FONT>&nbsp;+&nbsp;iox.getMessage()); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>private</FONT>&nbsp;Singleton&nbsp;readSingleton()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Singleton&nbsp;s&nbsp;=&nbsp;<FONT color=#0000ff>null</FONT>; 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>try</FONT>&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT class=classLink><U>FileInputStream</U></FONT>&nbsp;fis&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;<FONT class=classLink><U>FileInputStream</U></FONT>(<FONT color=#ff33ff>"serializedSingleton"</FONT>); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT class=classLink><U>ObjectInputStream</U></FONT>&nbsp;ois&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;<FONT class=classLink><U>ObjectInputStream</U></FONT>(fis); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s&nbsp;=&nbsp;(Singleton)ois.readObject(); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>ClassNotFoundException</U></FONT>&nbsp;cnf)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"Class&nbsp;Not&nbsp;Found&nbsp;Exception:&nbsp;"</FONT>&nbsp;+&nbsp;cnf.getMessage()); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>NotSerializableException</U></FONT>&nbsp;se)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"Not&nbsp;Serializable&nbsp;Exception:&nbsp;"</FONT>&nbsp;+&nbsp;se.getMessage()); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>catch</FONT>(<FONT class=classLink><U>IOException</U></FONT>&nbsp;iox)&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.fatal(<FONT color=#ff33ff>"IO&nbsp;Exception:&nbsp;"</FONT>&nbsp;+&nbsp;iox.getMessage()); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>return</FONT>&nbsp;s; 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>&nbsp;&nbsp;&nbsp;<FONT color=#0000ff>public</FONT>&nbsp;<FONT color=#0000ff>void</FONT>&nbsp;testUnique()&nbsp;{ 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info(<FONT color=#ff33ff>"testing&nbsp;singleton&nbsp;uniqueness..."</FONT>); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Singleton&nbsp;another&nbsp;=&nbsp;<FONT color=#0000ff>new</FONT>&nbsp;Singleton(); 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info(<FONT color=#ff33ff>"checking&nbsp;singletons&nbsp;for&nbsp;equality"</FONT>); 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT class=classLink><U>Assert</U></FONT>.assertEquals(<FONT color=#0000ff>true</FONT>,&nbsp;sone&nbsp;==&nbsp;stwo); 
<LI>&nbsp;&nbsp;&nbsp;} 
<LI>} </LI></OL></DIV><BR>前面这个测试案例序列化例12中的单例类，并且两次重构它。然后这个测试案例检查看是否被重构的单例类实例是同一个对象。下面是测试案例的输出：<BR>
<DIV class=codeStyle>
<OL>
<LI>　 
<LI>Buildfile:&nbsp;build.xml 
<LI>　 
<LI>init: 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[echo]&nbsp;Build&nbsp;20030422&nbsp;(22-04-2003&nbsp;11:32) 
<LI>　 
<LI>compile: 
<LI>　 
<LI>run-test-text: 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[java]&nbsp;.INFO&nbsp;main:&nbsp;testing&nbsp;singleton&nbsp;serialization... 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[java]&nbsp;.INFO&nbsp;main:&nbsp;testing&nbsp;singleton&nbsp;uniqueness... 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[java]&nbsp;INFO&nbsp;main:&nbsp;checking&nbsp;singletons&nbsp;<FONT color=#0000ff>for</FONT>&nbsp;equality 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[java]&nbsp;<FONT color=#ff0000>Time</FONT>:&nbsp;0.1 
<LI>　 
<LI>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[java]&nbsp;OK&nbsp;(2&nbsp;tests) </LI></OL></DIV><BR>
<H3>单例模式结束语</H3><BR>单例模式简单却容易让人迷惑，特别是对于Java的开发者来说。在这篇文章中，作者演示了Java开发者在顾及多线程、类载入器和序列化情况如何实现单例模式。作者也展示了你怎样才能实现一个单例类的注册表，以便能够在运行期指定单例类。<BR></LI></UL></B></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/3410.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-18 14:33 <a href="http://www.blogjava.net/kapok/archive/2005/04/18/3410.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>More Nifty Corners</title><link>http://www.blogjava.net/kapok/archive/2005/04/17/3390.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 17 Apr 2005 15:26:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/17/3390.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3390.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/17/3390.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3390.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3390.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<DIV style="FONT-SIZE: 16px; COLOR: black; FONT-FAMILY: tahoma,verdana,arial"><A href="http://pro.html.it/articoli/id_599/idcat_31/pro.html">http://pro.html.it/articoli/id_599/idcat_31/pro.html</A><BR><A href="http://www.forgetfoo.com/?blogid=3436">http://www.forgetfoo.com/?blogid=3436</A><BR><BR><A id=clp599 style="CURSOR: hand"><BR><BR><IMG alt="Aggiungi 'More Nifty Corners' ai tuoi preferiti" src="http://pro.html.it/img/add.gif" width=11 heigth="11"></A>
<SCRIPT type=text/javascript>
				allClippings[allClippings.length] =  new Clipping(599,"More Nifty Corners","http://pro.html.it/articoli/id_599/idcat_31/pro.html");
				</SCRIPT>
 &nbsp;<B>More Nifty Corners</B></DIV></TD>
<TD align=right width=59>
<DIV align=right><IMG height=15 alt="Giudizio dell'articolo" src="http://pro.html.it/img/voto4.gif" width=71 border=0></DIV></TD></TR></TBODY></TABLE><IMG height=1 alt="" src="http://pro.html.it/img/grigio.gif" width="100%" vspace=2 border=0><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<DIV align=left><FONT face=arial size=1>di Alessandro Fulciniti&nbsp;(<A title="Spedisci una e-mail a Alessandro Fulciniti" href="mailto:a.fulciniti@html.it">a.fulciniti@html.it</A>)</DIV></FONT></TD>
<TD align=right>
<DIV style="FONT-SIZE: 10px; COLOR: black; FONT-FAMILY: tahoma,verdana,arial" align=right>mercoledì 6 aprile 2005</DIV></TD></TR></TBODY></TABLE><BR><BR><!-- FINE TITOLO --><!-- TESTO SCRIPT --><A name=corpo></A><FONT face=arial size=2><!-- TESTO SCRIPT -->
<P>The article on <B>Nifty Corners</B> catched a <A title="delicious bookmarks on nifty corners" href="http://del.icio.us/url/64570f4974b50f3b29cdbbbfaefd2291">lot of interest</A> that we didn't expect at all. So, a big <B>Thank you</B> goes to the international webdesign community and in particular <A href="http://www.456bereastreet.com/">Roger Johansson from 456bereastreet.com</A> and <A href="http://www.web-graphics.com/">Nate Stainer from web-graphics.com</A> that originally launched the link through the <A title="bloglines citations for nifty corners" href="http://www.bloglines.com/citations?url=http%3A%2F%2Fpro.html.it%2Fesempio%2Fnifty%2F&amp;d=0&amp;mysubs=0&amp;submit=Search">blogosphere</A>.</P>
<P>Since its publication, I've worked a lot on Nifty Corners, bringing many improvements: looking back at the solution presented in the first article, seems to me that it was written ages ago. If you hadn't read it, I suggest you to <A href="http://pro.html.it/esempio/nifty/">have a look</A> to understand what I'm going to talk about. If you read it, but you're so curious that you couldn't wait, have a look at the <A href="http://pro.html.it/esempio/nifty2/nifty10js.html">final example</A> I prepared for this article. No images were used, just CSS and Javascript.</P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B>The news about Nifty Corners</B></SPAN><BR>
<P>First, I'm going to present briefly the many improvements I brought to Nifty Corners.</P>
<P>The first and major improvement is that I' ve extended <B>browser support</B> (hat tip <A title=positioniseverything.net href="http://www.positioniseverything.net/">John Gallant from positioniseverything.net</A> for the <SPAN style="COLOR: #ba1b2a"><B>font-size: 1px</B></SPAN> CSS fix!). Now, Nifty Corners are fully supported also in Internet Explorer 5.5, covering a very wide range of modern browsers: the examples that we'll see in this article have been successfully tested in IE6, IE5.5, Opera 7.6, Firefox 1.0 and Safari 1.1. The support for IE5.0 is partial, only classic nifty corners are displayed well, but I got rid anyway of that not-so-good browser sniffing that was used in the first version.</P>
<P>Talking about support: now Nifty Corners work also on XHTML pages served with mime types of <B>application/xhtml+xml</B> (hat tip <A title=456bereastreet.com href="http://www.456bereastreet.com/">Roger Johansson</A> for <SPAN style="COLOR: #ba1b2a"><B>document.createElementNS</B></SPAN> hint!). There's also a more robust <B>error detection</B> for the script, and an improved support for <B>class selector</B>.</P>
<P>Second, it's now possible <B>choose the corners to round</B>: you can choose to round one to all corners.</P>
<P>The other improvements I did are all based on the use of borders, togheter with the coloured stripes and margins, to get a quite wide variety of effects. While with the first version it was possible to get rounded corners <EM>transparent outside</EM>, now it's possible to get them <B>transparent inside</B>. A light form of <B>antialiasing</B> is now possible with Nifty Corners, and you can also get <B>nifty corners with edges</B>. Next, I added a basic <B>vertical padding handling</B> that you could find useful in some case.</P>
<P>We'll cover this new features in depth later through <B>ten examples</B> I prepared. But first, let's see how nifty corners works in general and the usage of the main function of the Javascript library.</P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B>How Nifty Corners work</B></SPAN><BR>
<P>Nifty Corners are a combination of CSS and Javascript to get <EM>rounded corners without images</EM>. The technique is made up of four essential parts:</P>
<OL>
<LI><A href="http://pro.html.it/esempio/nifty2/niftycssCode.html">CSS file</A> for the screen</A> 
<LI><A href="http://pro.html.it/esempio/nifty2/niftycssprintCode.html">CSS file</A> for the print</A> 
<LI><A href="http://pro.html.it/esempio/nifty2/niftyjsCode.html">Javascript library</A> to get Nifty Corners</A> 
<LI>The Javascript calls to round the elements you want </LI></OL>
<P>The firts three components just don't need changes, whatever you want to accomplish. They need just to be declared in the <SPAN style="COLOR: #ba1b2a"><B>head</B></SPAN> section of any HTML page like this:</P>
<P class=codice>&lt;link rel="stylesheet" type="text/css" href="niftyCorners.css"&gt;<BR>&lt;link rel="stylesheet" type="text/css" href="niftyPrint.css" media="print"&gt;<BR>&lt;script type="text/javascript" src="nifty.js"&gt;&lt;/script&gt; </P>
<P>As you can see, togheter with the support and the possibilities, even the complexity of Javascript and CSS file increased a bit respect the first version, but I've tried to mantain the usage really easy.</P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B>The Javascript functions</B></SPAN><BR>
<P>Let's see the <A href="http://pro.html.it/esempio/nifty2/nifty1js.html">first example</A>: if you see the code, you'll note that I left embedded the CSS and part of the Javascript to show how the page is build. Here's the embedded Javascript code:</P>
<P class=codice>&lt;script type="text/javascript"&gt;<BR>window.onload=function(){<BR>if(!NiftyCheck())<BR>&nbsp;&nbsp;&nbsp;&nbsp;return;<BR><B>Rounded("div#nifty","all","#FFF","#D4DDFF","smooth");</B><BR>}<BR>&lt;/script&gt; </P>
<P>The function <B>NiftyCheck</B> performs a check for DOM support. If the test has passed, the <B>Rounded</B> function is called, that is now the only one function that you should call to get nifty corners. It accepts five parameters, that are in order:</P>
<OL>
<LI>A <B>CSS selector</B> that indicates on wich elements apply the function 
<LI>A string that indicates <B>wich corners to round</B> 
<LI><B>Outer color</B> of the rounded corners 
<LI><B>Inner color</B> of the rounded corners 
<LI>An <B>optional fifth parameter</B>, that will contain the options for Nifty Corners </LI></OL>
<P>Let's see them in depth.<BR><BR><BR><BR><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B>First parameter: the CSS selector</B></SPAN><BR></P>
<P>One of the strenghts of the function is that is capable of accepting a <B>CSS selector</B> to target the elements to round. Are accepted parameters:</P>
<UL>
<LI><B>Tag selector</B>, i.e. <SPAN style="COLOR: #ba1b2a"><B>"p"</B></SPAN> or <SPAN style="COLOR: #ba1b2a"><B>"blockquote"</B></SPAN> or <SPAN style="COLOR: #ba1b2a"><B>"h2"</B></SPAN> 
<LI><B>Id selector</B>, with specified tag of the element: for example <SPAN style="COLOR: #ba1b2a"><B>"div#content"</B></SPAN> or <SPAN style="COLOR: #ba1b2a"><B>"p#news"</B></SPAN> or <SPAN style="COLOR: #ba1b2a"><B>"li#home"</B></SPAN> 
<LI><B>Class selector</B>, with specified tag of the element: for example <SPAN style="COLOR: #ba1b2a"><B>"div.entry"</B></SPAN> or <SPAN style="COLOR: #ba1b2a"><B>"h2.highlight"</B></SPAN> 
<LI><B>Descendant selector</B>, with some limitation: this have to be composed by an id selector followed by a tag selector. Valid examples are: <SPAN style="COLOR: #ba1b2a"><B>"div#news div"</B></SPAN> or <SPAN style="COLOR: #ba1b2a"><B>"ul#menu li"</B></SPAN> </LI></UL>
<P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B>Second parameter: specifing wich corners to round</B></SPAN><BR></P>
<P>Let's see again the Javascript call for the <A href="http://pro.html.it/esempio/nifty2/nifty1js.html">first example</A>:</P>
<P class=codice>Rounded("div#nifty",<B>"all"</B>,"#FFF","#D4DDFF","smooth");</P>
<P>The second parameter specify wich corners to round. It accept one or more of the following keywords separated by a space:</P>
<UL>
<LI><SPAN style="COLOR: #ba1b2a"><B>all</B></SPAN> will round all corners 
<LI><SPAN style="COLOR: #ba1b2a"><B>top</B></SPAN> will round top corners 
<LI><SPAN style="COLOR: #ba1b2a"><B>bottom</B></SPAN> will round bottom corners 
<LI><SPAN style="COLOR: #ba1b2a"><B>tl</B></SPAN> will round top-left corner 
<LI><SPAN style="COLOR: #ba1b2a"><B>tr</B></SPAN> will round top-right corner 
<LI><SPAN style="COLOR: #ba1b2a"><B>bl</B></SPAN> will round bottom-left corner 
<LI><SPAN style="COLOR: #ba1b2a"><B>br</B></SPAN> will round bottom-right corner </LI></UL>
<P>You can also combine the keywords. For example, you can use the parameter <SPAN style="COLOR: #ba1b2a"><B>"top br"</B></SPAN> to round the top corners and the bottom-right one. Note also that both the parameter <SPAN style="COLOR: #ba1b2a"><B>"top bottom"</B></SPAN> and <SPAN style="COLOR: #ba1b2a"><B>"tl tr bl br"</B></SPAN> are valid to get all the four corners rounded.</P>
<P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B>Third and fourth parameters: specifying the colors</B></SPAN><BR></P>
<P>Let's look again the Javascript call for the <A href="http://pro.html.it/esempio/nifty2/nifty1js.html">first example</A>:</P>
<P class=codice>Rounded("div#nifty","all",<B>"#FFF","#D4DDFF"</B>,"smooth");</P>
<P>The third and fourth parameters are used to specifying respectively <B>outer</B> and <B>inner</B> color. They should be specified in hex code with <B>#</B> symbol, in three or six digits. The good news is that they could also be setted to <SPAN style="COLOR: #ba1b2a"><B>"transparent"</B></SPAN>. While in the first version you could only get the outer color transparent, now you can also get nifty corners transparent <EM>inside</EM>. Obviously, you cannot get them both. But transparency inside it's really useful for example to play with elements with background, or avoiding specifying inner color but just the outer color.</P>
<P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B>Fifth parameter: nifty corners options</B></SPAN><BR></P>
<P>Here's the Javascript call for the <A href="http://pro.html.it/esempio/nifty2/nifty1js.html">first example</A>:</P>
<P class=codice>Rounded("div#nifty","all","#FFF","#D4DDFF",<B>"smooth"</B>);</P>
<P>As you can see, there's a fifth parameter setted to <SPAN style="COLOR: #ba1b2a"><B>"smooth"</B></SPAN>, in order to produce lighly smoothed rounded corners. The fifth parameter is optional, and if specified will render nifty corners in different way. There are some <B>keywords</B> that you could use:</P>
<UL>
<LI><SPAN style="COLOR: #ba1b2a"><B>smooth</B></SPAN> will produce lighly antialiased nifty corners: the Javascript library will compute automatically the intermediate color to blend the inner and outer color more gently: so, in this case they must be both be specified. 
<LI><SPAN style="COLOR: #ba1b2a"><B>border</B></SPAN> (followed by a color in hex code with <B>#</B> symbol, in three or six digits) will make corners with edges. Note that you can also get transparent corners, but just outside the edges. 
<LI><SPAN style="COLOR: #ba1b2a"><B>small</B></SPAN> will produce small corners, and could be applied to every kind of corners </LI></UL>
<P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B>The vertical padding handling</B></SPAN><BR></P>
<P>Before we start with the examples, let me explaing a good feature I've added to Nifty Corners but is <EM>behind the scenes</EM>. I' ve added a basic vertical padding handling,you can now specify via CSS a vertical padding to the elements to round: The script will automatically eliminate it before adding nifty corners. This is very useful for three reasons: it's easier adopting nifty corners on existing pages, the pages will degrade better if Javascript is disabled and also to avoid that <EM>jump-effect</EM> when the page wich use nifty corners is loaded. Optimal values are 5px vertical padding on the elements to round if you're planning to use normal nifty corners and 2px for small ones. 
<P>
<P>Now that we've covered all the points, we can start with some example.<BR><BR><BR><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B>The examples</B></SPAN><BR></P>
<P>Now the fun part.. As in the first article, I did some examples to show the possibilities of Nifty Corners. In this case you'll see how the possibilities increased. For each example will be reported on this page just the Javascript calls to <B>Rounded</B> function, but keep in mind that these calls should be included in the following code:</P>
<P class=codice>window.onload=function(){<BR>if(!NiftyCheck())<BR>&nbsp;&nbsp;&nbsp;&nbsp;return;<BR><B>/* here the calls to add Nifty Corners */</B><BR>} </P>
<P>Now, let's start.</P>
<P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B><A href="http://pro.html.it/esempio/nifty2/nifty1js.html">Example one: a single div with antialias</A></B></SPAN><BR></P>
<P>This is the example we saw in the opening. The Javascript call is:</P>
<P class=codice>Rounded("div#nifty","all","#FFF","#D4DDFF","smooth"); </P>
<P>In this case all corners were rounded with a very light antialiasing. Someone over the net tried to produce really antialiased rounded corners without images modifying heavily my original script. But I think that the solution I'm presenting here is the best I could find to blend outer and inner colour more gently, without overcharging too much the CSS, the Javascript, the computing time and the DOM tree of the page.</P>
<P>I'm not so radical about the use of images, and I think that in those case where real antialised rounded corners must be used in a page, the traditional html+CSS+background images method will still be the best way to get them.</P>
<P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B><A href="http://pro.html.it/esempio/nifty2/nifty2js.html">Example two: brothers divs</A></B></SPAN><BR></P>
<P>In this example, two divs were rounded. The js calls are:</P>
<P class=codice>Rounded("div#content","all","#FFF","#9DD4FF","smooth");<BR>Rounded("div#nav","tr br","#FFF","#FFC79D","smooth"); </P>
<P>Note that the small one has only right corners rounded.</P>
<P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B><A href="http://pro.html.it/esempio/nifty2/nifty3js.html">Example three: transparent by nature</A></B></SPAN><BR></P>
<P>This one is one of the most significant improvements to nifty corners. You just have to specify outer color and "transparent" for the inner color and you got it:</P>
<P class=codice>Rounded("div#box","all","#6385D8","transparent"); </P>
<P>A thing about transparency inside: while you can choose the corners to round, and get small and transparent inside nifty corners, it's not possible to get them smoothed or with edges.</P>
<P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B><A href="http://pro.html.it/esempio/nifty2/nifty4js.html">Example four: with or without them</A></B></SPAN><BR></P>
<P>In this example, I put two divs sharing the same class in a page. One of them has got an <B>id</B> too, but no additional CSS rules, to show how <B>nifty corners with edges</B> are rendered. In this case two calls to Rounded function are needed, in order to get different inner color for top and bottom corners:</P>
<P class=codice>Rounded("div#nifty","top","transparent","#FFC5FF","border #C0C0C0");<BR>Rounded("div#nifty","bottom","transparent","#F0F0E7","small border #C0C0C0"); </P>
<P>Note that after the <SPAN style="COLOR: #ba1b2a"><B>border</B></SPAN> option should follow the color of them, and at the bottom I setted small corners with edges. A small note on the use of borders: the script will automatically add horizontal borders to the direct children of the element(s) to round with edges, avoiding you to specify them in the CSS and allowing a better degradation in the case Javascript is off but CSS is on. So, if you' re having bad rendering issues with borders, the easiest way to fix is adding an extra wrapper to contain everything of the element to round. This solution is also suggested when the element to round contains lot of elements as direct children, since the script traverses them all for adding vertical borders and could take a little time.</P>
<P><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B><A href="http://pro.html.it/esempio/nifty2/nifty5js.html">Example five: color explosion</A></B></SPAN><BR></P>
<P>In this example, I used descendant selector and transparency inside to round four elements in one shot, but just on two opposite corners. Here's the only Javascript call:</P>
<P class=codice>Rounded("div#news div","tr bl","#FFF","transparent"); <BR><BR><BR><BR><!-- TITOLO --><!-- TESTO SCRIPT --><A name=corpo></A><FONT face=arial size=2><!-- TESTO SCRIPT --><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B><A href="http://pro.html.it/esempio/nifty2/nifty6js.html">Example six: elastic nifty</A></B></SPAN><BR></P>
<P>In this example I wanted to underline that most of the examples that use nifty corners won't be possible just with the use of background images and CSS rules, but sometimes extra wrappers just for presentational purpose are needed to serve the backgrounds in a traditional way. The markup of this example is, like others, minimal and semantical, and the newsboxes are dimensioned with <SPAN style="COLOR: #ba1b2a"><B>em</B></SPAN> to get an elastic effect. Transparency was also used. The js code for this example is:</P>
<P class=codice>Rounded("h3","all","#FFF","#efeadc","small border #999");<BR>Rounded("div#news h3","all","#FFF","transparent"); </P>
<P>Note in that case that borders are added around <SPAN style="COLOR: #ba1b2a"><B>h3</B></SPAN> elements. The script in fact is able to add border in elements that just contains text inside, by wrapping it around a <SPAN style="COLOR: #ba1b2a"><B>span</B></SPAN> with <SPAN style="COLOR: #ba1b2a"><B>dislay:block</B></SPAN> setted throug DOM. However, note that nifty corners will fail if the element to round contain a mix of elements and anonymous (both inline and block) boxes as direct children. The solutions is even in that case are two: avoiding the use of borders, or adding an extra wrapper to contain it all.</P>
<P class=codice><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B><A href="http://pro.html.it/esempio/nifty2/nifty7js.html">Example seven: transparent, tabbed menu. Now with borders.</A></B></SPAN><BR></P>
<P>This example show the power of the discendant selector and transparency to get a tabbed menu with borders through a single Javascript call:</P>
<P class=codice>Rounded("div#nav li","top","transparent","#BEFF9A","border #508F0F"); </P>
<P class=codice><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B><A href="http://pro.html.it/esempio/nifty2/nifty8js.html">Example eight: navigational leaves</A></B></SPAN><BR></P>
<P>With the same markup of previous example we could get also a quite unordinary and appealing menu with borders. Here's the only Javascript call needed:</P>
<P class=codice>Rounded("div#nav li","tr bl","transparent","#9ABEFF","border #3562B1"); </P>
<P class=codice><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B><A href="http://pro.html.it/esempio/nifty2/nifty9js.html">Example nine: a floated image gallery</A></B></SPAN><BR></P>
<P>This example is quite similar to one we saw in the first article, but will render much better for two reasons. First, antialising is provided. Second and more important, the vertical <B>padding</B>, initially setted with CSS, is automatically removed by the script. This in order to avoid the <EM>jump effect</EM> of the thumbnails while the page is entirely loaded and Javascript starts to run.</P>
<P>The transition now, as you can see, is quite similar to the loading of background images in a traditional manner. Note that in the CSS of the page, a vertical padding of 5px on top and bottom is added to list-items that will be removed by the script for nifty corners. Using vertical padding in CSS and letting them be automatically removed by the script is higly reccomended in particular when using nifty corners on heavy pages or with images in them, even also for a better degradation if Javascript is disabled. Let's see the Javascript call used:</P>
<P class=codice>Rounded("div#minipics li","all","#C7C7F2","#FFF","smooth"); </P>
<P class=codice><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B><A href="http://pro.html.it/esempio/nifty2/nifty10js.html">Example ten: Nifty layout</A></B></SPAN><BR></P>
<P>So we arrived at the last example, a layout using nifty corners. I really enjoyed preparing it without using images at all but just nifty corners. In this case, I did not used embedded CSS or Javascript. The <SPAN style="COLOR: #ba1b2a"><B>head</B></SPAN> section contains the following lines:</P>
<P class=codice>&lt;link rel="stylesheet" type="text/css" href="niftylayout.css"&gt;<BR>&lt;link rel="stylesheet" type="text/css" href="niftyCorners.css"&gt;<BR>&lt;link rel="stylesheet" type="text/css" href="niftyPrint.css" media="print"&gt;<BR>&lt;script type="text/javascript" src="nifty.js"&gt;&lt;/script&gt;<BR><B>&lt;script type="text/javascript" src="layout.js"&gt;&lt;/script&gt;</B> </P>
<P>The file <B>layout.js</B> contains all the Javascript calls, let's see its content:</P>
<P class=codice>window.onload=function(){<BR>if(!NiftyCheck())<BR>&nbsp;&nbsp;&nbsp;&nbsp;return;<BR>Rounded("div#header","bottom","#D6DEEC","#84B7FF","smooth");<BR>Rounded("div#header h1","bottom","#84B7FF","#657DA6","small smooth");<BR>Rounded("div#content","tl bottom","#D6DEEC","#FFF","smooth");<BR>Rounded("div#nav","tr bottom","#D6DEEC","#95B3DE","smooth");<BR>Rounded("div#sidenotes","all","#D6DEEC","#B1C0D5","smooth");<BR>Rounded("form","all","#D6DEEC","#B4CEF7","smooth");<BR>Rounded("blockquote","tr bl","#FFF","#CDFFAA","border #88D84F");<BR>Rounded("div#relax","all","#FFF","transparent");<BR>Rounded("div#footer","all","#D6DEEC","#CCCCCC","small border #fff");<BR>} </P>
<P>A good practice is in fact avoiding using embedded Javascript or CSS in the head section. One thing about this last example: it's curious note that we got <B>thick nifty corners</B> around the header by rounding both the header and the <SPAN style="COLOR: #ba1b2a"><B>h1</B></SPAN> inside it.</P>
<P>Before concluding: try to view how would render the <A href="http://pro.html.it/esempio/nifty2/nifty10nojs.html">page with Javascript disabled</A> to see how it degrade gracefully. In making this final example, I simply started with this.</P>
<P class=codice><BR><SPAN style="FONT-SIZE: 110%; FONT-FAMILY: verdana"><B>Conclusion and download</B></SPAN><BR></P>
<P>When the first article was published, I knew there were many improvements to be done, and now I think this version is a stable and definitive release. Nifty Corners are free, but no modifications to the script and/or the CSS are allowed. This article and Nifty Corners are fully copyrighted by HTML.it and Alessandro Fulciniti, and were published on the 6th of April 2005 both in Italian and in English. You can contact the author writing to <EM>a.fulciniti[at]html.it</EM></P>
<P>You could download sources and examples <A href="http://pro.html.it/esempio/nifty2/nifty2.zip">here</A>. Enjoy!</P>
<P class=codice><BR></FONT></P></FONT><img src ="http://www.blogjava.net/kapok/aggbug/3390.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-17 23:26 <a href="http://www.blogjava.net/kapok/archive/2005/04/17/3390.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Servlet 2.3过滤器编程</title><link>http://www.blogjava.net/kapok/archive/2005/04/15/3334.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 15 Apr 2005 15:50:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/15/3334.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3334.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/15/3334.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3334.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3334.html</trackback:ping><description><![CDATA[<FONT face=宋体>2005年3月17日&nbsp;&nbsp;作者：l_walker&nbsp;&nbsp;Matrix-与Java共舞<BR><A href="http://www.matrix.org.cn/article/1266.html">http://www.matrix.org.cn/article/1266.html</A><BR><BR>Jason&nbsp;Hunter通过对一些自由而又实用的过滤器的研究以对新的servlet过滤器模型进行深入探讨。你将知道这些过滤器是如何工作以及你能用他们做什么。最后，Jason介绍了他自己为简化文件上传而做的多路请求过滤器。<BR><BR>&nbsp;<BR><BR>&nbsp;<BR><BR>在"Servlet&nbsp;2.3:&nbsp;New&nbsp;Features&nbsp;Exposed,"中，我介绍了Servlet&nbsp;API&nbsp;2.3中的变化并给出了一个简单的servlet过滤器模型。在随后的文章中，我将对servlet过滤器进行深入的挖掘，而你看到的这些servlet过滤器都是能从Web上免费下载的。对每一个过滤器，我将检视它是做什么的，如何工作的，以及你能从哪里得到它。<BR><BR>&nbsp;<BR><BR>你可以在两种情况下使用本文：学习过滤器的功用，或者作为你写过滤器时的辅助。我将从几个简单的例子开始然后继续更多高级的过滤器。最后，我将向你介绍我为了支持多路请求而写的一个文件上传过滤器。<BR><BR>&nbsp;<BR><BR>Servlet&nbsp;过滤器<BR><BR>也许你还不熟悉情况，一个过滤器是一个可以传送请求或修改响应的对象。过滤器并不是servlet，他们并不实际创建一个请求。他们是请求到达一个servlet前的预处理程序，和/或响应离开servlet后的后处理程序。就像你将在后面的例子中看到的，一个过滤器能够：<BR><BR>·在一个servlet被调用前截获该调用<BR><BR>·在一个servlet被调用前检查请求<BR><BR>·修改在实际请求中提供了可定制请求对象的请求头和请求数据<BR><BR>·修改在实际响应中提供了可定制响应对象的响应头和响应数据<BR><BR>·在一个servlet被调用之后截获该调用<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;你可以一个过滤器以作用于一个或一组servlet，零个或多个过滤器能过滤一个或多个servlet。一个过滤器实现java.servlet.Filter接口并定义它的三个方法：<BR><BR>1．&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;init(FilterConfig&nbsp;config)&nbsp;throws&nbsp;ServletException:在过滤器执行service前被调用，以设置过滤器的配置对象。<BR><BR>2．&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;destroy();在过滤器执行service后被调用。<BR><BR>3．&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Void&nbsp;doFilter(ServletRequest&nbsp;req,ServletResponse&nbsp;res,FilterChain&nbsp;chain)&nbsp;throws&nbsp;IOException,ServletException;执行实际的过滤工作。<BR><BR>&nbsp;<BR><BR>服务器调用一次init(FilterConfig)以为服务准备过滤器，然后在请求需要使用过滤器的任何时候调用doFilter()。FilterConfig接口检索过滤器名、初始化参数以及活动的servlet上下文。服务器调用destory()以指出过滤器已结束服务。过滤器的生命周期和servelt的生命周期非常相似&nbsp;——在Servlet&nbsp;API&nbsp;2.3&nbsp;最终发布稿2号&nbsp;中最近改变的。先前得用setFilterConfig(FilterConfig)方法来设置生命周期。<BR><BR>&nbsp;<BR><BR>在doFilter()方法中，每个过滤器都接受当前的请求和响应，而FilterChain包含的过滤器则仍然必须被处理。doFilter()方法中，过滤器可以对请求和响应做它想做的一切。（就如我将在后面讨论的那样，通过调用他们的方法收集数据，或者给对象添加新的行为。）过滤器调用<BR><BR>chain.doFilter()将控制权传送给下一个过滤器。当这个调用返回后，过滤器可以在它的doFilter()方法的最后对响应做些其他的工作；例如，它能记录响应的信息。如果过滤器想要终止请求的处理或或得对响应的完全控制，则他可以不调用下一个过滤器。<BR><BR>&nbsp;<BR><BR>循序渐进<BR><BR>如果想要真正理解过滤器，则应该看它们在实际中的应用。我们将看到的第一个过滤器是简单而有用的，它记录了所有请求的持续时间。在Tomcat&nbsp;4.0发布中被命名为ExampleFilter。代码如下：<BR><BR></FONT>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word">import&nbsp;java.io.*;<BR><BR>import&nbsp;javax.servlet.*;<BR><BR>import&nbsp;javax.servlet.http.*;<BR><BR>&nbsp;<BR><BR>public&nbsp;class&nbsp;TimerFilter&nbsp;implements&nbsp;Filter&nbsp;{<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;private&nbsp;FilterConfig&nbsp;config&nbsp;=&nbsp;null;<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;init(FilterConfig&nbsp;config)&nbsp;throws&nbsp;ServletException&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;this.config&nbsp;=&nbsp;config;<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;destroy()&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;config&nbsp;=&nbsp;null;<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;doFilter(ServletRequest&nbsp;request,&nbsp;ServletResponse&nbsp;response,<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FilterChain&nbsp;chain)&nbsp;throws&nbsp;IOException,&nbsp;ServletException&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;long&nbsp;before&nbsp;=&nbsp;System.currentTimeMillis();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;chain.doFilter(request,&nbsp;response);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;long&nbsp;after&nbsp;=&nbsp;System.currentTimeMillis();<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;name&nbsp;=&nbsp;"";<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(request&nbsp;instanceof&nbsp;HttpServletRequest)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name&nbsp;=&nbsp;((HttpServletRequest)request).getRequestURI();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;config.getServletContext().log(name&nbsp;+&nbsp;":&nbsp;"&nbsp;+&nbsp;(after&nbsp;-&nbsp;before)&nbsp;+&nbsp;"ms");<BR><BR>&nbsp;&nbsp;}<BR><BR>}</TD></TR></TBODY></TABLE><BR>&nbsp;<BR><BR>当服务器调用init()时，过滤器用config变量来保存配置类的引用，这将在后面的doFilter()方法中被使用以更改ServletContext。当调用doFilter()时，过滤器计算请求发生到该请求执行完毕之间的时间。该过滤器很好的演示了请求之前和之后的处理。注意doFilter()方法的参数并不是HTTP对象，因此要调用HTTP专用的getRequestURI()方法时必须将request转化为HttpServletRequest类型。<BR><BR>&nbsp;<BR><BR>使用此过滤器，你还必须在web.xml文件中用&lt;filter&gt;标签部署它，见下：<BR><BR>&nbsp;&nbsp;&nbsp; 
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word">&nbsp;&lt;filter&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-name&gt;timerFilter&lt;/filter-name&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-class&gt;TimerFilter&lt;/filter-class&gt;<BR><BR>&lt;/filter&gt;</TD></TR></TBODY></TABLE><BR><BR>&nbsp;<BR><BR>这将通知服务器一个叫timerFiter的过滤器是从TimerFiter类实现的。你可以使用确定的URL模式或使用&lt;filter-mapping&gt;标签命名的servelt&nbsp;来注册一个过滤器，如：<BR><BR>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word">&lt;filter-mapping&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-name&gt;timerFilter&lt;/filter-name&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;url-pattern&gt;/*&lt;/url-pattern&gt;<BR><BR>&lt;/filter-mapping&gt;</TD></TR></TBODY></TABLE><BR><BR>&nbsp;<BR><BR>这种配置使过滤器操作所有对服务器的请求（静态或动态），正是我们需要的计时过滤器。如果你连接一个简单的页面，记录输出可能如下：<BR><BR>2001-05-25&nbsp;00:14:11&nbsp;/timer/index.html:&nbsp;10ms<BR><BR>&nbsp;<BR><BR>在Tomcat&nbsp;4.0&nbsp;beta&nbsp;5中，你可以在server_root/logs/下找到该记录文件。<BR><BR>&nbsp;<BR><BR>此过滤器的WAR文件从此下载：<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<IMG src="http://www.matrix.org.cn/article/images/small/url.gif" align=absMiddle><A href="http://www.javaworld.com/jw-06-2001/Filters/timer.war" target=_blank>http://www.javaworld.com/jw-06-2001/Filters/timer.war</A><BR><BR>&nbsp;<BR><BR>谁在你的网站上？他们在做什么？<BR><BR>我们下一个过滤器是由OpenSymphony成员写的clickstream过滤器。这个过滤器跟踪用户请求（比如：点击）和请求队列（比如：点击流）以向网络管理员显示谁在她的网站上以及每个用户正在访问那个页面。这是个使用LGPL的开源库。<BR><BR>&nbsp;<BR><BR>在clickstream包中你将发现一个捕获请求信息的ClickstreamFilter类，一个像操作结构一样的Clickstream类以保存数据，以及一个保存会话和上下文事件的ClickstreamLogger类以将所有东西组合在一起。还有个BotChecker类用来确定客户端是否是一个机器人（简单的逻辑，像“他们是否是从robots.txt来的请求?”）。该包中提供了一个clickstreams.jsp摘要页面和一个viewstream.jsp详细页面来查看数据。<BR><BR>&nbsp;<BR><BR>我们先看ClickstreamFilter类。所有的这些例子都做了些轻微的修改以格式化并修改了些可移植性问题，这我将在后面将到。<BR>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word"><BR>import&nbsp;java.io.IOException;<BR><BR>import&nbsp;javax.servlet.*;<BR><BR>import&nbsp;javax.servlet.http.*;<BR><BR>&nbsp;<BR><BR>public&nbsp;class&nbsp;ClickstreamFilter&nbsp;implements&nbsp;Filter&nbsp;{<BR><BR>&nbsp;&nbsp;protected&nbsp;FilterConfig&nbsp;filterConfig;<BR><BR>&nbsp;&nbsp;private&nbsp;final&nbsp;static&nbsp;String&nbsp;FILTER_APPLIED&nbsp;=&nbsp;"_clickstream_filter_applied";<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;init(FilterConfig&nbsp;config)&nbsp;throws&nbsp;ServletException&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;this.filterConfig&nbsp;=&nbsp;filterConfig;<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;doFilter(ServletRequest&nbsp;request,&nbsp;ServletResponse&nbsp;response,<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FilterChain&nbsp;chain)&nbsp;throws&nbsp;IOException,&nbsp;ServletException&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;确保该过滤器在每次请求中只被使用一次<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(request.getAttribute(FILTER_APPLIED)&nbsp;==&nbsp;null)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setAttribute(FILTER_APPLIED,&nbsp;Boolean.TRUE);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpSession&nbsp;session&nbsp;=&nbsp;((HttpServletRequest)request).getSession();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Clickstream&nbsp;stream&nbsp;=&nbsp;(Clickstream)session.getAttribute("clickstream");<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stream.addRequest(((HttpServletRequest)request));<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;传递请求<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;chain.doFilter(request,&nbsp;response);<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;destroy()&nbsp;{&nbsp;}<BR><BR>}</TD></TR></TBODY></TABLE><BR><BR>&nbsp;<BR><BR>doFilter()方法取得用户的session，从中获取Clickstream，并将当前请求数据加到Clickstream中。其中使用了一个特殊的FILTER_APPLIED标记属性来标注此过滤器是否已经被当前请求使用（可能会在请求调度中发生）并且忽略所有其他的过滤器行为。你可能疑惑过滤器是怎么知道当前session中有clickstream属性。那是因为ClickstreamLogger在会话一开始时就已经设置了它。ClickstreamLogger代码：<BR><BR>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word">import&nbsp;java.util.*;<BR><BR>import&nbsp;javax.servlet.*;<BR><BR>import&nbsp;javax.servlet.http.*;<BR><BR>&nbsp;<BR><BR>public&nbsp;class&nbsp;ClickstreamLogger&nbsp;implements&nbsp;ServletContextListener,<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpSessionListener&nbsp;{<BR><BR>&nbsp;&nbsp;Map&nbsp;clickstreams&nbsp;=&nbsp;new&nbsp;HashMap();<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;ClickstreamLogger()&nbsp;{&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;contextInitialized(ServletContextEvent&nbsp;sce)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;sce.getServletContext().setAttribute("clickstreams",&nbsp;clickstreams);<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;contextDestroyed(ServletContextEvent&nbsp;sce)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;sce.getServletContext().setAttribute("clickstreams",&nbsp;null);<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;sessionCreated(HttpSessionEvent&nbsp;hse)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;HttpSession&nbsp;session&nbsp;=&nbsp;hse.getSession();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;Clickstream&nbsp;clickstream&nbsp;=&nbsp;new&nbsp;Clickstream();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;session.setAttribute("clickstream",&nbsp;clickstream);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;clickstreams.put(session.getId(),&nbsp;clickstream);<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;sessionDestroyed(HttpSessionEvent&nbsp;hse)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;HttpSession&nbsp;session&nbsp;=&nbsp;hse.getSession();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;Clickstream&nbsp;stream&nbsp;=&nbsp;(Clickstream)session.getAttribute("clickstream");<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;clickstreams.remove(session.getId());<BR><BR>&nbsp;&nbsp;}<BR><BR>}<BR><BR>&nbsp;</TD></TR></TBODY></TABLE><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger（记录器）获取应用事件并将使用他们将所有东西帮定在一起。当context创建中，logger在context中放置了一个共享的流map。这使得clickstream.jsp页面知道当前活动的是哪个流。而在context销毁中，logger则移除此map。当一个新访问者创建一个新的会话时，logger将一个新的Clickstream实例放入此会话中并将此Clickstream加入到中心流map中。在会话销毁时，由logger从中心map中移除这个流。<BR><BR>&nbsp;<BR><BR>下面的web.xml部署描述片段将所有东西写在一块：<BR><BR>&nbsp;&nbsp;&nbsp; 
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word">&nbsp;&lt;filter&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-name&gt;clickstreamFilter&lt;/filter-name&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-class&gt;ClickstreamFilter&lt;/filter-class&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/filter&gt;</TD></TR></TBODY></TABLE><BR><BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp; 
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word">&nbsp;&lt;filter-mapping&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-name&gt;clickstreamFilter&lt;/filter-name&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;url-pattern&gt;*.jsp&lt;/url-pattern&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/filter-mapping&gt;<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-mapping&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-name&gt;clickstreamFilter&lt;/filter-name&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;url-pattern&gt;*.html&lt;/url-pattern&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/filter-mapping&gt;<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;listener&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;listener-class&gt;ClickstreamLogger&lt;/listener-class&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/listener&gt;</TD></TR></TBODY></TABLE><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;这注册了ClickstreamFilter并设置其处理*.jsp和*.html来的请求。这也将ClickstreamLogger注册为一个监听器以在应用事件发生时接受他们。<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;两个JSP页面从会话中取clickstream数据和context对象并使用HTML界面来显示当前状态。下面的clickstream.jsp文件显示了个大概：<BR><BR>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word">&lt;%@&nbsp;page&nbsp;import="java.util.*"&nbsp;%&gt;<BR><BR>&lt;%@&nbsp;page&nbsp;import="Clickstream"&nbsp;%&gt;<BR><BR>&lt;%<BR><BR>Map&nbsp;clickstreams&nbsp;=&nbsp;(Map)application.getAttribute("clickstreams");<BR><BR>String&nbsp;showbots&nbsp;=&nbsp;"false";<BR><BR>&nbsp;<BR><BR>if&nbsp;(request.getParameter("showbots")&nbsp;!=&nbsp;null)&nbsp;{<BR><BR>&nbsp;&nbsp;if&nbsp;(request.getParameter("showbots").equals("true"))<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;showbots&nbsp;=&nbsp;"true";<BR><BR>&nbsp;&nbsp;else&nbsp;if&nbsp;(request.getParameter("showbots").equals("both"))<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;showbots&nbsp;=&nbsp;"both";<BR><BR>}<BR><BR>%&gt;<BR><BR>&nbsp;<BR><BR>&lt;font&nbsp;face="Verdana"&nbsp;size="-1"&gt;<BR><BR>&lt;h1&gt;All&nbsp;Clickstreams&lt;/h1&gt;<BR><BR>&nbsp;<BR><BR>&lt;a&nbsp;href="clickstreams.jsp?showbots=false"&gt;No&nbsp;Bots&lt;/a&gt;&nbsp;|<BR><BR>&lt;a&nbsp;href="clickstreams.jsp?showbots=true"&gt;All&nbsp;Bots&lt;/a&gt;&nbsp;|<BR><BR>&lt;a&nbsp;href="clickstreams.jsp?showbots=both"&gt;Both&lt;/a&gt;&nbsp;&lt;p&gt;<BR><BR>&nbsp;<BR><BR>&lt;%&nbsp;if&nbsp;(clickstreams.keySet().size()&nbsp;==&nbsp;0)&nbsp;{&nbsp;%&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;No&nbsp;clickstreams&nbsp;in&nbsp;progress<BR><BR>&lt;%&nbsp;}&nbsp;%&gt;<BR><BR>&nbsp;<BR><BR>&lt;%<BR><BR>Iterator&nbsp;it&nbsp;=&nbsp;clickstreams.keySet().iterator();<BR><BR>int&nbsp;count&nbsp;=&nbsp;0;<BR><BR>while&nbsp;(it.hasNext())&nbsp;{<BR><BR>&nbsp;&nbsp;String&nbsp;key&nbsp;=&nbsp;(String)it.next();<BR><BR>&nbsp;&nbsp;Clickstream&nbsp;stream&nbsp;=&nbsp;(Clickstream)clickstreams.get(key);<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;if&nbsp;(showbots.equals("false")&nbsp;&amp;&amp;&nbsp;stream.isBot())&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;continue;<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;else&nbsp;if&nbsp;(showbots.equals("true")&nbsp;&amp;&amp;&nbsp;!stream.isBot())&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;continue;<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;count++;<BR><BR>&nbsp;&nbsp;try&nbsp;{<BR><BR>%&gt;<BR><BR>&nbsp;<BR><BR>&lt;%=&nbsp;count&nbsp;%&gt;.&nbsp;<BR><BR>&lt;a&nbsp;href="viewstream.jsp?sid=&lt;%=&nbsp;key&nbsp;%&gt;"&gt;&lt;b&gt;<BR><BR>&lt;%=&nbsp;(stream.getHostname()&nbsp;!=&nbsp;null&nbsp;&amp;&amp;&nbsp;!stream.getHostname().equals("")&nbsp;?<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stream.getHostname()&nbsp;:&nbsp;"Stream")&nbsp;%&gt;<BR><BR>&lt;/b&gt;&lt;/a&gt;&nbsp;&lt;font&nbsp;size="-1"&gt;&lt;%=&nbsp;stream.getStream().size()&nbsp;%&gt;&nbsp;reqs&lt;/font&gt;&lt;br&gt;<BR><BR>&nbsp;<BR><BR>&lt;%<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;catch&nbsp;(Exception&nbsp;e)&nbsp;{<BR><BR>%&gt;<BR><BR>&nbsp;&nbsp;An&nbsp;error&nbsp;occurred&nbsp;-&nbsp;&lt;%=&nbsp;e&nbsp;%&gt;&lt;br&gt;<BR><BR>&lt;%<BR><BR>&nbsp;&nbsp;}<BR><BR>}<BR><BR>%&gt;</TD></TR></TBODY></TABLE><BR><BR><BR>这个包很容易从OpenSymphony下载并安装。将Java文件编译并放在<BR><BR>WEB-INF/classes下，将JSP文件放到Web应用路径下，按帮助修改web.xml文件。为防止在这些工作前的争论，你可以从<BR><BR><IMG src="http://www.matrix.org.cn/article/images/small/url.gif" align=absMiddle><A href="http://www.javaworld.com/jw-06-2001/Filters/clickstream.war" target=_blank>http://www.javaworld.com/jw-06-2001/Filters/clickstream.war</A>处找到打好包的WAR文件。<BR><BR>&nbsp;<BR><BR>为能让此过滤器能在Tomcat&nbsp;4.0&nbsp;beta&nbsp;5下工作，我发现我不得不做一些轻微的改动。我做的改动显示了一些在servlet和过滤器的可移植性中通常容易犯的错误，所以我将他们列在下面：<BR><BR>·我不得不将在JSP中添加一个额外的导入语句：&lt;%@&nbsp;page&nbsp;import=”Clickstream”&nbsp;%&gt;。在Java中你并不需要导入在同一包下的类，而在服务器上JSP被编译到默认包中，你并不需要这句导入行。但在像Tomcat这样的服务器上，JSP被编译到一个自定义的包中，你不得不明确地从默认包中导入类。<BR><BR>·我不得不将&lt;listener&gt;元素移动到web.xml文件中的&lt;filter&gt;和&lt;filter-mapping&gt;元素之后，就像部署描述DTD要求的那样。并不是所有服务器对元素都要求固定的顺序。但Tomcat必须要。<BR><BR>·我不得不将web.xml中的映射由/*.html和/*.jsp改成正确的*.html和*.jsp。一些服务器会忽略开头的/，但Tomcat强硬的规定开头不能有/。<BR><BR>·最后，我得将ClickstreamFilter类升级到最新的生命周期API，将setFilterConfig()改成新的init()和destory()方法。<BR><BR>&nbsp;<BR><BR>可下载的WAR文件已经包含了这些修改并能通过服务器在包外运行，虽然我并没有广泛的进行测试。<BR><BR>&nbsp;<BR><BR>压缩响应<BR><BR>第三个过滤器是自动压缩响应输出流，以提高带宽利用率并提供一个很好的包装响应对象的示例。这个过滤器是由来自SUN的Amy&nbsp;Roh编写的，他为Tomcat&nbsp;4.0&nbsp;的“examples”Web程序做出过贡献。你将从webapps/examples/WEB-INF/classes/compressionFilters下找到原始代码。这里的例子代码以及WAR下的都已经为了更清晰和更简单而编辑过了。<BR><BR>&nbsp;<BR><BR>CompressionFilter类的策略是检查请求头以判定客户端是否支持压缩，如果支持，则将响应对象用自定义的响应来打包，它的getOutputStream()和getWriter()方法已经被定义为可以利用压缩过的输出流。使用过滤器允许如此简单而有效的解决问题。<BR><BR>&nbsp;<BR><BR>我们将从init()开始看代码：<BR><BR>&nbsp;&nbsp; 
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word">public&nbsp;void&nbsp;init(FilterConfig&nbsp;filterConfig)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;config&nbsp;=&nbsp;filterConfig;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;compressionThreshold&nbsp;=&nbsp;0;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(filterConfig&nbsp;!=&nbsp;null)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;str&nbsp;=&nbsp;filterConfig.getInitParameter("compressionThreshold");<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(str&nbsp;!=&nbsp;null)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;compressionThreshold&nbsp;=&nbsp;Integer.parseInt(str);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;compressionThreshold&nbsp;=&nbsp;0;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;}</TD></TR></TBODY></TABLE><BR>&nbsp;<BR><BR>注意在检索请求头前必须把request转化为HttpServletRequest，就想在第一个例子里那样。过滤器使用wrapper类CompressResponseWrapper，一个从<BR><BR>HttpServletResponseWrapper类继承下来的自定义类。这个wrapper的代码相对比较简单：<BR><BR>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word">public&nbsp;class&nbsp;CompressionResponseWrapper&nbsp;extends&nbsp;HttpServletResponseWrapper&nbsp;{<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;protected&nbsp;ServletOutputStream&nbsp;stream&nbsp;=&nbsp;null;<BR><BR>&nbsp;&nbsp;protected&nbsp;PrintWriter&nbsp;writer&nbsp;=&nbsp;null;<BR><BR>&nbsp;&nbsp;protected&nbsp;int&nbsp;threshold&nbsp;=&nbsp;0;<BR><BR>&nbsp;&nbsp;protected&nbsp;HttpServletResponse&nbsp;origResponse&nbsp;=&nbsp;null;<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;CompressionResponseWrapper(HttpServletResponse&nbsp;response)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;super(response);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;origResponse&nbsp;=&nbsp;response;<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;void&nbsp;setCompressionThreshold(int&nbsp;threshold)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;this.threshold&nbsp;=&nbsp;threshold;<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;ServletOutputStream&nbsp;createOutputStream()&nbsp;throws&nbsp;IOException&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;(new&nbsp;CompressionResponseStream(origResponse));<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;ServletOutputStream&nbsp;getOutputStream()&nbsp;throws&nbsp;IOException&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(writer&nbsp;!=&nbsp;null)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;IllegalStateException("getWriter()&nbsp;has&nbsp;already&nbsp;been&nbsp;"&nbsp;+<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"called&nbsp;for&nbsp;this&nbsp;response");<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(stream&nbsp;==&nbsp;null)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stream&nbsp;=&nbsp;createOutputStream();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;((CompressionResponseStream)&nbsp;stream).setCommit(true);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;((CompressionResponseStream)&nbsp;stream).setBuffer(threshold);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;stream;<BR><BR>&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;public&nbsp;PrintWriter&nbsp;getWriter()&nbsp;throws&nbsp;IOException&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(writer&nbsp;!=&nbsp;null)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;writer;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(stream&nbsp;!=&nbsp;null)&nbsp;{<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;IllegalStateException("getOutputStream()&nbsp;has&nbsp;already&nbsp;"&nbsp;+<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"been&nbsp;called&nbsp;for&nbsp;this&nbsp;response");<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;stream&nbsp;=&nbsp;createOutputStream();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;((CompressionResponseStream)&nbsp;stream).setCommit(true);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;((CompressionResponseStream)&nbsp;stream).setBuffer(threshold);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;writer&nbsp;=&nbsp;new&nbsp;PrintWriter(stream);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;writer;<BR><BR>&nbsp;&nbsp;}<BR><BR>}</TD></TR></TBODY></TABLE><BR><BR>&nbsp;<BR><BR>所有调用getOutputStream()&nbsp;或者getWriter()都返回一个使用<BR><BR>CompressResponseStream类的对象。CompressionResponseStrteam类没有显示在这个例子中，因为它继承于ServletOutputStream并使用java.util.zip.GZIPOutputStream类来压缩流。<BR><BR>&nbsp;<BR><BR>Tomcat的”examples”Web程序中已经预先配置了这个压缩过滤器并加载了一个示例servlet。示例servlet响应/CompressionTestURL（确定先前的路径是/examples）。使用我制作的有用的WAR文件，你可以用/servlet/compressionTest(再次提醒，别忘了适当的前导路径)访问此测试servlet。你可以使用如下的web.xml片段来配置这个测试：<BR>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 borderColorDark=#ffffff cellPadding=4 width="98%" align=center bgColor=#e6e6e6 borderColorLight=#009ace border=1>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word"><BR>&lt;filter&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-name&gt;compressionFilter&lt;/filter-name&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-class&gt;CompressionFilter&lt;/filter-class&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;init-param&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;param-name&gt;compressionThreshold&lt;/param-name&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;param-value&gt;10&lt;/param-value&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/init-param&gt;<BR><BR>&lt;/filter&gt;<BR><BR>&nbsp;<BR><BR>&lt;filter-mapping&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-name&gt;compressionFilter&lt;/filter-name&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;servlet-name&gt;compressionTest&lt;/servlet-name&gt;<BR><BR>&lt;/filter-mapping&gt;<BR><BR>&nbsp;<BR><BR>&lt;servlet&gt;<BR><BR>&nbsp;&nbsp;&lt;servlet-name&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;compressionTest<BR><BR>&nbsp;&nbsp;&lt;/servlet-name&gt;<BR><BR>&nbsp;&nbsp;&lt;servlet-class&gt;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;CompressionTestServlet<BR><BR>&nbsp;&nbsp;&lt;/servlet-class&gt;<BR><BR>&lt;/servlet&gt;</TD></TR></TBODY></TABLE><BR>&nbsp;<BR><BR>CompressionTestServlet（这里没有显示）输出压缩是否可用，如果可用，则输出压缩响应成功！&nbsp;<BR>&nbsp;<BR><BR>&nbsp;<BR><BR>//~~~~~~~~~~CSDN这里到底有多少字数限制？<BR><BR>请各位斧正了：）<BR><BR>来自:csdn<img src ="http://www.blogjava.net/kapok/aggbug/3334.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-15 23:50 <a href="http://www.blogjava.net/kapok/archive/2005/04/15/3334.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Digester学习笔记(二)</title><link>http://www.blogjava.net/kapok/archive/2005/04/15/3300.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 15 Apr 2005 01:28:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/15/3300.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3300.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/15/3300.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3300.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3300.html</trackback:ping><description><![CDATA[<DIV class=blogbody>
<H3 class=title id=startcontent>Digester学习笔记(二)</H3>
<P class=title><A href="http://www.infomall.cn/cgi-bin/mallgate/20040310/http://hedong.3322.org/archives/000336.html">http://www.infomall.cn/cgi-bin/mallgate/20040310/http://hedong.3322.org/archives/000336.html</A></P>
<P>　　为便于理解，将笔记的内容结构作了一些调整。</P><A name=more></A>
<P>
<H4>对象栈</H4>　　对digester技术最普通的应用，是用来动态创建一个由Java对象构成的树结构，各对象的属性以及对象间的关系，基于XML文档的内容来设置(XML文档就是一棵树)。为实现这种应用，Digester提供了一个对象栈，以供在相关的模板识别后被激活的处理规则操作。此栈的基本操作包括：<BR>
<OL>
<LI>clear(),清空栈的内容<BR>
<LI>peek(),返回对栈顶对象的引用<BR>
<LI>pop(),将栈顶对象弹出并返回<BR>
<LI>push(),将一个新的对象压入栈顶</LI></OL><BR>　　用栈的原因，就是当识别出一个XML元素的“开始”时，将相关对象生成并压入栈顶，这个对象在处理该元素的子元素的过程中一直在栈中，当所有子元素都处理完后，解析器遇到这个元素的“结束”时，则弹出此对象，并进行相关的处理。<BR>　　如何描述对象间的关系呢？将栈顶的对象做为一个参数，传递给第二栈顶(即先于栈顶对象入栈的那个对象，在栈顶对象的下面)的一个方法，就可以简单地建立起一种“父子关系”，从而可以简单地建立起1：1的关系(第二栈顶对象与栈顶对象之间)和1：N的关系(第二栈顶对象不动，N次压栈顶弹栈顶对象).<BR>　　如果取得生成的第一个对象呢？可以让parse()方法返回，或者在调用parse()方法前，先行压入一个对象，在parse()方法结束后弹出这个对象，则其子对象即为我们想要的第一个对象。<BR>
<H4>日志(logging)</H4><BR>　　日志是一个调试Digester规则集的非常重要的工具，它可以记录非常丰富的信息，因它在使用Digester之前有必要了解日志是如何工作的。<BR>　　Digester使用Jakarta Commons Logging，这个模块并不是具体的日志实现，而只是一个可设置的接口。可以设置它将各种日志信息传递它自身带的基本记录器，或者传递给其它的更复杂的日志工具。具体请参考commons logging的文档，或<A href="http://www.infomall.cn/cgi-bin/mallgate/20040310/http://hedong.3322.org/archives/000316.html">Jakarta Commons Logging学习笔记</A><BR>　　Digester主要使用两个记录器： 
<OL>
<LI>SAX相关的信息，被送往org.apache.commons.digester.Digester.sax记录器，记录了Digester收到的SAX的事件的信息。 
<LI>其它的所有信息，都被送往org.apache.commons.digester.Digester记录器，这个记录器在调试Digester时打开而在产品中常将其关闭</LI></OL><BR>　　假定用commons logging自带的基本日志工具，并以DEBUG级别记录Digester调试信息以及INFO级别记录SAX事件信息，则对logging的配置文件设置如下： 
<DIV class=code>org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog org.apache.commons.logging.simplelog.log.org.apache.commons.digester.Digester=debug org.apache.commons.logging.simplelog.log.org.apache.commons.digester.Digester.sax=info</DIV><BR>
<H4>Digester包中的例子</H4>
<DIV class=code>***********Example.xml**********<BR>&lt;address-book&gt;<BR>&nbsp;&nbsp;&lt;person&nbsp;id="1"&nbsp;category="acquaintance"&nbsp;try="would&nbsp;be&nbsp;ignored"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;name&gt;Gonzo&lt;/name&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;email&nbsp;type="business"&gt;gonzo@muppets.com&lt;/email&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;gender&nbsp;result="the&nbsp;whole&nbsp;tag&nbsp;would&nbsp;be&nbsp;ignored"&gt;male&lt;/gender&gt;<BR>&nbsp;&nbsp;&lt;/person&gt;<BR>&nbsp;&nbsp;&lt;person&nbsp;id="2"&nbsp;category="rolemodel"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;name&gt;Kermit&lt;/name&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;email&nbsp;type="business"&gt;kermit@muppets.com&lt;/email&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;email&nbsp;type="home"&gt;kermie@acme.com&lt;/email&gt;<BR>&nbsp;&nbsp;&lt;/person&gt;<BR>&lt;/address-book&gt;**********Person.java************<BR>import&nbsp;java.util.HashMap;<BR>import&nbsp;java.util.Iterator;<BR>public&nbsp;class&nbsp;Person&nbsp;{<BR>&nbsp;&nbsp;private&nbsp;int&nbsp;id;<BR>&nbsp;&nbsp;private&nbsp;String&nbsp;category;<BR>&nbsp;&nbsp;private&nbsp;String&nbsp;name;<BR>&nbsp;&nbsp;private&nbsp;HashMap&nbsp;emails&nbsp;=&nbsp;new&nbsp;HashMap();<BR>&nbsp;&nbsp;//下面的两个方法的名字中set以后的部分，与&lt;person&gt;的属性名字对映。当从xml文件中识别出&lt;person&gt;的属性时，如果有要求(即调用过addSetProperties方法),Digester会依据这种对映关系自动调用相应的方法。<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;setId(int&nbsp;id)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.id&nbsp;=&nbsp;id;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;setCategory(String&nbsp;category)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.category&nbsp;=&nbsp;category;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;//对name而言，因为其值来自&lt;name&gt;标签的内容而非属性值，需要用addCallMethod指定识别&lt;name&gt;后的要调用此方法(想自动调用也要可以，需要addBeanPropertySetter，参见第下一个例子)。<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;setName(String&nbsp;name)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.name&nbsp;=&nbsp;name;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;//同name，此时还要一一指定addEmail的参数值的来源。<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;addEmail(String&nbsp;type,&nbsp;String&nbsp;address)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;emails.put(type,&nbsp;address);<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;print()&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Person&nbsp;#"&nbsp;+&nbsp;id);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("&nbsp;&nbsp;category="&nbsp;+&nbsp;category);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("&nbsp;&nbsp;name="&nbsp;+&nbsp;name);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for(Iterator&nbsp;i&nbsp;=&nbsp;emails.keySet().iterator();&nbsp;i.hasNext();&nbsp;)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;type&nbsp;=&nbsp;(String)&nbsp;i.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;address&nbsp;=&nbsp;(String)&nbsp;emails.get(type);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("&nbsp;&nbsp;email&nbsp;(type&nbsp;"&nbsp;+&nbsp;type&nbsp;+&nbsp;")&nbsp;:&nbsp;"&nbsp;+&nbsp;address);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>}<BR>**********AddressBook.java***********<BR>import&nbsp;java.util.LinkedList;<BR>import&nbsp;java.util.Iterator;<BR>public&nbsp;class&nbsp;AddressBook&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;LinkedList&nbsp;people&nbsp;=&nbsp;new&nbsp;LinkedList();<BR>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;addPerson(Person&nbsp;p)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;people.addLast(p);<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;print()&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Address&nbsp;book&nbsp;has&nbsp;"&nbsp;+&nbsp;people.size()&nbsp;+&nbsp;"&nbsp;entries"); 
<P></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for(Iterator&nbsp;i&nbsp;=&nbsp;people.iterator();&nbsp;i.hasNext();&nbsp;)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Person&nbsp;p&nbsp;=&nbsp;(Person)&nbsp;i.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p.print();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>}<BR>************AddressBookDigester*********<BR>import&nbsp;org.apache.commons.digester.Digester;<BR>/**<BR>&nbsp;*&nbsp;Usage:&nbsp;java&nbsp;Example1&nbsp;example.xml<BR>&nbsp;*/<BR>public&nbsp;class&nbsp;AddressBookDigester&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(args.length&nbsp;!=&nbsp;1)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;usage();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.exit(-1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;filename&nbsp;=&nbsp;args[0];<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;创建一个Digester实例<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Digester&nbsp;d&nbsp;=&nbsp;new&nbsp;Digester();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;创建AddressBook实例，并将其压入栈顶。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AddressBook&nbsp;book&nbsp;=&nbsp;new&nbsp;AddressBook();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.push(book);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;增加规则<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addRules(d);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;处理输入的xml文件<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;java.io.File&nbsp;srcfile&nbsp;=&nbsp;new&nbsp;java.io.File(filename);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.parse(srcfile);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch(java.io.IOException&nbsp;ioe)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Error&nbsp;reading&nbsp;input&nbsp;file:"&nbsp;+&nbsp;ioe.getMessage());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.exit(-1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch(org.xml.sax.SAXException&nbsp;se)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Error&nbsp;parsing&nbsp;input&nbsp;file:"&nbsp;+&nbsp;se.getMessage());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.exit(-1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;将解析出的地址数据打印出来<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;book.print();<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;static&nbsp;void&nbsp;addRules(Digester&nbsp;d)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;当遇到&lt;person&gt;时，创建类Person的一个实例，并将其压入栈顶<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addObjectCreate("address-book/person",&nbsp;Person.class);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;将&lt;person&gt;标签的属性(attribute)与栈顶Person类对象的属性(property)设置方法根据各自的名字进行映射，(例如，将标签属性id与属性设置方法setId进行映射，将标签属性category与属性设置方法setCategory进行映射)，然后将属性的值作参数传递给执行相应的方法。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;如果某标签属性没法通过名字找到相应的属性设置方法，则此标签属性被忽略(如example.xml中第一个&lt;person&gt;的try属性)。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addSetProperties("address-book/person");</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;调用第二栈顶对象(AddressBook实例)的addPerson方法，以栈对象(Person实例)的对象为参数<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addSetNext("address-book/person",&nbsp;"addPerson");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;当遇到&lt;person&gt;的子元素&lt;name&gt;时，调用栈顶对象(Person实例)的setName方法。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;此处addCallMethod方法的第一参数是规则，第二个参数是方法的名字，第三个是参数的数量(为0时，表示只有一个参数，且参数的值是元素的内容)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addCallMethod("address-book/person/name",&nbsp;"setName",&nbsp;0);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;当遇到&lt;person&gt;的子元素&lt;email&gt;时，调用栈顶对象(Person实例)的addEmail方法,addEmail方法有两个参数，取值分别来自&lt;email&gt;的属性type的值和&lt;email&gt;本身的内容。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;此处addCallParam方法的第一参数是规则，第二个参数是指明被调用方法(addEmail)参数的序号，第三个是参数为字符串时指属性的名字)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addCallMethod("address-book/person/email",&nbsp;"addEmail",&nbsp;2);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addCallParam("address-book/person/email",&nbsp;0,&nbsp;"type");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addCallParam("address-book/person/email",&nbsp;1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;}</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;static&nbsp;void&nbsp;usage()&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Usage:&nbsp;java&nbsp;Example1&nbsp;example.xml");<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>}</P></DIV>运行结果如下（运行时可能需要xml-crimson,一个源sun的XML解析器，可到http://xml.apache.org/crimson/下载) 
<DIV class=code>Address&nbsp;book&nbsp;has&nbsp;2&nbsp;entries<BR>Person&nbsp;#1<BR>&nbsp;&nbsp;category=acquaintance<BR>&nbsp;&nbsp;name=Gonzo<BR>&nbsp;&nbsp;email&nbsp;(type&nbsp;business)&nbsp;:&nbsp;gonzo@muppets.com<BR>Person&nbsp;#2<BR>&nbsp;&nbsp;category=rolemodel<BR>&nbsp;&nbsp;name=Kermit<BR>&nbsp;&nbsp;email&nbsp;(type&nbsp;business)&nbsp;:&nbsp;kermit@muppets.com<BR>&nbsp;&nbsp;email&nbsp;(type&nbsp;home)&nbsp;:&nbsp;kermie@acme.com</DIV>
<P></P><SPAN class=posted>Posted by Hilton at October 26, 2003 11:46 PM | <A onclick="OpenTrackback(this.href); return false" href="http://www.infomall.cn/cgi-bin/mallgate/20040310/http://hedong.3322.org/mt/mt-tb.cgi?__mode=view&amp;entry_id=336">TrackBack</A> <BR></SPAN></DIV><img src ="http://www.blogjava.net/kapok/aggbug/3300.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-15 09:28 <a href="http://www.blogjava.net/kapok/archive/2005/04/15/3300.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Servlet Life Cycle </title><link>http://www.blogjava.net/kapok/archive/2005/04/14/3273.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 14 Apr 2005 03:56:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/14/3273.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3273.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/14/3273.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3273.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3273.html</trackback:ping><description><![CDATA[<BLOCKQUOTE>
<H2><A name=64198></A><A href="http://java.sun.com/webservices/docs/1.0/tutorial/doc/Servlets4.html#64218">http://java.sun.com/webservices/docs/1.0/tutorial/doc/Servlets4.html#64218</A><FONT face=sans-serif color=#666699></FONT></H2>
<H2><FONT face=sans-serif color=#666699></FONT>&nbsp;</H2>
<H2><FONT face=sans-serif color=#666699>Servlet Life Cycle</FONT> </H2>
<P><A name=64200></A><FONT size=+0>The life cycle of a servlet is controlled by the container in which the servlet has been deployed. When a request is mapped to a servlet, the container performs the following steps.</FONT> </P>
<OL type=1><FONT size=+0>
<LI value=1><A name=64202></A>If an instance of the servlet does not exist, the Web container</FONT> 
<OL type=a><FONT size=+0>
<LI><A name=64203></A>Loads the servlet class.</FONT> <FONT size=+0>
<LI><A name=64204></A>Creates an instance of the servlet class.</FONT> <FONT size=+0>
<LI><A name=64205></A>Initializes the servlet instance by calling the <CODE>init</CODE> method. Initialization is covered in <A href="http://java.sun.com/webservices/docs/1.0/tutorial/doc/Servlets6.html#64410">Initializing a Servlet</A>.</FONT> </LI></OL><FONT size=+0>
<LI value=2><A name=64209></A>Invokes the <CODE>service</CODE> method, passing a request and response object. Service methods are discussed in <A href="http://java.sun.com/webservices/docs/1.0/tutorial/doc/Servlets7.html#64424">Writing Service Methods</A>.</FONT> </LI></OL>
<P><A name=64213></A><FONT size=+0>If the container needs to remove the servlet, it finalizes the servlet by calling the servlet's <CODE>destroy</CODE> method. Finalization is discussed in <A href="http://java.sun.com/webservices/docs/1.0/tutorial/doc/Servlets12.html#64798">Finalizing a Servlet</A>.</FONT> </P>
<H3><A name=64218></A><FONT face=sans-serif color=#666699>Handling Servlet Life Cycle Events</FONT> </H3>
<P><A name=64221></A><FONT size=+0>You can monitor and react to events in a servlet's life cycle by defining listener objects whose methods get invoked when life cycle events occur. To use these listener objects you must define the listener class and specify the listener class.</FONT> </P>
<H4><A name=64224></A><FONT face=sans-serif color=#666699>Defining The Listener Class</FONT> </H4>
<P><A name=64230></A><FONT size=+0>You define a listener class as an implementation of a listener interface. <A href="http://java.sun.com/webservices/docs/1.0/tutorial/doc/Servlets4.html#64237">Servlet Life Cycle Events</A> lists the events that can be monitored and the corresponding interface that must be implemented. When a listener method is invoked, it is passed an event that contains information appropriate to the event. For example, the methods in the <CODE>HttpSessionListener</CODE> interface are passed an <CODE>HttpSessionEvent</CODE>, which contains an <CODE>HttpSession</CODE>. 
<P></P>
<TABLE cellSpacing=0 cellPadding=5 border=1>
<CAPTION align=left><B><FONT face=""><A name=64237></A>Table 12-3 Servlet Life Cycle Events&nbsp;</FONT></B></CAPTION>
<TBODY>
<TR align=middle bgColor=#cccccc>
<TH><FONT face="" color=#666699><A name=64243></A><FONT size=+0>Object<BR></FONT></FONT></TH>
<TH><FONT face="" color=#666699><A name=64245></A><FONT size=+0>Event<BR></FONT></FONT></TH>
<TH><FONT face="" color=#666699><A name=64247></A><FONT size=+0>Listener Interface and Event Class<BR></FONT></FONT></TH></TR>
<TR>
<TD rowSpan=2><FONT face=""><A name=64249></A><FONT size=+0>Web context(See <A href="http://java.sun.com/webservices/docs/1.0/tutorial/doc/Servlets10.html#64724">Accessing the Web Context</A>)<BR></FONT></FONT></TD>
<TD><FONT face=""><A name=64254></A><FONT size=+0>Initialization and destruction <BR></FONT></FONT></TD>
<TD><FONT face=""><A name=64257></A><FONT size=+0><CODE><A href="http://java.sun.com/webservices/docs/1.0/api/javax/servlet/ServletContextListener.html" target=_blank>javax.servlet.ServletContextListener</A></CODE> and <BR></FONT><A name=64259></A><FONT size=+0><CODE><A href="http://java.sun.com/webservices/docs/1.0/api/javax/servlet/ServletContextEvent.html" target=_blank>ServletContextEvent</A></CODE><BR></FONT></FONT></TD></TR>
<TR>
<TD><FONT face=""><A name=64263></A><FONT size=+0>Attribute added, removed, or replaced<BR></FONT></FONT></TD>
<TD><FONT face=""><A name=64266></A><FONT size=+0><CODE><A href="http://java.sun.com/webservices/docs/1.0/api/javax/servlet/ServletContextAttributeListener.html" target=_blank>javax.servlet.ServletContextAttributeListener</A></CODE> and<BR></FONT><A name=64268></A><FONT size=+0><CODE><A href="http://java.sun.com/webservices/docs/1.0/api/javax/servlet/ServletContextAttributeEvent.html" target=_blank>ServletContextAttributeEvent</A></CODE><BR></FONT></FONT></TD></TR>
<TR>
<TD rowSpan=2><FONT face=""><A name=64270></A><FONT size=+0>Session(See <A href="http://java.sun.com/webservices/docs/1.0/tutorial/doc/Servlets11.html#64744">Maintaining Client State</A>)<BR></FONT></FONT></TD>
<TD><FONT face=""><A name=64275></A><FONT size=+0>Creation, invalidation, and timeout<BR></FONT></FONT></TD>
<TD><FONT face=""><A name=64278></A><FONT size=+0><CODE><A href="http://java.sun.com/webservices/docs/1.0/api/javax/servlet/http/HttpSessionListener.html" target=_blank>javax.servlet.http.HttpSessionListener</A></CODE> and <BR></FONT><A name=64280></A><FONT size=+0><CODE><A href="http://java.sun.com/webservices/docs/1.0/api/javax/servlet/http/HttpSessionEvent.html" target=_blank>HttpSessionEvent</A></CODE><BR></FONT></FONT></TD></TR>
<TR>
<TD><FONT face=""><A name=64284></A><FONT size=+0>Attribute added, removed, or replaced<BR></FONT></FONT></TD>
<TD><FONT face=""><A name=64287></A><FONT size=+0><CODE><A href="http://java.sun.com/webservices/docs/1.0/api/javax/servlet/http/HttpSessionAttributeListener.html" target=_blank>javax.servlet.http.HttpSessionAttributeListener</A></CODE> and<BR></FONT><A name=64289></A><FONT size=+0><CODE><A href="http://java.sun.com/webservices/docs/1.0/api/javax/servlet/http/HttpSessionBindingEvent.html" target=_blank>HttpSessionBindingEvent</A></CODE><BR></FONT></FONT></TD></TR></TBODY></TABLE></FONT>
<P></P>
<P><A name=64292></A><FONT size=+0>The <CODE><A href="http://java.sun.com/webservices/docs/1.0/tutorial/examples/web/bookstore1/src/listeners/ContextListener.java" target=_blank>listeners.ContextListener</A></CODE> class creates and removes the database helper and counter objects used in the Duke's Bookstore application. The methods retrieve the Web context object from <CODE>ServletContextEvent</CODE> and then store (and remove) the objects as servlet context attributes. </FONT></P>
<BLOCKQUOTE><PRE>import database.BookDB;
import javax.servlet.*;
import util.Counter;

public final class ContextListener
&nbsp;&nbsp;implements ServletContextListener {
&nbsp;&nbsp;private ServletContext context = null;
&nbsp;&nbsp;public void contextInitialized(ServletContextEvent event) {
&nbsp;&nbsp;&nbsp;&nbsp;context = event.getServletContext();
&nbsp;&nbsp;&nbsp;&nbsp;try {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BookDB bookDB = new BookDB();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;context.setAttribute("bookDB", bookDB);
&nbsp;&nbsp;&nbsp;&nbsp;} catch (Exception ex) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Couldn't create database: " 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ ex.getMessage());
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;Counter counter = new Counter();
&nbsp;&nbsp;&nbsp;&nbsp;context.setAttribute("hitCounter", counter);
&nbsp;&nbsp;&nbsp;&nbsp;context.log("Created hitCounter"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ counter.getCounter());
&nbsp;&nbsp;&nbsp;&nbsp;counter = new Counter();
&nbsp;&nbsp;&nbsp;&nbsp;context.setAttribute("orderCounter", counter);
&nbsp;&nbsp;&nbsp;&nbsp;context.log("Created orderCounter"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ counter.getCounter());
&nbsp;&nbsp;}

&nbsp;&nbsp;public void contextDestroyed(ServletContextEvent event) {
&nbsp;&nbsp;&nbsp;&nbsp;context = event.getServletContext();
&nbsp;&nbsp;&nbsp;&nbsp;BookDB bookDB = context.getAttribute(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"bookDB");
&nbsp;&nbsp;&nbsp;&nbsp;bookDB.remove();
&nbsp;&nbsp;&nbsp;&nbsp;context.removeAttribute("bookDB");
&nbsp;&nbsp;&nbsp;&nbsp;context.removeAttribute("hitCounter");
&nbsp;&nbsp;&nbsp;&nbsp;context.removeAttribute("orderCounter");
&nbsp;&nbsp;}
}
<A name=64293> </A>
</PRE></BLOCKQUOTE>
<H4><A name=64294></A><FONT face=sans-serif color=#666699>Specifying Event Listener Classes</FONT> </H4>
<P><A name=67335></A><FONT size=+0>To specify an event listener class, you add a <CODE>listener</CODE> element to the Web application deployment descriptor. Here is the <CODE>listener</CODE> element for the Duke's Bookstore application:</FONT> </P>
<BLOCKQUOTE><PRE>&lt;listener&gt;
&nbsp;&nbsp;&lt;listener-class&gt;listeners.ContextListener&lt;/listener-class&gt;
&lt;/listener&gt;
<A name=67304> </A>
</PRE></BLOCKQUOTE>
<P><A name=71003></A><FONT size=+0>You specify a listener class for a WAR in the <CODE>deploytool</CODE> Event Listeners inspector (see <A href="http://java.sun.com/webservices/docs/1.0/tutorial/doc/WebApp4.html#75346">Event Listeners</A>).</FONT> </P>
<H3><A name=64301></A><FONT face=sans-serif color=#666699>Handling Errors</FONT> </H3>
<P><A name=64308></A><FONT size=+0>Any number of exceptions can occur when a servlet is executed. The Web container will generate a default page containing the message <CODE>A Servlet Exception Has Occurred </CODE>when an exception occurs, but you can also specify that the container should return a specific error page for a given exception. To specify such a page, you add an <CODE>error-page</CODE> element to the Web application deployment descriptor. These elements map the exceptions returned by the Duke's Bookstore application to <CODE>errorpage.html</CODE>:</FONT> </P>
<BLOCKQUOTE><PRE>&lt;error-page&gt;
&nbsp;&nbsp;&lt;exception-type&gt;
&nbsp;&nbsp;&nbsp;&nbsp;exception.BookNotFoundException
&nbsp;&nbsp;&lt;/exception-type&gt;
&nbsp;&nbsp;&lt;location&gt;/errorpage.html&lt;/location&gt;
&lt;/error-page&gt;
&lt;error-page&gt;
&nbsp;&nbsp;&lt;exception-type&gt;
&nbsp;&nbsp;&nbsp;&nbsp;exception.BooksNotFoundException
&nbsp;&nbsp;&lt;/exception-type&gt;
&nbsp;&nbsp;&lt;location&gt;/errorpage.html&lt;/location&gt;
&lt;/error-page&gt;
&lt;error-page&gt;
&nbsp;&nbsp;&lt;exception-type&gt;exception.OrderException&lt;/exception-type&gt;
&nbsp;&nbsp;&lt;location&gt;/errorpage.html&lt;/location&gt;
&lt;/error-page&gt;
<A name=67341> </A>
</PRE></BLOCKQUOTE>
<P><A name=71009></A><FONT size=+0>You specify error pages for a WAR in the <CODE>deploytool</CODE> File Refs inspector (see <A href="http://java.sun.com/webservices/docs/1.0/tutorial/doc/WebApp4.html#69666">Error Mappings</A>).</FONT> </P></BLOCKQUOTE><img src ="http://www.blogjava.net/kapok/aggbug/3273.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-14 11:56 <a href="http://www.blogjava.net/kapok/archive/2005/04/14/3273.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>About tag</title><link>http://www.blogjava.net/kapok/archive/2005/04/13/3222.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Wed, 13 Apr 2005 08:38:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/13/3222.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3222.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/13/3222.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3222.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3222.html</trackback:ping><description><![CDATA[<FONT color=#555555>11. 一个Tag处理程序类必须实现的方法 <BR>答：标签处理程序类型 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;方法 <BR>&nbsp; &nbsp;Simple &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;doStartTag, doEndTag, release <BR>&nbsp; &nbsp;Attributes &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;doStartTag, doEndTag, set/getAttribute...release <BR>&nbsp; &nbsp;Body,No Itrative and &nbsp; &nbsp; &nbsp; &nbsp;doStartTag, doEndTag, release <BR>&nbsp; &nbsp;Evaluation &nbsp; <BR>&nbsp; &nbsp;Body, Itrative Evaluation &nbsp; doStartTag, doAterTag, doEndTag, release <BR>&nbsp; &nbsp;Body, Interaction &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; doStartTag, doEndTag, release, doInitbody, doAfterBody <BR><BR>12. 简单的标签处理程序类 <BR>答：1) 必须实现Tag接口的doStartTag()和doEndTag()方法; <BR>&nbsp; &nbsp;2) 因为不存在Body，doStartTag()方法必须返回SKIP_BODY; <BR>&nbsp; &nbsp;3) 如其余页面要执行，doEndTag()方法返回EVAL_PAGE, 否则返回SKIP_PAGE; <BR>&nbsp; &nbsp;4) 对于每一个标签属性，你必须在标签处理程序类里定义一个特性以及get和set方法以一致于JavaBeans体系惯例 <BR><BR>13. 带Body的自定义标签 <BR>答：1) 必须实现Tag接口的doStartTag()和doEndTag()方法; <BR>&nbsp; &nbsp;2) 可以实现IterationTag接口的doAfterBody()方法; <BR>&nbsp; &nbsp;3) 可以实现BodyTag接口的doInitBody和setBodyContent方法; <BR>&nbsp; &nbsp;4) doStartTag方法可以返回SKIP_BODY、EVAL_BODY_INCLUDE、或者EVAL_BODY_BUFFERED(当你想使用BodyContent); <BR>&nbsp; &nbsp;5) doEndTag方法可以返回SKIP_PAGE或EVAL_PAGE; <BR>&nbsp; &nbsp;6) doAfterBody方法可以返回EVAL_BODY_AGAIN, SKIP_BODY; <BR><BR>14. 定义脚本变量的标签(迭代的标签库) <BR>答：1) 定义脚本标签的二个步骤: <BR>&nbsp; &nbsp; &nbsp; a. 在标记库描述符(TLD)文件中列明脚本变量; <BR>&nbsp; &nbsp; &nbsp; b. 定义标签扩展信息类(TEI)并且在TLD文件中包括这个类元素(tei-class); <BR>&nbsp; &nbsp;2) 变量必须在标签处理程序类中使用pageContext.setAttribute()方法设置; <BR>&nbsp; &nbsp;3) 标签扩展信息类(TEI)必须继承TagExtraInfo以及覆盖getVariableInfo()方法; <BR>&nbsp; &nbsp;4) 变量的范围可以是AT_BEGIN, NESTED, AT_END(标签扩展信息类(TEI)的VariableInfo中定义)之一; <BR><BR>15. 脚本变量的有效性 <BR>答：变量 &nbsp; &nbsp; | &nbsp; 有效性 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <BR>&nbsp; &nbsp;--------------------------------------- <BR>&nbsp; &nbsp;NESTED &nbsp; | 标签中的参数在starttag到endtag之间是有效的 <BR>&nbsp; &nbsp;AT_BEGIN | 标签中的参数在标签的开始到JSP页面结束是有效的 <BR>&nbsp; &nbsp;AT_END &nbsp; | 标签中的参数在标签的结束到JSP页面结束是有效的 <BR><BR><BR><BR><BR>
<P><B>元素listener</B></P>
<P>标签库可以指定一些类——事件监听类。（参照处理servlet生命周期事件）。这些监听类都作为listener元素列于TLD中，网络容器将实例化监听类并通过与在WAR中定义的监听类类似的方法来注册。不像WAR级的监听类，，标签库中注册的监听类没有定义顺序。Listener元素的唯一子元素是listener-class，它必须包含监听类名的全称。<BR><A href="http://www.leftworld.net/online/j2ee/13.htm">http://www.leftworld.net/online/j2ee/13.htm</A></P></FONT><img src ="http://www.blogjava.net/kapok/aggbug/3222.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-13 16:38 <a href="http://www.blogjava.net/kapok/archive/2005/04/13/3222.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JSP页面中的自定义标签2</title><link>http://www.blogjava.net/kapok/archive/2005/04/13/3220.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Wed, 13 Apr 2005 08:09:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/13/3220.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3220.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/13/3220.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3220.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3220.html</trackback:ping><description><![CDATA[<H3><A href="http://dev.csdn.net/article/48/48512.shtm">http://dev.csdn.net/article/48/48512.shtm</A><A name=wp66577></A></H3>
<H3>&nbsp;</H3>
<H3>带属性的标签 </H3>
<H4><A name=wp66579></A>在标签handler中定义属性</H4>
<P><A name=wp66581></A>对于每一个标签属性，都必须在标签handler中定义一个属性以及符合JavaBean结构规范的get和set方法。例如，logic:present标签的标签handler</P>
<BLOCKQUOTE>
<P>&lt;logic:present parameter="Clear"&gt;<A name=wp66582> </A></P></BLOCKQUOTE>
<P><A name=wp66583></A>包含下列声明和方法：</P>
<BLOCKQUOTE>
<P>protected String parameter = null;<BR>public String getParameter() {<BR>&nbsp;&nbsp;return (this.parameter);<BR>}<BR>public void setParameter(String parameter) {<BR>&nbsp;&nbsp;this.parameter = parameter;<BR>}<A name=wp66584> </A></P></BLOCKQUOTE>
<HR>

<P><A name=wp66585></A>注意，如果属性命名为id并且标签handler继承自TagSupport类，那么就不需要定义属性和set和get方法，因为它们已经由TagSupport定义了。</P>
<HR>

<P><A name=wp66586></A>值为String的标签属性可以指定标签handler可用的隐式对象的一个属性。通过向隐式对象的[set|get]Attribute方法传递标签属性值可以访问一个隐式对象属性。这是将脚本变量名传递给标签handler的好方式，在这里脚本变量与储存在页面上下文中的对象相关联(见<A href="http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/jpt.15/15.1.htm#wp66045">隐式对象</A>)。</P>
<H4><A name=wp66591></A>attribute元素</H4>
<P><A name=wp66592></A>对于每一个标签属性，都必须在attribute元素中指定这个属性是否是必需的、其值是否可以由表达式确定、还可能指定属性的类型。对于静态值，类型总是java.lang.String。如果rtexprvalue元素是true或者yes，那么type元素定义会将任何指定的表达式的预期返回类型指定为属性的值。</P>
<BLOCKQUOTE>
<P>&lt;attribute&gt;<BR>&nbsp;&nbsp;&lt;name&gt;<I>attr1</I>&lt;/name&gt;<BR>&nbsp;&nbsp;&lt;required&gt;true|false|yes|no&lt;/required&gt;<BR>&nbsp;&nbsp;&lt;rtexprvalue&gt;true|false|yes|no&lt;/rtexprvalue&gt;<BR>&nbsp;&nbsp;&lt;type&gt;<I>fully_qualified_type</I>&lt;/type&gt;<BR>&lt;/attribute&gt;<A name=wp66594> </A></P></BLOCKQUOTE>
<P><A name=wp66595></A>如果tag属性不是必需的，那么标签handler应该提供一个默认值。</P>
<P><A name=wp66596></A>logic:present标签的tag元素声明parameter属性不是必需的(因为标签还可以测试是否存在其它实体，如bean属性)以及其值可以由运行时表达式设置。</P>
<BLOCKQUOTE>
<P>&lt;tag&gt;<BR>&nbsp;&nbsp;&lt;name&gt;present&lt;/name&gt;<BR>&nbsp;&nbsp;&lt;tag-class&gt;org.apache.struts.taglib.<BR>&nbsp;&nbsp;&nbsp;&nbsp;logic.PresentTag&lt;/tag-class&gt;<BR>&nbsp;&nbsp;&lt;body-content&gt;JSP&lt;/body-content&gt;<BR>&nbsp;&nbsp;...<BR>&nbsp;&nbsp;&lt;attribute&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;name&gt;parameter&lt;/name&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;required&gt;false&lt;/required&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;rtexprvalue&gt;true&lt;/rtexprvalue&gt;<BR>&nbsp;&nbsp;&lt;/attribute&gt; <BR>&nbsp;&nbsp;...<BR>&lt;/tag&gt;<A name=wp66597> </A></P></BLOCKQUOTE>
<H4><A name=wp66598></A>属性验证</H4>
<P><A name=wp66599></A>标签库的文档应该描述标签属性的有效值。在转换JSP页面时，Web容器将强制应用每一个属性的TLD元素中包含的限制。</P>
<P><A name=wp66602></A>在转换时还用从TagExtraInfo派生的类的isValid方法验证传递给标签的属性。这个类也用于提供有关标签定义的脚本变量的信息(见<A href="http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/ctjp.16/16.2.htm#wp66701">提供有关脚本变量的信息</A>)。</P>
<P><A name=wp66608></A>用TagData对象向isValid方法传递属性信息，它包含每一个标签属性的属性-值元组。因为验证在转换时发生，所以在请求时计算的属性值将设置为TagData.REQUEST_TIME_VALUE。</P>
<P><A name=wp66609></A>&lt;tt:twa attr1="value1"/&gt;标签有下列TLD attribute元素：</P>
<BLOCKQUOTE>
<P>&lt;attribute&gt;<BR>&nbsp;&nbsp;&lt;name&gt;attr1&lt;/name&gt; <BR>&nbsp;&nbsp;&lt;required&gt;true&lt;/required&gt;<BR>&nbsp;&nbsp;&lt;rtexprvalue&gt;true&lt;/rtexprvalue&gt;<BR>&lt;/attribute&gt;<A name=wp66610> </A></P></BLOCKQUOTE>
<P><A name=wp66611></A>这个声明表明attr1的值可以在运行时确定。</P>
<P><A name=wp66612></A>下面的isValid方法检查attr1的值是否为有效的布尔值。注意由于的attr1值可以在运行时计算，所以isValid必须检查标签用户是否选择了提供运行时值。</P>
<BLOCKQUOTE>
<P>public class TwaTEI extends TagExtraInfo {<BR>&nbsp;&nbsp;public boolean isValid(Tagdata data) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;Object o = data.getAttribute("attr1");<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (o != null &amp;&amp; o != TagData.REQUEST_TIME_VALUE) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (((String)o).toLowerCase().equals("true") ||<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;((String)o).toLowerCase().equals("false") )<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return true;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return false;<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return true; &nbsp;&nbsp;}<BR>}<A name=wp66613> </A></P></BLOCKQUOTE>
<H3><A name=wp66614></A>带正文的标签 </H3>
<H4><A name=wp66615></A>标签handler </H4>
<P><A name=wp66617></A>带正文的标签的标签handler根据标签handler是否需要与正文交互而有不同的实现。我们说的交互的意思是标签handler读取或者修改正文的内容。</P>
<H5><A name=wp66619></A><I>标签</I><I>handler</I><I>不与正文交互</I><I> </I></H5>
<P><A name=wp66620></A>如果标签handler不需要与正文交互，那么标签handler应该实现Tag接口(或者从TagSupport派生)。如果需要对标签的正文进行判断，那么doStartTag方法就需要返回EVAL_BODY_INCLUDE，否则，它应该返回SKIP_BODY。</P>
<P><A name=wp66621></A>如果标签handler需要反复地判断正文，那么它就应该实现IterationTag接口或者从TagSupport派生。如果它确定需要再次评估正文，那么它应该从doStartTag和doAfterBody方法返回EVAL_BODY_AGAIN。</P>
<H5><A name=wp66622></A><I>标签</I><I>handler</I><I>与正文交互</I><I> </I></H5>
<P><A name=wp66623></A>如果标签handler需要与正文交互，那么标签handler必须实现BodyTag (或者从BodyTagSupport派生)。这种处理器通常实现doInitBody和doAfterBody方法。这些方法与由JSP页面的servlet传递给tag handler的正文内容交互。</P>
<P><A name=wp66624></A>正文内容支持几种读取和写入其内容的方法。标签handler可以用正文内容的getString或者getReader方法从正文中提取信息，用writeOut(out)方法将正文内容写入一个输出流。为writeOut方法提供的writer是用标签handler的getPreviousOut方法得到的。用这个方法保证标签handler的结果对于其外围标签handler是可用的。</P>
<P><A name=wp66625></A>如果需要对标签的正文进行判断，那么doStartTag方法需要返回EVAL_BODY_BUFFERED，否则它就应该返回SKIP_BODY。</P>
<P><A name=wp66626></A>doInitBody<B> </B><B>方法</B> </P>
<P><A name=wp66628></A>在已经设置正文内容之后、但是对它进行判断之前调用doInitBody方法。一般用这个方法执行所有依赖于正文内容的初始化。</P>
<P><A name=wp66630></A>doAfterBody<B>方法</B> </P>
<P><A name=wp66631></A>doAfterBody方法在判断了正文内容<I>之后</I>调用。</P>
<P><A name=wp66632></A>像doStartTag方法一样，doAfterBody必须返回指明是否继续判断正文的指示。因此，如果应该再次判断正文，就像实现枚举标签的情况，那么doAfterBody应该返回EVAL_BODY_BUFFERED，否则doAfterBody应该返回SKIP_BODY。</P>
<P><A name=wp66634></A>Release<B>方法</B> </P>
<P><A name=wp66635></A>标签handler应该在release方法中重新设置其状态并释放所有私有资源。</P>
<P><A name=wp66636></A>下面的例子读取正文的内容(它包含一个SQL查询)并将它传递给一个执行这个查询的对象。因为不需要对正文再次判断，所以doAfterBody返回SKIP_BODY。</P>
<BLOCKQUOTE>
<P>public class QueryTag extends BodyTagSupport {<BR>&nbsp;&nbsp;public int doAfterBody()<BR>throws JspTagException {<BR>&nbsp;&nbsp;&nbsp;&nbsp;BodyContent bc = getBodyContent();<BR>&nbsp;&nbsp;&nbsp;&nbsp;// get the bc as string<BR>&nbsp;&nbsp;&nbsp;&nbsp;String query = bc.getString();<BR>&nbsp;&nbsp;&nbsp;&nbsp;// clean up &nbsp;&nbsp;&nbsp;&nbsp;bc.clearBody();<BR>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Statement stmt = connection.createStatement();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result = stmt.executeQuery(query);<BR>&nbsp;&nbsp;&nbsp;&nbsp;} catch (SQLException e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new JspTagException("QueryTag: " + <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.getMessage());<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;return SKIP_BODY;<BR>&nbsp;&nbsp;}<BR>}<A name=wp66637> </A></P></BLOCKQUOTE>
<H4><A name=wp66639></A>body-content元素 </H4>
<P><A name=wp66640></A>对于有正文的标签，必须用body-content元素指定正文内容的类型：</P>
<BLOCKQUOTE>
<P>&lt;body-content&gt;JSP|tagdependent&lt;/body-content&gt;<A name=wp66642> </A></P></BLOCKQUOTE>
<P><A name=wp66643></A>正文内容包含自定义和核心标签、脚本元素以及属于JSP的HTML文字。这是为Struts logic:present标签声明的值。所有其它类型的正文内容——如传递给查询标签的SQL语句，都标记为tagdependent。</P>
<P><A name=wp66644></A>注意body-content元素的值不影响标签handler对正文的解读，这个元素只是由编写工具用于呈现正文内容。</P>
<H3><A name=wp66646></A>定义脚本变量的标签 </H3>
<H4><A name=wp66647></A>标签handler </H4>
<P><A name=wp66649></A>标签handler负责创建脚本变量引用的对象并设置到页面可以访问的上下文中。它是用pageContext.setAttribute(name, value, scope)或者pageContext.setAttribute(name, value)方法完成这项工作的。通常传递给自定义标签的属性指定脚本变量对象的名字，通过调用在<A href="http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/jst.14/14.1.htm#wp64315">使用范围对象</A>中描述的属性的get方法可以提取这个名字。</P>
<P><A name=wp66653></A>如果脚本变量的值依赖于在标签handler上下文中出现的一个对象，那么它可以用pageContext.getAttribute(name, scope)方法提取这个对象。</P>
<P><A name=wp66654></A>一般的通常过程是标签handler提取脚本变量、对对象执行一些处理、再用pageContext.setAttribute(name, object)方法设置脚本变量的值。</P>
<P><A name=wp66658></A><A href="http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/ctjp.16/16.2.htm#wp66665">表16-4</A>总结了对象可以有的作用域。作用域限制了对象的可访问性和寿命。</P>
<TABLE cellPadding=0 border=1>
<TBODY>
<TR>
<TD colSpan=3>
<P><A name=wp66665></A><I>表</I><I>16-4 </I><I>对象范围</I></P></TD></TR>
<TR>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66671></A><B>名字</B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66673></A><B>可访问性</B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66675></A><B>寿命</B></P></TD></TR>
<TR>
<TD>
<P><A name=wp66677></A>page </P></TD>
<TD>
<P><A name=wp66679></A>当前页面 </P></TD>
<TD>
<P><A name=wp66681></A>直到响应被返回到用户或者请求被传递给一个新页面 </P></TD></TR>
<TR>
<TD>
<P><A name=wp66683></A>request </P></TD>
<TD>
<P><A name=wp66685></A>当前页面及所有包含或者转发页面 </P></TD>
<TD>
<P><A name=wp66687></A>直到响应被返回到用户 </P></TD></TR>
<TR>
<TD>
<P><A name=wp66689></A>session </P></TD>
<TD>
<P><A name=wp66691></A>当前请求和所有从同一个浏览器发出的后续请求(取决于会话寿命) </P></TD>
<TD>
<P><A name=wp66693></A>用户会话的寿命 </P></TD></TR>
<TR>
<TD>
<P><A name=wp66695></A>application </P></TD>
<TD>
<P><A name=wp66697></A>同一Web应用程序的当前和所有未来请求 </P></TD>
<TD>
<P><A name=wp66699></A>应用程序的寿命 </P></TD></TR></TBODY></TABLE>
<H4>&nbsp;</H4>
<H4><A name=wp66701></A>提供有关脚本变量的信息 </H4>
<P><A name=wp66705></A>在<A href="http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/ctjp.16/16.1.htm#wp66315">定义脚本变量的标签</A>中描述的例子定义了用于访问图书信息的脚本变量：book</P>
<BLOCKQUOTE>
<P>&lt;bean:define id="book"<BR>name="bookDB" property="bookDetails"<BR>&nbsp;&nbsp;type="database.BookDetails"/&gt;<BR>&lt;font color="red" size="+2"&gt;<BR>&nbsp;&nbsp;&lt;%=messages.getString("CartRemoved")%&gt;<BR>&nbsp;&nbsp;&lt;strong&gt;&lt;jsp:getProperty name="book"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;property="title"/&gt;&lt;/strong&gt;<BR>&lt;br&gt;&amp;nbsp;&lt;br&gt;<BR>&lt;/font&gt;<A name=wp66706> </A></P></BLOCKQUOTE>
<P><A name=wp66707></A>在转换包含这个标签的JSP页面时，Web容器会生成同步脚本变量与由变量引用的对象的代码。要生成这些代码，Web容器需要关于脚本变量的一些信息：</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66709></A>·&nbsp; 变量名</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66710></A>·&nbsp; 变量类</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66711></A>·&nbsp; 变量是否引用新的对象或者是现有对象</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66712></A>·&nbsp; 变量的可用性 </P>
<P><A name=wp66713></A>有两种方法提供这种信息：指定variable TLD子元素或者定义tag extra info类并在TLD中包含tei-class元素。用variable元素更简单，但是灵活性要差一些。</P>
<H5><A name=wp66715></A><I>variable </I><I>元素</I><I> </I></H5>
<P><A name=wp66717></A>variable元素有下列子元素：</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66718></A>·&nbsp; name-given：变量名为常量</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66719></A>·&nbsp; name-from-attribute：一个属性的名字，其转换时（translation-time）值将给出属性的名字</P>
<P><A name=wp66720></A>必须有name-given或者name-from-attribute之中的一个。下列子元素是可选的：</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66721></A>·&nbsp; variable-class—变量的完全限定名。默认为java.lang.String。</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66722></A>·&nbsp; declare—变量是否引用新对象。默认为True。</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66723></A>·&nbsp; scope—定义的脚本变量的作用域。默认为NESTED。<A href="http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/ctjp.16/wp66733">表16-5</A>描述了脚本变量的可用性以及必须设置或者重新设置变量值的方法。 </P>
<TABLE cellPadding=0 border=1>
<TBODY>
<TR>
<TD colSpan=3>
<P><A name=wp66733></A><I>表</I><I> 16-5 </I><I>脚本变量可用性</I><I>&nbsp; </I></P></TD></TR>
<TR>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66739></A><B>值</B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66741></A><B>可用性</B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66743></A><B>方法</B></P></TD></TR>
<TR>
<TD>
<P><A name=wp66745></A>NESTED </P></TD>
<TD>
<P><A name=wp66747></A>开始和结束标签之间 </P></TD>
<TD>
<P><A name=wp66749></A>在实现BodyTag的标签handler的doInitBody 和doAfterBody方法中，否则，在 doStartTag中</P></TD></TR>
<TR>
<TD>
<P><A name=wp66751></A>AT_BEGIN </P></TD>
<TD>
<P><A name=wp66753></A>从开始标签到页面的结束 </P></TD>
<TD>
<P><A name=wp66755></A>在实现BodyTag的标签handler的doInitBody 和doAfterBody方法中，否则，在 doStartTag和doEndTag中</P></TD></TR>
<TR>
<TD>
<P><A name=wp66757></A>AT_END </P></TD>
<TD>
<P><A name=wp66759></A>在结束标签之后直到页面的结束 </P></TD>
<TD>
<P><A name=wp66761></A>在doEndTag中</P></TD></TR></TBODY></TABLE>
<P><A name=wp66762></A>Struts bean:define标签的实现符合JSP规范版本1.1，它要求定义tag extra info类。JSP规范版本1.2增加了variable元素。可以为bean:define标签定义下面的variable元素：</P>
<BLOCKQUOTE>
<P>&lt;tag&gt;<BR>&nbsp;&nbsp;&lt;variable&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;name-from-attribute&gt;id&lt;/name-from-attribute&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;variable-class&gt;database.BookDetails&lt;/variable-class&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;declare&gt;true&lt;/declare&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;scope&gt;AT_BEGIN&lt;/scope&gt;<BR>&nbsp;&nbsp;&lt;/variable&gt;<BR>&lt;/tag&gt;<A name=wp66764> </A></P></BLOCKQUOTE>
<H5><A name=wp66766></A><I>TagExtraInfo</I><I>类</I></H5>
<P><A name=wp66767></A>通过扩展类javax.servlet.jsp.TagExtraInfo定义tag extra info类。TagExtraInfo. A TagExtraInfo必须实现getVariableInfo方法以返回包含下列信息的VariableInfo对象数组：</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66768></A>·&nbsp; 变量名</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66769></A>·&nbsp; 变量类</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66770></A>·&nbsp; 变量是否引用新对象</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66771></A>·&nbsp; 变量可用性</P>
<P><A name=wp66772></A>Web容器向getVariableInfo方法传递包含每一个标签属性的属性-值元组的名为data的参数。这些属性可以用于为VariableInfo对象提供脚本变量名和类。</P>
<P><A name=wp66773></A>Struts标签库提供有关由DefineTei tag extra info类中的bean:define标签创建的脚本变量的信息。由于脚本变量的name (book)和class (database.BookDetails)作为标签属性传递，所以可以用data.getAttributeString方法提取它们，并用于填充VariableInfo构造函数。要使脚本变量book用于页面的其他地方，book的作用域设置为AT_BEGIN。</P>
<BLOCKQUOTE>
<P>public class DefineTei extends TagExtraInfo {<BR>&nbsp;&nbsp;public VariableInfo[] getVariableInfo(TagData data) {<BR>&nbsp;&nbsp;String type = data.getAttributeString("type");<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (type == null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type = "java.lang.Object";<BR>&nbsp;&nbsp;&nbsp;&nbsp;return new VariableInfo[] {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new VariableInfo(data.getAttributeString("id"),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;true,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;VariableInfo.AT_BEGIN)<BR>&nbsp;&nbsp;&nbsp;&nbsp;};<BR>&nbsp;&nbsp;}<BR>}<A name=wp66775> </A></P></BLOCKQUOTE>
<P><A name=wp66776></A><A name=May21></A>为脚本变量定义的tag extra info类的完全限定名必须在tag元素的tei-class子元素的TLD中声明。因此，DefineTei的tei-class元素像下面这样：</P>
<BLOCKQUOTE>
<P>&lt;tei-class&gt;<BR>&nbsp;&nbsp;org.apache.struts.taglib.bean.DefineTagTei<BR>&lt;/tei-class&gt;<A name=wp66777> </A></P></BLOCKQUOTE>
<H3><A name=wp66780></A>标签协同操作 </H3>
<P><A name=wp66781></A>标签通过共享对象实现合作。JSP技术支持两种类型的对象共享。</P>
<P><A name=wp66782></A>第一种类型要求在页面上下文中命名和储存共享的对象(JSP页面和标签handler都可以访问的一种隐式对象)。要访问由另一个标签创建和命名的对象，标签handler使用pageContext.getAttribute(name, scope)方法。</P>
<P><A name=wp66783></A>在第二种对象共享类型中，由一组嵌入标签中的外围标签handler创建的对象可以被所有内部标签handler访问。这种形式的对象共享的优点是它对对象使用私有命名空间，因此减少了潜在的命名冲突。</P>
<P><A name=wp66784></A>要访问由外围标签创建的对象，标签handler必须首先用静态方法TagSupport.findAncestorWithClass(from, class)或者TagSupport.getParent方法获得其外围标签。在不能保证有特定的嵌入标签handler时应该使用前一个方法。一旦获取了上级，那么标签handler就可以访问所有静态或动态创建的对象了。静态创建的对象是父标签的成员。私有对象也可以动态创建。这种对象可以用setValue方法储存在标签 handler中，并用getValue方法获取它。</P>
<P><A name=wp66785></A>下面的例子展示了同时支持命名的和私有对象方式共享对象的标签handler。在这个例子中，查询标签的handler检查名为connection的属性是否已在doStartTag方法中设置。如果属性已经设置，那么handler就从页面上下文中获取连接对象。否则，标签handler首先获取外围标签的标签handler，然后从那个handler中获取连接对象。</P>
<BLOCKQUOTE>
<P>public class QueryTag extends BodyTagSupport {<BR>&nbsp;&nbsp;private String connectionId;<BR>&nbsp;&nbsp;public int doStartTag()<BR>throws JspException {<BR>&nbsp;&nbsp;&nbsp;&nbsp;String cid = getConnection();<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (cid != null) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;// there is a connection id, use it<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;connection =(Connection)pageContext.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getAttribute(cid);<BR>&nbsp;&nbsp;&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ConnectionTag ancestorTag =<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(ConnectionTag)findAncestorWithClass(this,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ConnectionTag.class);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (ancestorTag == null) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new JspTagException("A query without<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a connection attribute must be nested<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;within a connection tag.");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;connection = ancestorTag.getConnection();<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>}<A name=wp66786> </A></P></BLOCKQUOTE>
<P><A name=wp66787></A>由这个标签 handler实现的查询标签可以以下面任何一种方式使用：</P>
<BLOCKQUOTE>
<P>&lt;tt:connection id="con01" ....&gt;<BR>...<BR>&lt;/tt:connection&gt;<BR>&lt;tt:query id="balances" connection="con01"&gt;<BR>&nbsp;&nbsp;SELECT account, balance FROM acct_table<BR>&nbsp;&nbsp;&nbsp;&nbsp;where customer_number = &lt;%= request.getCustno()%&gt;<BR>&lt;/tt:query&gt;<BR>&nbsp; &lt;tt:connection ...&gt;<BR>&nbsp;&nbsp;&lt;x:query id="balances"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;SELECT account, balance FROM acct_table<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;where customer_number = &lt;%= request.getCustno()%&gt;<BR>&nbsp;&nbsp;&lt;/x:query&gt;<BR>&lt;/tt:connection&gt;<A name=wp66788> </A></P></BLOCKQUOTE>
<P><A name=wp66789></A>标签handler的TLD必须用下面声明指明connection属性是可选的：</P>
<BLOCKQUOTE>
<P>&lt;tag&gt;<BR>&nbsp;&nbsp;...<BR>&nbsp;&nbsp;&lt;attribute&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;name&gt;connection&lt;/name&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;required&gt;false&lt;/required&gt;<BR>&nbsp;&nbsp;&lt;/attribute&gt;<BR>&lt;/tag&gt;<A name=wp66790> </A></P></BLOCKQUOTE>
<H2><A name=wp66793></A><FONT size=4>示例</FONT> </H2>
<P><A name=wp66794></A>本节中描述的自定义标签展示了在开发JSP应用程序时会经常遇到的两个问题的解决方法：尽可能减少JSP页面中的Java编程以及保证整个应用程序的共同外观。在这个过程中，展示了本章前面讨论过的许多类型的标签。</P>
<H3><A name=wp66795></A>迭代（Iteration）标签 </H3>
<P><A name=wp66796></A>构建依赖于动态生成的数据的页面内容通常需要使用流控制脚本语句。通过将流控制逻辑转换到标签handler中，流控制标签减少了在JSP页面中需要的脚本量。</P>
<P><A name=wp66798></A>Struts logic:iterate标签从储存在JavaBeans组件中的集合中获取对象并将它们指定给脚本变量。标签的正文从脚本变量中提取信息。如果集合中仍有元素，则iterate标签会再次对正文进行判断。</P>
<H4><A name=wp66799></A>JSP页面 </H4>
<P><A name=wp66802></A>两个Duke's Bookstore应用程序页面<A href="http://java.sun.com/webservices/docs/1.1/tutorial/examples/web/bookstore3/web/catalog.txt">catalog.jsp</A>和<A href="http://java.sun.com/webservices/docs/1.1/tutorial/examples/web/bookstore3/web/showcart.txt">showcart.jsp</A>使用了logic:iterate标签以迭代对象的集合。下面展示了catalog.jsp的一部分。JSP页面用bookDB bean集合(由property属性命名)初始化iterate标签。iterate标签在对集合上的每一次迭代中设置book脚本变量。book变量的bookId属性作为另一个脚本变量公开。两个变量的属性都用于动态生成一个包含到其他页面的图书目录信息的链接的表。</P>
<BLOCKQUOTE>
<P>&lt;logic:iterate name="bookDB" property="books"<BR>&nbsp;&nbsp;id="book" type="database.BookDetails"&gt;<BR>&nbsp;&nbsp;&lt;bean:define id="bookId" name="book" property="bookId"<BR>&nbsp;&nbsp;&nbsp;&nbsp;type="java.lang.String"/&gt;</P>
<P>&nbsp; &nbsp;&nbsp;&lt;tr&gt;<BR>&nbsp;&nbsp;&lt;td bgcolor="#ffffaa"&gt;<BR>&nbsp;&nbsp;&lt;a href="&lt;%=request.getContextPath()%&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;/bookdetails?bookId=&lt;%=bookId%&gt;"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;strong&gt;&lt;jsp:getProperty name="book" <BR>&nbsp;&nbsp;&nbsp;&nbsp;property="title"/&gt;&amp;nbsp;&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;</P>
<P>&nbsp; &nbsp;&nbsp;&lt;td bgcolor="#ffffaa" rowspan=2&gt;<BR>&nbsp;&nbsp;&lt;jsp:setProperty name="currency" property="amount"<BR>&nbsp;&nbsp;&nbsp;&nbsp;value="&lt;%=book.getPrice()%&gt;"/&gt;<BR>&nbsp;&nbsp;&lt;jsp:getProperty name="currency" property="format"/&gt;<BR>&nbsp;&nbsp;&amp;nbsp;&lt;/td&gt; &nbsp; &nbsp;&nbsp;&lt;td bgcolor="#ffffaa" rowspan=2&gt;<BR>&nbsp;&nbsp;&lt;a href="&lt;%=request.getContextPath()%&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;/catalog?Add=&lt;%=bookId%&gt;"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&amp;nbsp;&lt;%=messages.getString("CartAdd")%&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&amp;nbsp;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;</P>
<P>&nbsp; &nbsp;&nbsp;&lt;tr&gt;<BR>&nbsp;&nbsp;&lt;td bgcolor="#ffffff"&gt;<BR>&nbsp;&nbsp;&amp;nbsp;&amp;nbsp;&lt;%=messages.getString("By")%&gt; &lt;em&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;jsp:getProperty name="book"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;property="firstName"/&gt;&amp;nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;jsp:getProperty name="book"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;property="surname"/&gt; &lt;/em&gt; &lt;/td&gt; &lt;/tr&gt;<BR>&lt;/logic:iterate&gt;<A name=wp66803> </A></P></BLOCKQUOTE>
<H4><A name=wp66804></A>标签handler </H4>
<P><A name=wp66805></A>Struts logic:iterate标签的实现符合JSP版本1.1规范的要求，它需要扩展BodyTagSupport类。JSP版本1.2规范添加了简化迭代性地对正文判断的编程标签的功能(在<A href="http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/ctjp.16/16.2.htm#wp66619">不与正文交互的标签handler</A>中描述)。下面的讨论是使用这些功能的实现。</P>
<P><A name=wp66809></A>logic:iterate标签以几种方式支持集合初始化：用作为标签属性而提供的集合，或者用作为bean或者bean属性集合而提供的集合。我们的例子使用后一种方法。doStartTag中的大多数代码是关于构建对于一个对象集合的迭代器的。方法首先检查是否设置了handler的集合属性，如果没有，则进一步检查bean和property属性。如果name和property属性都设置了，则doStartTag调用使用JavaBean自省方法的工具方法来获取集合。一旦确定了集合对象，方法就构建迭代器。</P>
<P><A name=wp66810></A>如果迭代器中还有元素，那么doStartTag就设置脚本变量的值为下一个元素，然后表明要对这个正文进行判断，否则就返回SKIP_BODY结束迭代。</P>
<P><A name=wp66811></A>在判断完正文后，doAfterBody方法提取正文内容并将它写入输出流。然后清除正文内容对象以便为另一次正文判断作准备。如果迭代器还包含元素，那么doAfterBody就再次设置脚本变量的值为下一个元素并返回EVAL_BODY_AGAIN以表明应该再次对正文进行判断。这样会再次执行doAfterBody。如果没有剩余元素了，那么就返回SKIP_BODY终止这个过程。</P>
<BLOCKQUOTE>
<P>public class IterateTag extends TagSupport {<BR>&nbsp;&nbsp;protected Iterator iterator = null;<BR>&nbsp;&nbsp;protected Object collection = null;<BR>&nbsp;&nbsp;protected String id = null;<BR>&nbsp;&nbsp;protected String name = null;<BR>&nbsp;&nbsp;protected String property = null;<BR>&nbsp;&nbsp;protected String type = null; <BR>&nbsp;&nbsp;public int doStartTag() throws JspException {<BR>&nbsp;&nbsp;&nbsp;&nbsp;Object collection = this.collection;<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (collection == null) { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object bean = pageContext.findAttribute(name);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (bean == null) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;... throw an exception<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (property == null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collection = bean;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collection =<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PropertyUtils.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getProperty(bean, property);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (collection == null) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;... throw an exception<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} catch<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;... catch exceptions thrown<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;by PropertyUtils.getProperty<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;// Construct an iterator for this collection<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (collection instanceof Collection)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;iterator = ((Collection) collection).iterator();<BR>&nbsp;&nbsp;&nbsp;&nbsp;else if (collection instanceof Iterator)<BR>&nbsp;&nbsp;&nbsp;&nbsp;iterator = (Iterator) collection;<BR>&nbsp;&nbsp;&nbsp;&nbsp;...<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;// Store the first value and evaluate,<BR>&nbsp;&nbsp;&nbsp;&nbsp;// or skip the body if none<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (iterator.hasNext()) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object element = iterator.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pageContext.setAttribute(id, element);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return (EVAL_BODY_AGAIN);<BR>&nbsp;&nbsp;&nbsp;&nbsp;} else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return (SKIP_BODY); &nbsp;&nbsp;<BR>}<BR>&nbsp;&nbsp;public int doAfterBody() throws JspException { &nbsp;<BR>&nbsp;&nbsp;&nbsp;if (bodyContent != null) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JspWriter out = getPreviousOut();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.print(bodyContent.getString());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bodyContent.clearBody();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} catch (IOException e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (iterator.hasNext()) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object element = iterator.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pageContext.setAttribute(id, element);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return (EVAL_BODY_AGAIN);<BR>&nbsp;&nbsp;&nbsp;&nbsp;} else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return (SKIP_BODY);<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>}<A name=wp66812> </A></P></BLOCKQUOTE>
<H4><A name=wp66813></A>标签额外信息类 </H4>
<P><A name=wp66814></A>有关脚本变量的信息是在IterateTei标签额外信息类中提供的。脚本变量的名字和类以标签属性的形式传入，并用于加入VariableInfo构造函数。</P>
<BLOCKQUOTE>
<P>public class IterateTei extends TagExtraInfo {<BR>&nbsp;&nbsp;public VariableInfo[] getVariableInfo(TagData data) {<BR>&nbsp;&nbsp;String type = data.getAttributeString("type");<BR>&nbsp;&nbsp;if (type == null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;type = "java.lang.Object";</P>
<P>&nbsp; &nbsp;&nbsp;return new VariableInfo[] {<BR>&nbsp;&nbsp;&nbsp;&nbsp;new VariableInfo(data.getAttributeString("id"),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;true,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;VariableInfo.AT_BEGIN) &nbsp;&nbsp;&nbsp;&nbsp;};<BR>&nbsp;&nbsp;}<BR>}<A name=wp66815> </A></P></BLOCKQUOTE>
<H3><A name=wp66817></A>模板标签库 </H3>
<P><A name=wp66818></A>模板提供了一种将应用程序中每一屏幕都会出现的共用元素与每一屏幕都会改变的元素分离开来的方法。将所有公共元素一起放到一个文件中更容易进行维护，并可以加强所有屏幕的外观一致性。它还使每一屏幕的开发更容易了，因为开发者只要注重于该屏幕特定的那部分内容就可以了，模板会负责公共部分。</P>
<P><A name=wp66819></A>模板是JSP页面，在每一屏幕需要改变的地方有占位符。每一个占位符称为模板的<I>参数</I>。例如，一个简单的模板可能包含在生成的屏幕顶部的一个标题参数和JSP页面的正文参数以设定屏幕的定制内容。</P>
<P><A name=wp66821></A>模板使用嵌入的标签——definition、screen和parameter——定义屏幕定义表并使用标签将屏幕定义插入到特定应用程序屏幕。</P>
<H4><A name=wp66823></A>JSP页面 </H4>
<P><A name=wp66825></A>下面展示Duke's Bookstore例子的模板<A href="http://java.sun.com/webservices/docs/1.1/tutorial/examples/web/bookstore3/web/template.txt">template.jsp</A>。这一页面包括一个创建屏幕定义、并用insert标签将定义中的参数插入应用程序屏幕的JSP页面。</P>
<BLOCKQUOTE>
<P>&lt;%@ taglib uri="/tutorial-template.tld" prefix="tt" %&gt;<BR>&lt;%@ page errorPage="errorpage.jsp" %&gt;<BR>&lt;%@ include file="screendefinitions.jsp" %&gt;&lt;html&gt;<BR>&nbsp;&nbsp;&lt;head&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;title&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;tt:insert definition="bookstore"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parameter="title"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/title&gt; &nbsp;&nbsp;&lt;/head&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;tt:insert definition="bookstore" <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parameter="banner"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;tt:insert definition="bookstore"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parameter="body"/&gt;<BR>&nbsp;&nbsp;&lt;/body&gt;<BR>&lt;/html&gt;<A name=wp66826> </A></P></BLOCKQUOTE>
<P><A name=wp66828></A><A href="http://java.sun.com/webservices/docs/1.1/tutorial/examples/web/bookstore3/web/screendefinitions.txt">screendefinitions.jsp</A>根据请求属性selectedScreen创建屏幕定义：</P>
<BLOCKQUOTE>
<P>&lt;tt:definition name="bookstore"<BR>&nbsp;&nbsp;screen="&lt;%= (String)request.<BR>&nbsp;&nbsp;&nbsp;&nbsp;getAttribute(\"selectedScreen\") %&gt;"&gt;<BR>&nbsp;&nbsp;&lt;tt:screen id="/enter"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;tt:parameter name="title"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value="Duke's Bookstore" direct="true"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;tt:parameter name="banner"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value="/banner.jsp" direct="false"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;tt:parameter name="body"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value="/bookstore.jsp" direct="false"/&gt;<BR>&nbsp;&nbsp;&lt;/tt:screen&gt;<BR>&nbsp;&nbsp;&lt;tt:screen id="/catalog"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;tt:parameter name="title"<BR>&nbsp;&nbsp;&nbsp;&nbsp;value="&lt;%=messages.getString("TitleBookCatalog")%&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;direct="true"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;...<BR>&lt;/tt:definition&gt;<A name=wp66829> </A></P></BLOCKQUOTE>
<P><A name=wp66831></A>模板由<A href="http://java.sun.com/webservices/docs/1.1/tutorial/examples/web/bookstore3/src/Dispatcher.java">Dispatcher</A> servlet实例化。Dispatcher首先得到所请求的屏幕并将它储存为请求的属性。这是必要的，因为在向template.jsp转发请求时，请求URL不包含原来的请求(如/bookstore3/catalog)，而是反映转发布页面的路径(/bookstore3/template.jsp)。最后，servlet将请求分发给template.jsp：</P>
<BLOCKQUOTE>
<P>public class Dispatcher extends HttpServlet {<BR>&nbsp;&nbsp;public void doGet(HttpServletRequest request,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpServletResponse response) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;request.setAttribute("selectedScreen",<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.getServletPath()); <BR>&nbsp;&nbsp;&nbsp;&nbsp;RequestDispatcher dispatcher =<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.getRequestDispatcher("/template.jsp");<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (dispatcher != null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dispatcher.forward(request, response);<BR>&nbsp;&nbsp;} &nbsp;&nbsp;public void doPost(HttpServletRequest request,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpServletResponse response) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;request.setAttribute("selectedScreen",<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.getServletPath());<BR>&nbsp;&nbsp;&nbsp;&nbsp;RequestDispatcher dispatcher =<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.getRequestDispatcher("/template.jsp");<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (dispatcher != null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dispatcher.forward(request, response);<BR>&nbsp;&nbsp;}<BR>}<A name=wp66832> </A></P></BLOCKQUOTE>
<H4><A name=wp66833></A>标签handler </H4>
<P><A name=wp66834></A>模板标签库包含四个标签handler——DefinitionTag、ScreenTag、ParameterTag和InsertTag，它们展示了协同操作标签的使用。DefinitionTag、ScreenTag和ParameterTag组成了一组嵌入的标签handler，它们共享公共（public）和私有（private）对象。DefinitionTag创建由InsertTag使用的名为definition的公共对象，</P>
<P><A name=wp66836></A>在doStartTag中，<A href="http://java.sun.com/webservices/docs/1.1/tutorial/examples/web/taglib/src/DefinitionTag.java">DefinitionTag</A>创建一个名为screens的公共对象，它包含屏幕定义的一个哈希表。屏幕定义包含屏幕标识符和一组与该屏幕相关联的参数。</P>
<BLOCKQUOTE>
<P>public int doStartTag() {<BR>&nbsp;&nbsp;HashMap screens = null;<BR>&nbsp;&nbsp;screens = (HashMap) pageContext.getAttribute("screens",<BR>&nbsp;&nbsp;&nbsp;&nbsp;pageContext.APPLICATION_SCOPE);<BR>&nbsp;&nbsp;if (screens == null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;pageContext.setAttribute("screens", new HashMap(),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pageContext.APPLICATION_SCOPE); <BR>&nbsp;&nbsp;return EVAL_BODY_INCLUDE; }<A name=wp66837> </A></P></BLOCKQUOTE>
<P><A name=wp66838></A>ScreenTag和ParameterTag用作为这些标签属性提供的文字填充屏幕定义表。<A href="http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/ctjp.16/16.2.htm#wp66848">表16-6</A>显示了Duke's Bookstore应用程序的屏幕定义哈希表的内容。</P>
<TABLE cellPadding=0 border=1>
<TBODY>
<TR>
<TD colSpan=4>
<P><A name=wp66848></A><I>表</I><I>16-6 </I><I>屏幕定义</I></P></TD></TR>
<TR>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66856></A><B>屏幕</B><B>Id </B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66858></A><B>标题</B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66860></A><B>横幅</B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66862></A><B>正文</B></P></TD></TR>
<TR>
<TD>
<P><A name=wp66864></A>/enter </P></TD>
<TD>
<P><A name=wp66866></A>Duke's Bookstore </P></TD>
<TD>
<P><A name=wp66868></A>/banner.jsp </P></TD>
<TD>
<P><A name=wp66870></A>/bookstore.jsp </P></TD></TR>
<TR>
<TD>
<P><A name=wp66872></A>/catalog </P></TD>
<TD>
<P><A name=wp66874></A>Book Catalog </P></TD>
<TD>
<P><A name=wp66876></A>/banner.jsp </P></TD>
<TD>
<P><A name=wp66878></A>/catalog.jsp </P></TD></TR>
<TR>
<TD>
<P><A name=wp66880></A>/bookdetails </P></TD>
<TD>
<P><A name=wp66882></A>Book Description </P></TD>
<TD>
<P><A name=wp66884></A>/banner.jsp </P></TD>
<TD>
<P><A name=wp66886></A>/bookdetails.jsp </P></TD></TR>
<TR>
<TD>
<P><A name=wp66888></A>/showcart </P></TD>
<TD>
<P><A name=wp66890></A>Shopping Cart </P></TD>
<TD>
<P><A name=wp66892></A>/banner.jsp </P></TD>
<TD>
<P><A name=wp66894></A>/showcart.jsp </P></TD></TR>
<TR>
<TD>
<P><A name=wp66896></A>/cashier </P></TD>
<TD>
<P><A name=wp66898></A>Cashier </P></TD>
<TD>
<P><A name=wp66900></A>/banner.jsp </P></TD>
<TD>
<P><A name=wp66902></A>/cashier.jsp </P></TD></TR>
<TR>
<TD>
<P><A name=wp66904></A>/receipt </P></TD>
<TD>
<P><A name=wp66906></A>Receipt </P></TD>
<TD>
<P><A name=wp66908></A>/banner.jsp </P></TD>
<TD>
<P><A name=wp66910></A>/receipt.jsp </P></TD></TR></TBODY></TABLE>
<P>. </P>
<P><A name=wp66912></A>在doEndTag中，DefinitionTag创建<A href="http://java.sun.com/webservices/docs/1.1/tutorial/examples/web/taglib/src/Definition.java">Definition</A>类的一个公共对象，根据在请求中传递的URL从screens对象中选择一个屏幕定义，并用它初始化Definition对象。</P>
<BLOCKQUOTE>
<P>public int doEndTag()throws JspTagException {<BR>&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;Definition definition = new Definition();<BR>&nbsp;&nbsp;&nbsp;&nbsp;HashMap screens = null;<BR>&nbsp;&nbsp;&nbsp;&nbsp;ArrayList params = null;<BR>&nbsp;&nbsp;&nbsp;&nbsp;TagSupport screen = null;<BR>&nbsp;&nbsp;&nbsp;&nbsp;screens = (HashMap)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pageContext.getAttribute("screens",<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pageContext.APPLICATION_SCOPE);<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (screens != null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;params = (ArrayList) screens.get(screenId);<BR>&nbsp;&nbsp;&nbsp;&nbsp;else <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (params == null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<BR>&nbsp;&nbsp;&nbsp;&nbsp;Iterator ir = null;<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (params != null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ir = params.iterator();<BR>&nbsp;&nbsp;&nbsp;&nbsp;while ((ir != null) &amp;&amp; ir.hasNext())<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;definition.setParam((Parameter) ir.next());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// put the definition in the page context<BR>&nbsp;&nbsp;&nbsp;&nbsp;pageContext.setAttribute(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;definitionName, definition);<BR>&nbsp;&nbsp;} catch (Exception ex) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;ex.printStackTrace();<BR>&nbsp;&nbsp;} &nbsp;&nbsp;return EVAL_PAGE;<BR>}<A name=wp66913> </A></P></BLOCKQUOTE>
<P><A name=wp66914></A>如果在请求中传递的URL是/enter，那么Definition包含<A href="http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/ctjp.16/16.2.htm#wp66848">表16-6</A>中第一行的项目：</P>
<TABLE cellPadding=0 border=1>
<TBODY>
<TR>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66920></A><B>标题</B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66922></A><B>横幅</B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66924></A><B>正文</B></P></TD></TR>
<TR>
<TD>
<P><A name=wp66926></A>Duke's Bookstore </P></TD>
<TD>
<P><A name=wp66928></A>/banner.jsp </P></TD>
<TD>
<P><A name=wp66930></A>/bookstore.jsp </P></TD></TR></TBODY></TABLE>
<P><A name=wp66934></A>URL /enter的定义如<A href="http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/ctjp.16/16.2.htm#wp66941">表16-7</A>所示。这个定义指定Title参数的值Duke's Bookstore应该直接插队入到输出流中，但是Banner和Body的值应该动态地加入。</P>
<TABLE cellPadding=0 border=1>
<TBODY>
<TR>
<TD colSpan=3>
<P><A name=wp66941></A><I>表</I><I>16-7 URL /enter&nbsp;</I><I>的屏幕定义</I></P></TD></TR>
<TR>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66947></A><B>参数名</B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66949></A><B>参数值</B></P></TD>
<TD>
<P style="TEXT-ALIGN: center" align=center><A name=wp66951></A><B>isDirect </B></P></TD></TR>
<TR>
<TD>
<P><A name=wp66953></A>title </P></TD>
<TD>
<P><A name=wp66955></A>Duke's Bookstore </P></TD>
<TD>
<P><A name=wp66957></A>true </P></TD></TR>
<TR>
<TD>
<P><A name=wp66959></A>banner </P></TD>
<TD>
<P><A name=wp66961></A>/banner.jsp </P></TD>
<TD>
<P><A name=wp66963></A>false </P></TD></TR>
<TR>
<TD>
<P><A name=wp66965></A>body </P></TD>
<TD>
<P><A name=wp66967></A>/bookstore.jsp </P></TD>
<TD>
<P><A name=wp66969></A>false </P></TD></TR></TBODY></TABLE>
<P><A name=wp66971></A><A href="http://java.sun.com/webservices/docs/1.1/tutorial/examples/web/taglib/src/InsertTag.java">InsertTag</A>使用Definition将屏幕定义的参数插入响应中。在doStartTag方法中，它从页面上下文中获取定义对象。</P>
<BLOCKQUOTE>
<P>public int doStartTag() {<BR>&nbsp;&nbsp;// get the definition from the page context<BR>&nbsp;&nbsp;definition = (Definition) pageContext.<BR>&nbsp;&nbsp;&nbsp;&nbsp;getAttribute(definitionName);<BR>&nbsp;&nbsp;// get the parameter<BR>&nbsp;&nbsp;if (parameterName != null &amp;&amp; definition != null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;parameter = (Parameter)definition.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getParam(parameterName);<BR>&nbsp;&nbsp;if (parameter != null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;directInclude = parameter.isDirect();<BR>&nbsp;&nbsp;return SKIP_BODY;<BR>}<A name=wp66972> </A></P></BLOCKQUOTE>
<P><A name=wp66973></A>doEndTag方法插入参数值。如果参数是直接的，那么就直接将它插入响应中，否则，请求就被发送给参数，而其响应则被动态地包含进整个响应中。</P>
<BLOCKQUOTE>
<P>public int doEndTag()throws JspTagException {<BR>&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (directInclude &amp;&amp; parameter != null)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pageContext.getOut().print(parameter.getValue());<BR>&nbsp;&nbsp;&nbsp;&nbsp;else {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ((parameter != null) &amp;&amp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(parameter.getValue() != null))<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pageContext.include(parameter.getValue());<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;} catch (Exception ex) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;throw new JspTagException(ex.getMessage());<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;return EVAL_PAGE;<BR>}<A name=wp66974> </A></P></BLOCKQUOTE>
<H3><A name=wp66976></A>如何调用标签handler？ </H3>
<P><A name=wp66977></A>Tag接口定义了标签handler与JSP页面的servlet之间的基本协议。它定义了生命周期以及在遇到开始和结束标签时要调用的方法。</P>
<P><A name=wp66979></A>JSP页面的servlet在调用doStartTag之前调用setPageContext、setParent和属性设置方法。JSP页面的servlet还保证在结束页面之前调用标签handler的release。</P>
<P><A name=wp66980></A>下面是典型的标签handler方法调用顺序：</P>
<BLOCKQUOTE>
<P>A Tag t = new ATag();<BR>t.setPageContext(...);<BR>t.setParent(...);<BR>t.setAttribute1(value1);<BR>t.setAttribute2(value2);<BR>t.doStartTag();<BR>t.doEndTag();<BR>t.release();<A name=wp66981> </A></P></BLOCKQUOTE>
<P><A name=wp66982></A>BodyTag接口通过定义让标签handler访问其正文的其他方法扩展Tag。这个接口提供三个新方法：</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66983></A>·&nbsp; setBodyContent—创建正文内容并添加给tag handler</P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66984></A>·&nbsp; doInitBody—在评估标签正文之前调用 </P>
<P style="TEXT-ALIGN: left" align=left><A name=wp66985></A>·&nbsp; doAfterBody—在评估标签正文之后调用</P>
<P><A name=wp66986></A>典型的调用顺序为： </P>
<BLOCKQUOTE>
<P>t.doStartTag();<BR>out = pageContext.pushBody();<BR>t.setBodyContent(out);<BR>// perform any initialization needed after body content is set t.doInitBody();<BR>t.doAfterBody();<BR>// while doAfterBody returns EVAL_BODY_BUFFERED we<BR>// iterate body evaluation<BR>...<BR>t.doAfterBody();<BR>t.doEndTag();<BR>t.pageContext.popBody();<BR>t.release();<A name=wp66987> </A></P></BLOCKQUOTE><img src ="http://www.blogjava.net/kapok/aggbug/3220.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-13 16:09 <a href="http://www.blogjava.net/kapok/archive/2005/04/13/3220.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用定制标记进行流控制和 URL 管理</title><link>http://www.blogjava.net/kapok/archive/2005/04/13/3219.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Wed, 13 Apr 2005 07:58:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/13/3219.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3219.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/13/3219.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3219.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3219.html</trackback:ping><description><![CDATA[<SPAN class=atitle2><A href="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/index.html">http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/index.html</A><BR><BR>使用定制标记进行流控制和 URL 管理</SPAN><BR>
<P><A href="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/index.html#author1"><NAME>Mark A. Kolb</NAME></A><BR>软件工程师<BR>2003 年 6 月 21 日</P>
<BLOCKQUOTE><ABSTRACT-EXTENDED>顾名思义，JSP 标准标记库（JSP Standard Tag Library，JSTL） <CODE>core</CODE> 库为一些基本功能（如，管理限定了作用域的变量和与 URL 交互等）和基本操作（如，迭代和条件化）提供了定制标记。这些标记不仅可以由页面设计人员直接利用，而且还为与其它 JSTL 库相结合从而提供更复杂的表示逻辑奠定了基础。Mark Kolb 在本文中继续对 JSTL 和 <CODE>core</CODE> 库进行探讨，研究用标记来协助流控制和 URL 管理。 </ABSTRACT-EXTENDED></BLOCKQUOTE>
<P>通过阅读本系列的 <A href="http://www-128.ibm.com/developerworks/cn/java/j-jstl0211/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">第一篇文章</A>，您对 JSTL 有了初步的了解。我们描述了使用其 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">表达式语言</I>（EL）来访问数据和操作数据。正如您所了解的那样，EL 用来为 JSTL 定制标记的属性赋予动态值，因此，它所起的作用与 JSP 表达式一样，为内置操作及其它定制标记库指定请求时的属性值。 </P>
<P>为了演示 EL 的用法，我们介绍了 <CODE>core</CODE> 库中的三个标记： <CODE>&lt;c:set&gt;</CODE> 、 <CODE>&lt;c:remove&gt;</CODE> 和 <CODE>&lt;c:out&gt;</CODE> 。 <CODE>&lt;c:set&gt;</CODE> 和 <CODE>&lt;c:remove&gt;</CODE> 用于管理限定了作用域的变量；而 <CODE>&lt;c:out&gt;</CODE> 用于显示数据，尤其是显示用 EL 计算出的值。在此基础上，接下来本文把注意力集中在 <CODE>core</CODE> 库的其余标记上，这些标记可以大致归为两大类别：流控制和 URL 管理。 </P>
<P><A name=1><SPAN class=atitle2>示例应用程序</SPAN></A><BR>为了演示 JSTL 标记，我们将使用来自一个工作应用程序的示例，本系列中余下的文章都将使用此应用程序。由于基于 Java 的 Weblog 日渐流行及为人们所熟悉，因此我们将出于此目的使用一个简单的基于 Java 的 Weblog；参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/index.html#resources" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">参考资料</A>以下载该应用程序的 JSP 页面和源代码。Weblog（也称为 blog）是一种基于 Web 的简短注释的日志，这些注释是有关 Weblog 的作者所感兴趣的主题，通常带有与 Web 上其它地方的相关文章及讨论的链接。图 1 中显示了该应用程序正在运行时的抓屏。 </P>
<P><A name=N400089><B>图 1. Weblog 应用程序 </B></A><BR><IMG height=644 alt="Weblog 示例应用程序的抓屏" src="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/weblog.jpg" width=600 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"> </P>
<P>虽然完整的实现需要二十四个 Java 类，但在表示层中却只涉及 Weblog 应用程序中的两个类， <CODE>Entry</CODE> 和 <CODE>UserBean</CODE> 。这样，对于理解 JSTL 示例而言，只有这两个类比较重要。图 2 显示了 <CODE>Entry</CODE> 和 <CODE>UserBean</CODE> 的类图。 </P>
<P><A name=N4000AB><B>图 2. Weblog 应用程序的类图 </B></A><BR><IMG height=153 alt="Weblog 示例应用程序的类图" src="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/Classes.jpg" width=428 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"> </P>
<P><CODE>Entry</CODE> 类表示 Weblog 中一个标有日期的项。其 <CODE>id</CODE> 属性用于在数据库中存储及检索该项，而 <CODE>title</CODE> 和 <CODE>text</CODE> 属性则表示该项的实际内容。 <CODE>created</CODE> 和 <CODE>lastModified</CODE> 属性引用了 Java 语言中 <CODE>Date</CODE> 类的两个实例，分别用来表示最初创建该项的时间和最后编辑该项的时间。 <CODE>author</CODE> 属性引用了标识该项的创建者的 <CODE>UserBean</CODE> 实例。 </P>
<P><CODE>UserBean</CODE> 类存储了有关应用程序的已认证用户的信息，如用户名、全名和电子邮件地址。该类还包含一个用于与相关数据库进行交互的 <CODE>id</CODE> 属性。其最后一个属性 <CODE>roles</CODE> 引用一列 <CODE>String</CODE> 值，这列值标识与相应用户相关的、特定于应用程序的角色。对于 Weblog 应用程序，相关的角色是“User”（所有应用程序用户常用的缺省角色）和“Author”（该角色指定可以创建和编辑 Weblog 项的用户）。 </P>
<P><A name=2><SPAN class=atitle2>流控制</SPAN></A><BR>由于可以用 EL 替代 JSP 表达式来指定动态属性值，因此页面创作人员无需使用脚本编制元素。因为脚本编制元素可能是引起 JSP 页面中维护问题的主要原因，所以 JSTL 的主要优点就在于提供了这样简单（且标准）的替代方法。</P>
<P>EL 从 JSP 容器检索数据，遍历对象层次结构，然后对结果执行简单的操作。不过，除了访问和操作数据之外，JSP 脚本编制元素的另一个常见用法是流控制。尤其是，页面创作人员常借助 scriptlet 来实现迭代或条件内容。然而，因为这样的操作超出了 EL 的能力，所以 <CODE>core</CODE> 库提供了几个定制操作来管理流控制，其形式有 <A href="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/index.html#2.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">迭代</A>、 <A href="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/index.html#2.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">条件化</A>和 <A href="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/index.html#2.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">异常处理</A>。 </P>
<P><A name=2.1><SPAN class=atitle3>迭代</SPAN></A><BR>在 Web 应用程序环境中，迭代主要用于访存和显示数据集，通常是以列表或表中的一系列行的形式显示。实现迭代内容的主要 JSTL 操作是 <CODE>&lt;c:forEach&gt;</CODE> 定制标记。该标记支持两种不同样式的迭代：整数范围上的迭代（类似 Java 语言的 <CODE>for</CODE> 语句）和集合上的迭代（类似 Java 语言的 <CODE>Iterator</CODE> 和 <CODE>Enumeration</CODE> 类）。 </P>
<P>进行整数范围迭代用到了清单 1 中所示的 <CODE>&lt;c:forEach&gt;</CODE> 标记的语法。 <CODE>begin</CODE> 和 <CODE>end</CODE> 属性要么是静态整数值，要么是可以得出整数值的表达式。它们分别指定迭代索引的初始值以及迭代索引的终止值。当使用 <CODE>&lt;c:forEach&gt;</CODE> 在整数范围内进行迭代时，这两个属性是必需的，而其它所有属性都是可选的。 </P><A name=N40013D><B>清单 1. 通过 &lt;c:forEach&gt; 操作进行数字迭代的语法</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:forEach var="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">name</I>" varStatus="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">name</I>"
    begin="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" end="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" step="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"&gt;
  
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">body content</I>
&lt;/c:forEach&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>当出现 <CODE>step</CODE> 时，它也必须是整数值。它指定每次迭代后索引的增量。这样，迭代索引从 <CODE>begin</CODE> 属性的值开始，以 <CODE>step</CODE> 属性的值为增量进行递增，在迭代索引超过 <CODE>end</CODE> 属性的值时停止迭代。注：如果省略了 <CODE>step</CODE> 属性，那么步长缺省为 1。 </P>
<P>如果指定了 <CODE>var</CODE> 属性，那么将会创建一个带有指定名称的并限定了作用域的变量，并将每次迭代的当前索引值赋给该变量。这一限定了作用域的变量具有嵌套式可视性 ― 只可以在 <CODE>&lt;c:forEach&gt;</CODE> 标记体内对其进行访问。（我们很快将讨论可选属性 <CODE>varStatus</CODE> 的用法。）清单 2 显示了对一组固定整数值进行迭代的 <CODE>&lt;c:forEach&gt;</CODE> 操作示例。 </P><A name=N400186><B>清单 2. 使用 &lt;c:forEach&gt; 标记来生成表列数据，这些数据对应于某一范围内的数值</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;table&gt;
&lt;tr&gt;&lt;th&gt;Value&lt;/th&gt;
    &lt;th&gt;Square&lt;/th&gt;&lt;/tr&gt;
&lt;c:forEach var="x" begin="0" end="10" step="2"&gt;
  &lt;tr&gt;&lt;td&gt;&lt;c:out value="${x}"/&gt;&lt;/td&gt;
      &lt;td&gt;&lt;c:out value="${x * x}"/&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/c:forEach&gt;
&lt;/table&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>如图 3 中所示，上面的示例代码生成了一张表，显示前五个偶数及其平方。这是通过将 <CODE>begin</CODE> 和 <CODE>step</CODE> 属性值指定为 2，而将 <CODE>end</CODE> 属性值指定为 10 实现的。此外，用 <CODE>var</CODE> 属性创建用于存储索引值的限定了作用域的变量， <CODE>&lt;c:forEach&gt;</CODE> 标记体内引用了该变量。尤其是，使用了一对 <CODE>&lt;c:out&gt;</CODE> 操作来显示索引及其平方，其中索引的平方是使用一个简单的表达式计算得来的。 </P>
<P><A name=N4001BB><B>图 3. 清单 2 的输出 </B></A><BR><IMG height=267 alt="清单 2 的输出" src="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/forEach.jpg" width=467 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"> </P>
<P>在对集合的成员进行迭代时，用到了 <CODE>&lt;c:forEach&gt;</CODE> 标记的另一个属性： <CODE>items</CODE> 属性，清单 3 中显示了该属性。当使用这种形式的 <CODE>&lt;c:forEach&gt;</CODE> 标记时， <CODE>items</CODE> 属性是唯一必需的属性。 <CODE>items</CODE> 属性的值应该是一个集合，对该集合的成员进行迭代，通常使用 EL 表达式指定值。如果变量名称是通过 <CODE>&lt;c:forEach&gt;</CODE> 标记的 <CODE>item</CODE> 属性指定的，那么对于每次迭代该已命名变量都将被绑定到集合后续元素上。 </P><A name=N4001E8><B>清单 3. 通过 &lt;c:forEach&gt; 操作对集合进行迭代的语法</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:forEach var="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">name</I>" items="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" varStatus="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">name</I>"
    begin="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" end="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" step="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"&gt;
  
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">body content</I>
&lt;/c:forEach&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P><CODE>&lt;c:forEach&gt;</CODE> 标记支持 Java 平台所提供的所有标准集合类型。此外，您可以使用该操作来迭代数组（包括基本类型数组）中的元素。表 1 列出了 <CODE>items</CODE> 属性所支持的所有值。正如表的最后一行所指出的那样，JSTL 定义了它自己的接口 <CODE>javax.servlet.jsp.jstl.sql.Result</CODE> ，用来迭代 SQL 查询的结果。（我们将在本系列后面的文章中详细讨论这一功能。） </P>
<P><A name=N10208><SPAN class=atitle3>表 1. &lt;c:forEach&gt; 标记的 items 属性所支持的集合 </SPAN></A><BR>
<TABLE cellSpacing=0 cellPadding=3 width="100%" border=1>
<TBODY>
<TR vAlign=top>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"><CODE>items</CODE> 的值 </B></TD>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">所产生的 <CODE>item</CODE> 值 </B></TD></TR>
<TR vAlign=top>
<TD><CODE>java.util.Collection</CODE> </TD>
<TD>调用 <CODE>iterator()</CODE> 所获得的元素 </TD></TR>
<TR vAlign=top>
<TD><CODE>java.util.Map</CODE> </TD>
<TD><CODE>java.util.Map.Entry</CODE> 的实例 </TD></TR>
<TR vAlign=top>
<TD><CODE>java.util.Iterator</CODE> </TD>
<TD>迭代器元素</TD></TR>
<TR vAlign=top>
<TD><CODE>java.util.Enumeration</CODE> </TD>
<TD>枚举元素</TD></TR>
<TR vAlign=top>
<TD><CODE>Object</CODE> 实例数组 </TD>
<TD>数组元素</TD></TR>
<TR vAlign=top>
<TD>基本类型值数组</TD>
<TD>经过包装的数组元素</TD></TR>
<TR vAlign=top>
<TD>用逗号定界的 <CODE>String</CODE> </TD>
<TD>子字符串</TD></TR>
<TR vAlign=top>
<TD><CODE>javax.servlet.jsp.jstl.sql.Result</CODE> </TD>
<TD>SQL 查询所获得的行</TD></TR></TBODY></TABLE></P>
<P>可以使用 <CODE>begin</CODE> 、 <CODE>end</CODE> 和 <CODE>step</CODE> 属性来限定在迭代中包含集合中哪些元素。和通过 <CODE>&lt;c:forEach&gt;</CODE> 进行数字迭代的情形一样，在迭代集合中的元素时同样要维护一个迭代索引。 <CODE>&lt;c:forEach&gt;</CODE> 标记实际上只处理那些与索引值相对应的元素，这些索引值与指定的 <CODE>begin</CODE> 、 <CODE>end</CODE> 和 <CODE>step</CODE> 值相匹配。 </P>
<P>清单 4 显示了用来迭代集合的 <CODE>&lt;c:forEach&gt;</CODE> 标记。对于该 JSP 代码段， <CODE>entryList</CODE> 这一限定了作用域的变量被设置成了 <CODE>Entry</CODE> 对象列表（确切的说， <CODE>ArrayList</CODE> ）。 <CODE>&lt;c:forEach&gt;</CODE> 标记依次处理列表中的每个元素，将其赋给一个限定了作用域的变量 <CODE>blogEntry</CODE> ，然后生成两个表行 ― 一个用于 Weblog 项的 <CODE>title</CODE> ，另一个则用于该项 <CODE>text</CODE> 。这些特性是通过一对带有相应 EL 表达式的 <CODE>&lt;c:out&gt;</CODE> 操作从 <CODE>blogEntry</CODE> 变量检索得到的。注：由于 Weblog 项的标题和文本都可能包含 HTML 标记，因此这两个 <CODE>&lt;c:out&gt;</CODE> 标记的 <CODE>escapeXml</CODE> 属性都被设置成了 false。图 4 显示了结果。 </P><A name=N4002FA><B>清单 4. 使用 &lt;c:forEach&gt; 标记显示表示给定日期的 Weblog 项</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;table&gt;
  &lt;c:forEach items="${entryList}" var="blogEntry"&gt;
    &lt;tr&gt;&lt;td align="left" class="blogTitle"&gt;
      &lt;c:out value="${blogEntry.title}" escapeXml="false"/&gt;
    &lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td align="left" class="blogText"&gt;
      &lt;c:out value="${blogEntry.text}" escapeXml="false"/&gt;
    &lt;/td&gt;&lt;/tr&gt;
  &lt;/c:forEach&gt;
&lt;/table&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=N400314><B>图 4. 清单 4 的输出 </B></A><BR><IMG height=312 alt="清单 4 的输出" src="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/blog1.jpg" width=590 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"> </P>
<P>不论是对整数还是对集合进行迭代， <CODE>&lt;c:forEach&gt;</CODE> 剩余的属性 <CODE>varStatus</CODE> 所起的作用相同。和 <CODE>var</CODE> 属性一样， <CODE>varStatus</CODE> 用于创建限定了作用域的变量。不过，由 <CODE>varStatus</CODE> 属性命名的变量并不存储当前索引值或当前元素，而是赋予 <CODE>javax.servlet.jsp.jstl.core.LoopTagStatus</CODE> 类的实例。该类定义了一组特性，它们描述了迭代的当前状态，表 2 中列出了这些特性。 </P>
<P><A name=table1><SPAN class=atitle3>表 2. LoopTagStatus 对象的特性</SPAN></A><BR>
<TABLE cellSpacing=0 cellPadding=3 width="100%" border=1>
<TBODY>
<TR vAlign=top>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">特性</B> </TD>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">Getter</B> </TD>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">描述</B> </TD></TR>
<TR vAlign=top>
<TD>current</TD>
<TD><CODE>getCurrent()</CODE> </TD>
<TD>当前这次迭代的（集合中的）项</TD></TR>
<TR vAlign=top>
<TD>index</TD>
<TD><CODE>getIndex()</CODE> </TD>
<TD>当前这次迭代从 0 开始的迭代索引</TD></TR>
<TR vAlign=top>
<TD>count</TD>
<TD><CODE>getCount()</CODE> </TD>
<TD>当前这次迭代从 1 开始的迭代计数</TD></TR>
<TR vAlign=top>
<TD>first</TD>
<TD><CODE>isFirst()</CODE> </TD>
<TD>用来表明当前这轮迭代是否为第一次迭代的标志</TD></TR>
<TR vAlign=top>
<TD>last</TD>
<TD><CODE>isLast()</CODE> </TD>
<TD>用来表明当前这轮迭代是否为最后一次迭代的标志</TD></TR>
<TR vAlign=top>
<TD>begin</TD>
<TD><CODE>getBegin()</CODE> </TD>
<TD><CODE>begin</CODE> 属性值 </TD></TR>
<TR vAlign=top>
<TD>end</TD>
<TD><CODE>getEnd()</CODE> </TD>
<TD><CODE>end</CODE> 属性值 </TD></TR>
<TR vAlign=top>
<TD>step</TD>
<TD><CODE>getStep()</CODE> </TD>
<TD><CODE>step</CODE> 属性值 </TD></TR></TBODY></TABLE></P>
<P>清单 5 显示了关于如何使用 <CODE>varStatus</CODE> 属性的一个示例。这个示例修改了清单 4 中的代码，将 Weblog 项的编号添加到显示 Weblog 标题（title）的表行。它是通过为 <CODE>varStatus</CODE> 属性指定一个值，然后访问所生成的限定了作用域的变量的 <CODE>count</CODE> 特性来实现这一点的。结果显示在图 5 中。 </P><A name=N4003ED><B>清单 5. 使用 varStatus 属性来显示 Weblog 项的数目</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;table&gt;
  &lt;c:forEach items=
    "${entryList}" var="blogEntry" varStatus="status"&gt;
    &lt;tr&gt;&lt;td align="left" class="blogTitle"&gt;
      &lt;c:out value="${status.count}"/&gt;.
      &lt;c:out value="${blogEntry.title}" escapeXml="false"/&gt;
    &lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td align="left" class="blogText"&gt;
      &lt;c:out value="${blogEntry.text}" escapeXml="false"/&gt;
    &lt;/td&gt;&lt;/tr&gt;
  &lt;/c:forEach&gt;
&lt;/table&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=N400407><B>图 5. 清单 5 的输出 </B></A><BR><IMG height=312 alt="清单 5 的输出" src="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/blog2.jpg" width=590 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"> </P>
<P>除 <CODE>&lt;c:forEach&gt;</CODE> 以外， <CODE>core</CODE> 库还提供了另一个迭代标记： <CODE>&lt;c:forTokens&gt;</CODE> 。JSTL 的这个定制操作与 Java 语言的 <CODE>StringTokenizer</CODE> 类的作用相似。清单 6 中显示的 <CODE>&lt;c:forTokens&gt;</CODE> 标记除了比 <CODE>&lt;c:forEach&gt;</CODE> 的面向集合版本多一个属性之外，其它属性都相同。对于 <CODE>&lt;c:forTokens&gt;</CODE> 而言，通过 <CODE>items</CODE> 属性指定要标记化的字符串，而通过 <CODE>delims</CODE> 属性提供用于生成标记的一组定界符。和 <CODE>&lt;c:forEach&gt;</CODE> 的情形一样，可以使用 <CODE>begin</CODE> 、 <CODE>end</CODE> 和 <CODE>step</CODE> 属性将要处理的标记限定为那些与相应索引值相匹配的标记。 </P><A name=N40044C><B>清单 6. 使用 &lt;c:forTokens&gt; 操作来迭代字符串标记的语法</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:forTokens var="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">name</I>" items="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"
    delims="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" varStatus="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">name</I>"
    begin="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" end="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" step="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"&gt;
  
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">body content</I>
&lt;/c:forTokens&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=2.2><SPAN class=atitle3>条件化</SPAN></A><BR>对于包含动态内容的 Web 页面，您可能希望不同类别的用户看到不同形式的内容。例如，在我们的 Weblog 中，访问者应该能够阅读各项，也许还应该能够提交反馈，但只有经过授权的用户才能公布新项，或编辑已有内容。</P>
<P>在同一个 JSP 页面内实现这样的功能，然后使用条件逻辑来根据每条请求控制所显示的内容，这样做常常能够改善实用性和软件维护。 <CODE>core</CODE> 库提供了两个不同的条件化标记 ― <CODE>&lt;c:if&gt;</CODE> 和 <CODE>&lt;c:choose&gt;</CODE> ― 来实现这些功能。 </P>
<P><CODE>&lt;c:if&gt;</CODE> 是这两个操作中较简单的一个，它简单地对单个测试表达式进行求值，接下来，仅当对表达式求出的值为 <CODE>true</CODE> 时，它才处理标记的主体内容。如果求出的值不为 <CODE>true</CODE> ，就忽略该标记的主体内容。如清单 7 所示， <CODE>&lt;c:if&gt;</CODE> 可以通过其 <CODE>var</CODE> 和 <CODE>scope</CODE> 属性（它们所起的作用和在 <CODE>&lt;c:set&gt;</CODE> 中所起的作用一样）选择将测试结果赋给限定了作用域的变量。当测试代价非常高昂时，这种能力尤为有用：可以将结果高速缓存在限定了作用域的变量中，然后在随后对 <CODE>&lt;c:if&gt;</CODE> 或其它 JSTL 标记的调用中检索该结果。 </P><A name=N4004A9><B>清单 7. &lt;c:if&gt; 条件操作的语法</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:if test="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" var="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">name</I>" scope="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">scope</I>"&gt;
  
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">body content</I>
&lt;/c:if&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>清单 8 显示了与 <CODE>&lt;c:forEach&gt;</CODE> 标记的 <CODE>LoopTagStatus</CODE> 对象的 <CODE>first</CODE> 特性一起使用的 <CODE>&lt;c:if&gt;</CODE> 。如图 6 中所示，在这种情况下，只在 Weblog 项的第一项上显示这组项的创建日期，而不在任何其它项前面重复该日期。 </P><A name=N4004D5><B>清单 8. 使用 &lt;c:if&gt; 来为 Weblog 项显示日期</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;table&gt;
  &lt;c:forEach items=
    "${entryList}" var="blogEntry" varStatus="status"&gt;
    &lt;c:if test="${status.first}"&gt;
      &lt;tr&gt;&lt;td align="left" class="blogDate"&gt;
            &lt;c:out value="${blogEntry.created}"/&gt;
      &lt;/td&gt;&lt;/tr&gt;
    &lt;/c:if&gt;
    &lt;tr&gt;&lt;td align="left" class="blogTitle"&gt;
      &lt;c:out value="${blogEntry.title}" escapeXml="false"/&gt;
    &lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td align="left" class="blogText"&gt;
      &lt;c:out value="${blogEntry.text}" escapeXml="false"/&gt;
    &lt;/td&gt;&lt;/tr&gt;
  &lt;/c:forEach&gt;
&lt;/table&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=N4004F5><B>图 6. 清单 8 的输出 </B></A><BR><IMG height=341 alt="清单 8 的输出" src="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/blog3.jpg" width=590 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"> </P>
<P>如清单 8 所示， <CODE>&lt;c:if&gt;</CODE> 标记为条件化内容的一些简单情形提供了一种非常简洁的表示法。对于需要进行互斥测试来确定应该显示什么内容的情况下，JSTL <CODE>core</CODE> 库还提供了 <CODE>&lt;c:choose&gt;</CODE> 操作。清单 9 中显示了 <CODE>&lt;c:choose&gt;</CODE> 的语法。 </P><A name=N400516><B>清单 9. &lt;c:choose&gt; 操作的语法</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:choose&gt;
  &lt;c:when test="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"&gt;
    
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">body content</I>
  &lt;/c:when&gt;
  ...
  &lt;c:otherwise&gt;
    
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">body content</I>
  &lt;/c:otherwise&gt;
&lt;/c:choose&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>每个要测试的条件都由相应的 <CODE>&lt;c:when&gt;</CODE> 标记来表示，至少要有一个 <CODE>&lt;c:when&gt;</CODE> 标记。只会处理第一个其 <CODE>test</CODE> 值为 <CODE>true</CODE> 的 <CODE>&lt;c:when&gt;</CODE> 标记体内的内容。如果没有一个 <CODE>&lt;c:when&gt;</CODE> 测试返回 <CODE>true</CODE> ，那么会处理 <CODE>&lt;c:otherwise&gt;</CODE> 标记的主体内容。注：尽管如此， <CODE>&lt;c:otherwise&gt;</CODE> 标记却是可选的； <CODE>&lt;c:choose&gt;</CODE> 标记至多可有一个嵌套的 <CODE>&lt;c:otherwise&gt;</CODE> 标记。如果所有 <CODE>&lt;c:when&gt;</CODE> 测试都为 <CODE>false</CODE> ，而且又没有给出 <CODE>&lt;c:otherwise&gt;</CODE> 操作，那么不会处理任何 <CODE>&lt;c:choose&gt;</CODE> 标记的主体内容。 </P>
<P>清单 10 显示了运用 <CODE>&lt;c:choose&gt;</CODE> 标记的示例。在这里，检索请求对象而获得协议信息（通过 EL 的 <CODE>pageContext</CODE> 隐式对象），并用简单的字符串比较对协议信息进行测试。根据这些测试的结果，会显示相应的文本消息。 </P><A name=N400572><B>清单 10. 使用 &lt;c:choose&gt; 进行内容条件化</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:choose&gt;
  &lt;c:when test="${pageContext.request.scheme eq 'http'}"&gt;
    This is an insecure Web session.
  &lt;/c:when&gt;
  &lt;c:when test="${pageContext.request.scheme eq 'https'}"&gt;
    This is a secure Web session.
  &lt;/c:when&gt;
  &lt;c:otherwise&gt;
    You are using an unrecognized Web protocol. How did this happen?!
  &lt;/c:otherwise&gt;
&lt;/c:choose&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=2.3><SPAN class=atitle3>异常处理</SPAN></A><BR>最后一个流控制标记是 <CODE>&lt;c:catch&gt;</CODE> ，它允许在 JSP 页面内进行初级的异常处理。更确切地说，在该标记的主体内容中产生的任何异常都会被捕获并被忽略（即，不会调用标准的 JSP 错误处理机制）。然而，如果产生了一个异常并且已经指定了 <CODE>&lt;c:catch&gt;</CODE> 标记的可选属性 <CODE>var</CODE> ，那么会将异常赋给（具有页面作用域的）指定的变量，这使得能够在页面自身内部进行定制错误处理。清单 11 显示了 <CODE>&lt;c:catch&gt;</CODE> 的语法（稍后在 <A href="http://www-128.ibm.com/developerworks/cn/java/j-jstl0318/index.html#listing18" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">清单 18</A>中给出一个示例）。 </P><A name=N40059D><B>清单 11. &lt;c:catch&gt; 操作的语法</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:catch var="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">name</I>"&gt;
  
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">body content</I>
&lt;/c:catch&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=3><SPAN class=atitle2>URL 操作</SPAN></A><BR>JSTL <CODE>core</CODE> 库中的其余标记主要是关于 URL。这些标记中的第一个被适当地命名为 <CODE>&lt;c:url&gt;</CODE> 标记，用于生成 URL。尤其是， <CODE>&lt;c:url&gt;</CODE> 提供了三个功能元素，它们在为 J2EE Web 应用程序构造 URL 时特别有用： </P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">
<LI>在前面附加当前 servlet 上下文的名称 
<LI>为会话管理重写 URL 
<LI>请求参数名称和值的 URL 编码 </LI></UL>
<P>清单 12 显示了 <CODE>&lt;c:url&gt;</CODE> 标记的语法。 <CODE>value</CODE> 属性用来指定基本 URL，然后在必要时标记对其进行转换。如果这个基本 URL 以一个斜杠开始，那么会在它前面加上 servlet 的上下文名称。可以使用 <CODE>context</CODE> 属性提供显式的上下文名称。如果省略该属性，那么就使用当前 servlet 上下文的名称。这一点特别有用，因为 servlet 上下文名称是在部署期间而不是开发期间决定的。（如果这个基本 URL 不是以斜杠开始的，那么就认为它是一个相对 URL，这时就不必添加上下文名称。） </P><A name=N4005E1><B>清单 12. &lt;c:url&gt; 操作的语法</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:url value="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" context="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"
    var="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">name</I>" scope="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">scope</I>"&gt;
  &lt;c:param name="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" value="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"/&gt;
  ...
&lt;/c:url&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>URL 重写是由 <CODE>&lt;c:url&gt;</CODE> 操作自动执行的。如果 JSP 容器检测到一个存储用户当前会话标识的 cookie，那么就不必进行重写。但是，如果不存在这样的 cookie，那么 <CODE>&lt;c:url&gt;</CODE> 生成的所有 URL 都会被重写以编码会话标识。注：如果在随后的请求中存在适当的 cookie，那么 <CODE>&lt;c:url&gt;</CODE> 将停止重写 URL 以包含该标识。 </P>
<P>如果为 <CODE>var</CODE> 属性提供了一个值（还可以同时为 <CODE>scope</CODE> 属性提供一个相应的值，这是可选的），那么将生成的 URL 赋值给这个限定了作用域的指定变量。否则，将使用当前的 <CODE>JspWriter</CODE> 输出生成的 URL。这种直接输出其结果的能力允许 <CODE>&lt;c:url&gt;</CODE> 标记作为值出现，例如，作为 HTML <CODE>&lt;a&gt;</CODE> 标记的 <CODE>href</CODE> 属性的值，如清单 13 中所示。 </P><A name=N40062A><B>清单 13. 生成 URL 作为 HTML 标记的属性值</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;a href="&lt;c:url value='/content/sitemap.jsp'/&gt;"&gt;View sitemap&lt;/a&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>最后，如果通过嵌套 <CODE>&lt;c:param&gt;</CODE> 标记指定了任何请求参数，那么将会使用 HTTP GET 请求的标准表示法将它们的名称和值添加到生成的 URL 后面。此外，还进行 URL 编码：为了生成有效的 URL，将对这些参数的名称或值中出现的任何字符适当地进行转换。清单 14 演示了 <CODE>&lt;c:url&gt;</CODE> 的这种行为。 </P><A name=N400645><B>清单 14. 生成带请求参数的 URL</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:url value="/content/search.jsp"&gt;
  &lt;c:param name="keyword" value="${searchTerm}"/&gt;
  &lt;c:param name="month" value="02/2003"/&gt;
&lt;/c:url&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>清单 14 中的 JSP 代码被部署到一个名为 <CODE>blog</CODE> 的 servlet 上下文，限定了作用域的变量 <CODE>searchTerm</CODE> 的值被设置为 <CODE>"core library"</CODE> 。如果检测到了会话 cookie，那么清单 14 生成的 URL 将类似于清单 15 中的 URL。注：在前面添加上下文名称，而在后面附加请求参数。此外， <CODE>keyword</CODE> 参数值中的空格和 <CODE>month</CODE> 参数值中的斜杠都被按照 HTTP GET 参数的需要进行了编码（确切地说，空格被转换成了 <CODE>+</CODE> ，而斜杠被转换成了 <CODE>%2F</CODE> 序列）。 </P><A name=N400671><B>清单 15. 有会话 cookie 时生成的 URL</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>/blog/content/search.jsp?keyword=foo+bar&amp;month=02%2F2003
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>当没有会话 cookie 时，生成的结果如清单 16 中所示。同样，servlet 上下文被添加到了前面，而 URL 编码的请求参数被附加到了后面。不过，除此以外还重写了基本 URL 以包含指定的会话标识。当浏览器发送用这种方式重写的 URL 请求时，JSP 容器自动抽取会话标识，并将请求与相应的会话进行关联。这样，需要会话管理的 J2EE 应用程序就无需依赖由应用程序用户启用的 cookie 了。</P><A name=N400681><B>清单 16. 没有会话 cookie 时生成的 URL</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>/blog/content/search.jsp;jsessionid=233379C7CD2D0ED2E9F3963906DB4290
  ?keyword=foo+bar&amp;month=02%2F2003
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=4><SPAN class=atitle2>导入内容</SPAN></A><BR>JSP 有两种内置机制可以将来自不同 URL 的内容合并到一个 JSP 页面： <CODE>include</CODE> 伪指令和 <CODE>&lt;jsp:include&gt;</CODE> 操作。不过，不管是哪种机制，要包含的内容都必须属于与页面本身相同的 Web 应用程序（或 servlet 上下文）。两个标记之间的主要区别在于： <CODE>include</CODE> 伪指令在页面编译期间合并被包含的内容，而 <CODE>&lt;jsp:include&gt;</CODE> 操作却在请求处理 JSP 页面时进行。 </P>
<P>从本质上讲， <CODE>core</CODE> 库的 <CODE>&lt;c:import&gt;</CODE> 操作是更通用、功能更强大的 <CODE>&lt;jsp:include&gt;</CODE> 版本（好像是 <CODE>&lt;jsp:include&gt;</CODE> “服用了兴奋剂”的）。和 <CODE>&lt;jsp:include&gt;</CODE> 一样， <CODE>&lt;c:import&gt;</CODE> 也是一种请求时操作，它的基本任务就是将其它一些 Web 资源的内容插入 JSP 页面中。如清单 17 中所示，它的语法非常类似于 <CODE>&lt;c:url&gt;</CODE> 的语法。 </P><A name=N4006C7><B>清单 17. &lt;c:import&gt; 操作的语法</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:import url="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" context="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"
    charEncoding="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" var="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">name</I>" scope="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">scope</I>"&gt;
  &lt;c:param name="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" value="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"/&gt;
  ...
&lt;/c:import&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>通过 <CODE>url</CODE> 属性指定将要导入内容的 URL，这个属性是 <CODE>&lt;c:import&gt;</CODE> 的唯一一个必选属性。这里允许使用相对 URL，并且根据当前页面的 URL 来解析这个相对 URL。但是，如果 <CODE>url</CODE> 属性的值以斜杠开始，那么它就被解释成本地 JSP 容器内的绝对 URL。如果没有为 <CODE>context</CODE> 属性指定值，那么就认为这样的绝对 URL 引用当前 servlet 上下文内的资源。如果通过 <CODE>context</CODE> 属性显式地指定了上下文，那么就根据指定的 servlet 上下文解析绝对（本地）URL。 </P>
<P>但 <CODE>&lt;c:import&gt;</CODE> 操作并不仅仅限于访问本地内容。也可以将包含协议和主机名的完整 URI 指定为 <CODE>url</CODE> 属性的值。实际上，协议甚至不仅局限于 HTTP。 <CODE>&lt;c:import&gt;</CODE> 的 <CODE>url</CODE> 属性值可以使用 <CODE>java.net.URL</CODE> 类所支持的任何协议。清单 18 中显示了这种能力。 </P>
<P>其中， <CODE>&lt;c:import&gt;</CODE> 操作用来包含通过 FTP 协议访问的文档内容。此外，还使用了 <CODE>&lt;c:catch&gt;</CODE> 操作，以便在本地处理 FTP 文件传送期间可能发生的任何错误。错误处理是这样实现的：使用 <CODE>&lt;c:catch&gt;</CODE> 的 <CODE>var</CODE> 属性为异常指定一个限定了作用域的变量，然后使用 <CODE>&lt;c:if&gt;</CODE> 检查其值。如果产生了异常，那么就会对那个限定了作用域的变量进行赋值：如清单 18 中的 EL 表达式所显示的那样，该变量的值将 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">不</I>会为空。由于 FTP 文档的检索将会失败，因此会显示有关这种情况的错误消息。 </P><A name=listing18><B>清单 18. 将 &lt;c:import&gt; 与 &lt;c:catch&gt; 相结合的示例</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:catch var="exception"&gt;
  &lt;c:import url="ftp://ftp.example.com/package/README"/&gt;
&lt;/c:catch&gt;
&lt;c:if test="${not empty exception}"&gt;
  Sorry, the remote content is not currently available.
&lt;/c:if&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><CODE>&lt;c:import&gt;</CODE> 操作的最后两个（可选的）属性是 <CODE>var</CODE> 和 <CODE>scope</CODE> 。 <CODE>var</CODE> 属性会导致从指定 URL 获取的内容（作为 <CODE>String</CODE> 值）被存储在一个限定了作用域的变量中，而不是包含在当前 JSP 页面中。 <CODE>scope</CODE> 属性控制该变量的作用域，缺省情况下是页面作用域。如同我们在今后的文章中将要看到的那样，JSTL <CODE>xml</CODE> 库中的标记利用了 <CODE>&lt;c:import&gt;</CODE> 这种能力，即将整个文档存储在一个限定了作用域的变量中。 </P>
<P>还要注意的是，可以使用（可选的）嵌套的 <CODE>&lt;c:param&gt;</CODE> 标记来为正在导入的 URL 指定请求参数。与在 <CODE>&lt;c:url&gt;</CODE> 中嵌套 <CODE>&lt;c:param&gt;</CODE> 标记一样，必要时也要对参数名称和参数值进行 URL 编码。 </P>
<P><A name=5><SPAN class=atitle2>请求重定向</SPAN></A><BR>最后一个 <CODE>core</CODE> 库标记是 <CODE>&lt;c:redirect&gt;</CODE> 。该操作用于向用户的浏览器发送 HTTP 重定向响应，它是 JSTL 中与 <CODE>javax.servlet.http.HttpServletResponse</CODE> 的 <CODE>sendRedirect()</CODE> 方法功能相当的标记。清单 19 中显示了该标记的 <CODE>url</CODE> 和 <CODE>context</CODE> 属性，它们的行为分别等同于 <CODE>&lt;c:import&gt;</CODE> 的 <CODE>url</CODE> 和 <CODE>context</CODE> 属性的行为，是嵌套任何 <CODE>&lt;c:param&gt;</CODE> 标记的结果。 </P><A name=N4007A2><B>清单 19. &lt;c:redirect&gt; 操作的语法</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:redirect url="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" context="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"&gt;
  &lt;c:param name="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>" value="
        <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">expression</I>"/&gt;
  ...
&lt;/c:redirect&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>清单 20 显示了 <CODE>&lt;c:redirect&gt;</CODE> 操作，它用一个到指定错误页面的重定向代替了清单 18 中的错误消息。在该示例中， <CODE>&lt;c:redirect&gt;</CODE> 标记的用法与标准 <CODE>&lt;jsp:forward&gt;</CODE> 操作的用法类似。不过请回忆一下：通过请求分派器进行转发是在服务器端实现的，而重定向却是由浏览器来执行的。从开发人员的角度来讲，转发比重定向更有效率，但 <CODE>&lt;c:redirect&gt;</CODE> 操作却更灵活一些，因为 <CODE>&lt;jsp:forward&gt;</CODE> 只能分派到当前 servlet 上下文内的其它 JSP 页面。 </P><A name=N4007D2><B>清单 20. 响应异常的重定向</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>&lt;c:catch var="exception"&gt;
  &lt;c:import url="ftp://ftp.example.com/package/README"/&gt;
&lt;/c:catch&gt;
&lt;c:if test="${not empty exception}"&gt;
  &lt;c:redirect url="/errors/remote.jsp"/&gt;
&lt;/c:if&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>从用户的角度来看，主要区别在于重定向会更新浏览器所显示的 URL，并因此影响书签的设置。转发却不这样，它对最终用户是透明的。这样，选择 <CODE>&lt;c:redirect&gt;</CODE> 还是 <CODE>&lt;jsp:forward&gt;</CODE> 还取决于所期望的用户体验。 </P>
<P><A name=6><SPAN class=atitle2>结束语</SPAN></A><BR>JSTL <CODE>core</CODE> 库含有多种通用的定制标记，广大的 JSP 开发人员都会使用这些标记。例如，URL 和异常处理标记很好地补充了现有的 JSP 功能，如 <CODE>&lt;jsp:include&gt;</CODE> 和 <CODE>&lt;jsp:forward&gt;</CODE> 操作、 <CODE>include</CODE> 伪指令以及 <CODE>page</CODE> 伪指令的 <CODE>errorpage</CODE> 属性。迭代和条件操作使得无需脚本编制元素就能够实现复杂的表示逻辑，尤其在将变量标记（ <CODE>&lt;c:set&gt;</CODE> 和 <CODE>&lt;c:remove&gt;</CODE> ）与 EL 相结合使用时更是如此。 </P>
<P><A name=resources><SPAN class=atitle2>参考资料 </SPAN></A>
<UL>
<LI>您可以参阅本文在 developerWorks 全球站点上的 <A href="http://www.ibm.com/developerworks/library/j-jstl0318/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">英文原文</A>. <BR><BR>
<LI>本系列的第一篇文章 <A href="http://www-128.ibm.com/developerworks/cn/java/j-jstl0211/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">JSTL 入门：表达式语言</A>（ <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">developerWorks</I>，2003 年 2 月）介绍了 JSTL，并详细讨论了表达式语言及 <CODE>core</CODE> 库中的几个标记。 <BR><BR>
<LI>下载 Weblog 示例应用程序的 <A href="ftp://www6.software.ibm.com/software/developer/library/j-jstl0318.jar" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">源代码</A>。 <BR><BR>
<LI>Sun 的 <A href="http://java.sun.com/products/jsp/jstl/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">JSP 标准标记库产品页面</A>是深入了解 JSTL 的不错的起点。 <BR><BR>
<LI><A href="http://jcp.org/aboutJava/communityprocess/final/jsr052/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">JSTL 1.0 规范</A>是 EL 和四个 JSTL 标记库的最权威的参考资料。 <BR><BR>
<LI><A href="http://jakarta.apache.org/taglibs/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">Jakarta Taglibs</A>项目是 JSTL 1.0 参考实现的主页。 <BR><BR>
<LI>Shawn Bayern 撰写的 <A href="http://www.manning.com/bayern/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"><I>JSTL in Action</I> </A>（Manning，2002 年）出色地讨论了由参考实现领导编写的全部 JSTL 功能。 <BR><BR>
<LI>David Geary 是一位很受欢迎的 Java 编程方面的作者，他还著有一部关于 JSTL 的书籍，名为 <A href="http://www.core-jstl.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"><I>Core JSTL</I> </A>。 <BR><BR>
<LI><A href="http://jsptags.com/index.jsp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">JSPTags.com</A>是一个 JSP 参考资料目录，它尤其着重于定制标记库方面的内容。 <BR><BR>
<LI>Sun 的 <A href="http://java.sun.com/webservices/docs/ea2/tutorial/doc/JSTL3.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">Java Web Services Tutorial</A>的一部分也涉及到了对 JSTL 的讨论。 <BR><BR>
<LI>通过学习“ <A href="http://www7b.software.ibm.com/wsdd/library/tutorials/vajwebsph353/Part-I/JSP11Part-I.html&amp;origin=j" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">Using JSPs and custom tags within VisualAge for Java and WebSphere Studio</A>”（ <A href="http://www7b.software.ibm.com/wsdd/&amp;origin=j" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">WebSphere 开发者园地</A>），动手实践 WBOnline，这篇文章演示了如何使用 servlet、JSP 页面及定制标记库。 <BR><BR>
<LI>通过 Jeff Wilson 的优秀文章“ <A href="http://www-128.ibm.com/developerworks/cn/java/j-taglib/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">使用定制标记控制 JSP 页面</A>”（ <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">developerWorks</I>，2002 年 1 月）来学习关于定制标记库的全部内容。 <BR><BR>
<LI>Noel Bergman 的文章“ <A href="http://www-128.ibm.com/developerworks/cn/java/j-jsptags/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">JSP 标记库：着意设计的更好的可用性</A>”（ <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">developerWorks</I>，2001 年 12 月）向您显示了声明性标记是如何帮助改善 JSP 页面的可用性。 <BR><BR>
<LI>在 <A href="http://www-128.ibm.com/developerworks/cn/java/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"><I>developerWorks</I>Java 技术专区 </A>可以找到数百篇有关 Java 技术的参考资料。 <BR></LI></UL>
<P></P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><A name=author1></A><SPAN class=atitle2>关于作者</SPAN><BR>Mark Kolb 是一位在德克萨斯州奥斯汀市（Austin）工作的软件工程师。他常就服务器端 Java 这一主题在业界演讲，而且还是 <A href="http://www.manning.com/fields2/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"><I>Web Development with JavaServer Pages, 第二版</I> </A>一书的合著者。可以通过 <A href="mailto:mak@taglib.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">mak@taglib.com</A>与 Mark 联系。 </TD></TR></TBODY></TABLE><BR clear=all><img src ="http://www.blogjava.net/kapok/aggbug/3219.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-13 15:58 <a href="http://www.blogjava.net/kapok/archive/2005/04/13/3219.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>再谈编码测试</title><link>http://www.blogjava.net/kapok/archive/2005/04/12/3166.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 12 Apr 2005 01:46:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/12/3166.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3166.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/12/3166.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3166.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3166.html</trackback:ping><description><![CDATA[<P>package test;</P>
<P>import java.io.UnsupportedEncodingException;</P>
<P>/**<BR>&nbsp;* Classname :<BR>&nbsp;* ClassType :<BR>&nbsp;*<BR>&nbsp;* @version $Revision$<BR>&nbsp;* @author $author$<BR>&nbsp;*/<BR>public class HelloWorld {<BR>&nbsp;&nbsp;&nbsp; String sEnglish&nbsp; = "Apple";<BR>&nbsp;&nbsp;&nbsp; String sChinese&nbsp; = "苹果";<BR>&nbsp;&nbsp;&nbsp; String mixString = "Apple 苹果";</P>
<P>&nbsp;&nbsp;&nbsp; /**<BR>&nbsp;&nbsp;&nbsp;&nbsp; * DOCUMENT ME!<BR>&nbsp;&nbsp;&nbsp;&nbsp; *<BR>&nbsp;&nbsp;&nbsp;&nbsp; * @param args DOCUMENT ME!<BR>&nbsp;&nbsp;&nbsp;&nbsp; * @throws UnsupportedEncodingException<BR>&nbsp;&nbsp;&nbsp;&nbsp; */<BR>&nbsp;&nbsp;&nbsp; public static void main(String[] args)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throws UnsupportedEncodingException {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Hello, World!");</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HelloWorld hw = new HelloWorld();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("sEnglish String length: " + hw.sEnglish.length());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("sChinese String length: " + hw.sChinese.length());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("mixString String length: " + hw.mixString.length());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("********************************************************************");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("UTF-8 Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.sEnglish.getBytes("UTF-8").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("UTF-8 Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.sChinese.getBytes("UTF-8").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("UTF-8 Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.mixString.getBytes("UTF-8").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("********************************************************************");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("GBK Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.sEnglish.getBytes("GBK").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("GBK Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.sChinese.getBytes("GBK").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("GBK Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.mixString.getBytes("GBK").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("********************************************************************");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("UTF-16 Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.sEnglish.getBytes("UTF-16").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("UTF-16 Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.sChinese.getBytes("UTF-16").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("UTF-16 Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.mixString.getBytes("UTF-16").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("********************************************************************");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Unicode Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.sEnglish.getBytes("Unicode").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Unicode Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.sChinese.getBytes("Unicode").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Unicode Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.mixString.getBytes("Unicode").length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("********************************************************************");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Default Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.sEnglish.getBytes().length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Default Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.sChinese.getBytes().length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Default Bytes length: "<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + hw.mixString.getBytes().length);<BR>&nbsp;&nbsp;&nbsp; }<BR>}<BR><BR><BR>Result:<BR><BR><BR>Hello, World!<BR>sEnglish String length: 5<BR>sChinese String length: 2<BR>mixString String length: 8<BR>********************************************************************<BR>UTF-8 Bytes length: 5<BR>UTF-8 Bytes length: 6<BR>UTF-8 Bytes length: 12<BR>********************************************************************<BR>GBK Bytes length: 5<BR>GBK Bytes length: 4<BR>GBK Bytes length: 10<BR>********************************************************************<BR>UTF-16 Bytes length: 12<BR>UTF-16 Bytes length: 6<BR>UTF-16 Bytes length: 18<BR>********************************************************************<BR>Unicode Bytes length: 12<BR>Unicode Bytes length: 6<BR>Unicode Bytes length: 18<BR>********************************************************************<BR>Default Bytes length: 5<BR>Default Bytes length: 4<BR>Default Bytes length: 10</P><img src ="http://www.blogjava.net/kapok/aggbug/3166.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-12 09:46 <a href="http://www.blogjava.net/kapok/archive/2005/04/12/3166.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tomcat的编码问题</title><link>http://www.blogjava.net/kapok/archive/2005/04/11/3118.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 11 Apr 2005 04:27:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/11/3118.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3118.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/11/3118.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3118.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3118.html</trackback:ping><description><![CDATA[<A href="http://www.javaworld.com.tw/jute/post/view?bid=9&amp;id=44042&amp;sty=1&amp;tpg=1&amp;age=0">http://www.javaworld.com.tw/jute/post/view?bid=9&amp;id=44042&amp;sty=1&amp;tpg=1&amp;age=0</A><img src ="http://www.blogjava.net/kapok/aggbug/3118.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-11 12:27 <a href="http://www.blogjava.net/kapok/archive/2005/04/11/3118.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>javascript modal dialog</title><link>http://www.blogjava.net/kapok/archive/2005/04/05/2861.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 05 Apr 2005 01:09:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/05/2861.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/2861.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/05/2861.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/2861.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/2861.html</trackback:ping><description><![CDATA[<P align=justify><A href="http://www.upsaid.com/hase/index.php?action=viewcom&amp;id=69">http://www.upsaid.com/hase/index.php?action=viewcom&amp;id=69</A><BR>因为业务系统的特殊性，在web程序中经常使用modal dialog，但是showModalDialog只能在IE中使用，于是又麻烦google，终于找到了解决方案： <A href="http://www.eggheadcafe.com/articles/javascript_modal_dialog.asp"><FONT color=#3399cc>javascript modal dialog</FONT></A>.</P>
<P>为了记忆，把demo的代码抄录： <BR><PRE><BR>&lt;html&gt;
<BR>&lt;script language=JavaScript&gt;
<BR>
<BR> 
<BR>var ModalDialogWindow;
<BR>var ModalDialogInterval;
<BR>var ModalDialog = new Object;
<BR>
<BR>ModalDialog.value = '';
<BR>ModalDialog.eventhandler = '';
<BR> 
<BR>
<BR>function ModalDialogMaintainFocus()
<BR>{
<BR>  try
<BR>  {
<BR>    if (ModalDialogWindow.closed)
<BR>     {
<BR>        window.clearInterval(ModalDialogInterval);
<BR>        eval(ModalDialog.eventhandler);       
<BR>        return;
<BR>     }
<BR>    ModalDialogWindow.focus(); 
<BR>  }
<BR>  catch (everything) {   }
<BR>}
<BR>        
<BR> function ModalDialogRemoveWatch()
<BR> {
<BR>    ModalDialog.value = '';
<BR>    ModalDialog.eventhandler = '';
<BR> }
<BR>        
<BR> function ModalDialogShow(Title,BodyText,Buttons,EventHandler)
<BR> {
<BR>
<BR>   ModalDialogRemoveWatch();
<BR>   ModalDialog.eventhandler = EventHandler;
<BR>
<BR>   var args='width=350,height=125,left=325,top=300,toolbar=0,';
<BR>       args+='location=0,status=0,menubar=0,scrollbars=1,resizable=0';  
<BR>
<BR>   ModalDialogWindow=window.open("","",args); 
<BR>   ModalDialogWindow.document.open(); 
<BR>   ModalDialogWindow.document.write('&lt;html&gt;');
<BR>   ModalDialogWindow.document.write('&lt;head&gt;'); 
<BR>   ModalDialogWindow.document.write('&lt;title&gt;' + Title + '&lt;/title&gt;');
<BR>   ModalDialogWindow.document.write('&lt;script' + ' language=JavaScript&gt;');
<BR>   ModalDialogWindow.document.write('function CloseForm(Response) ');
<BR>   ModalDialogWindow.document.write('{ ');
<BR>   ModalDialogWindow.document.write(' window.opener.ModalDialog.value = Response; ');
<BR>   ModalDialogWindow.document.write(' window.close(); ');
<BR>   ModalDialogWindow.document.write('} ');
<BR>   ModalDialogWindow.document.write('&lt;/script' + '&gt;');        
<BR>   ModalDialogWindow.document.write('&lt;/head&gt;');   
<BR>   ModalDialogWindow.document.write('&lt;body onblur="window.focus();"&gt;');
<BR>   ModalDialogWindow.document.write('&lt;table border=0 width="95%" align=center cellspacing=0 cellpadding=2&gt;');
<BR>   ModalDialogWindow.document.write('&lt;tr&gt;&lt;td align=left&gt;' + BodyText + '&lt;/td&gt;&lt;/tr&gt;');
<BR>   ModalDialogWindow.document.write('&lt;tr&gt;&lt;td align=left&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;');
<BR>   ModalDialogWindow.document.write('&lt;tr&gt;&lt;td align=center&gt;' + Buttons + '&lt;/td&gt;&lt;/tr&gt;');
<BR>   ModalDialogWindow.document.write('&lt;/body&gt;');
<BR>   ModalDialogWindow.document.write('&lt;/html&gt;'); 
<BR>   ModalDialogWindow.document.close(); 
<BR>   ModalDialogWindow.focus(); 
<BR>   ModalDialogInterval = window.setInterval("ModalDialogMaintainFocus()",5);
<BR>
<BR> }
<BR>
<BR>
<BR>&lt;/script&gt;
<BR>
<BR>&lt;script language=JavaScript&gt;
<BR>
<BR>
<BR>  function YesNoCancel(BodyText,EventHandler)
<BR>  {
<BR>     var Buttons=''; 
<BR>     Buttons = '&lt;a href=javascript:CloseForm("Yes");&gt;Yes&lt;/a&gt;&nbsp;&nbsp;';
<BR>     Buttons += '&lt;a href=javascript:CloseForm("No");&gt;No&lt;/a&gt;&nbsp;&nbsp;';
<BR>     Buttons += '&lt;a href=javascript:CloseForm("Cancel");&gt;Cancel&lt;/a&gt;&nbsp;&nbsp;';
<BR>     ModalDialogShow("Dialog",BodyText,Buttons,EventHandler);
<BR>  }
<BR>
<BR>  function YesNoMaybe(BodyText,EventHandler)
<BR>  {
<BR>     var Buttons=''; 
<BR>     Buttons = '&lt;a href=javascript:CloseForm("Yes");&gt;Yes&lt;/a&gt;&nbsp;&nbsp;';
<BR>     Buttons += '&lt;a href=javascript:CloseForm("No");&gt;No&lt;/a&gt;&nbsp;&nbsp;';
<BR>     Buttons += '&lt;a href=javascript:CloseForm("Maybe");&gt;Maybe&lt;/a&gt;&nbsp;&nbsp;';
<BR>     ModalDialogShow("Dialog",BodyText,Buttons,EventHandler);
<BR>  }
<BR> 
<BR> function YesNoCancelReturnMethod()
<BR> {
<BR>   document.getElementById('modalreturn1').value =  ModalDialog.value;
<BR>   ModalDialogRemoveWatch();
<BR> }
<BR>
<BR> function YesNoMaybeReturnMethod()
<BR>{
<BR>    document.getElementById('modalreturn2').value = ModalDialog.value;
<BR>    ModalDialogRemoveWatch();
<BR>}
<BR>
<BR>
<BR>&lt;/script&gt;
<BR> 
<BR>&lt;BODY &gt;
<BR>
<BR>  &lt;table border=1 cellpadding=2 cellspacing=2 align=center width="60%"&gt;
<BR>    &lt;tr&gt;&lt;td align=left&gt;&lt;/td&gt;&lt;/tr&gt;
<BR>    &lt;tr&gt;&lt;td align=left&gt;&lt;/td&gt;&lt;/tr&gt;
<BR>
<BR>    &lt;tr&gt;&lt;td align=left&gt;&lt;/td&gt;&lt;/tr&gt;
<BR>    &lt;tr&gt;
<BR>	   &lt;td align=left&gt;&lt;a href="javascript:YesNoCancel('Yes, no, or cancel me','YesNoCancelReturnMethod()');"&gt;Show Modal #1&lt;/a&gt;&nbsp;&nbsp;&nbsp;
<BR>          1. &lt;input type=text id=modalreturn1 name=modalreturn1 value=''&gt;&lt;/td&gt;
<BR>
<BR>  	&lt;/tr&gt;
<BR>    &lt;tr&gt;
<BR>	   &lt;td align=left&gt;&lt;a href="javascript:YesNoMaybe('Yes, no, or maybe me','YesNoMaybeReturnMethod()');"&gt;Show Modal #2&lt;/a&gt;&nbsp;&nbsp;&nbsp;
<BR>         2. &lt;input type=text id=modalreturn2 name=modalreturn2 value=''&gt;&lt;/td&gt;
<BR>
<BR>  	&lt;/tr&gt;
<BR> 
<BR>  &lt;/table&gt;
<BR>
<BR>&lt;/BODY&gt;
<BR>&lt;/HTML&gt;
</PRE><img src ="http://www.blogjava.net/kapok/aggbug/2861.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-05 09:09 <a href="http://www.blogjava.net/kapok/archive/2005/04/05/2861.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于集合框架的思考 </title><link>http://www.blogjava.net/kapok/archive/2005/04/03/2801.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 03 Apr 2005 15:06:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/03/2801.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/2801.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/03/2801.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/2801.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/2801.html</trackback:ping><description><![CDATA[<DIV class=postText><FONT size=2><FONT face=Verdana><STRONG><U><FONT color=#cc0000><SPAN class=style4><FONT size=3><A href="http://www.blogjava.net/jungleford/archive/2005/04/02/2759.html">http://www.blogjava.net/jungleford/archive/2005/04/02/2759.html</A><BR><BR>jungleford如是说<BR></FONT></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;对于Java集合框架（Java Collections Framework，JCF），Java玩家大概都不会陌生，在C++里面相似的概念是标准模板库（Standard Template Library，STL），主要是对一些数据结构和相关算法的封装。考虑到这是一个Java初学者将会经常接触的工具，所以有了以下的一些文字。主要是参考了</FONT></FONT><A href="http://www-900.ibm.com/developerWorks/cn/cnedu.nsf/java-onlinecourse-bytitle/4475AC55462B0B9448256B3B001F279A?OpenDocument" target=_blank><FONT face=Verdana color=#000080 size=2>IBM developerWorks上的一篇教程</FONT></A><FONT face=Verdana size=2>，它可能解释得更加清晰，这里算是浓缩了一下吧，真正的来龙去脉可以看看JDK文档里的“</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/guide/collections/index.html" target=_blank><FONT face=Verdana color=#000080 size=2>The Collections Framework</FONT></A><FONT face=Verdana size=2>”，说明更为详细。 </FONT>
<P><STRONG><U><FONT color=#cc0000><FONT size=2><FONT face=Verdana><SPAN class=style4><FONT size=3>问题的源头</FONT></SPAN><BR></FONT></FONT></FONT></U></STRONG></P>
<UL>
<LI><STRONG><FONT face=Verdana size=2>集合：对象的容器与数据结构</FONT></STRONG></LI></UL><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;回忆一下我们在程序设计里头可能会面对一些什么，无非是两类：基本类型和复合类型，后者常见的组织方式就是类。和基本类型不同，类对象通常是需要以动态方式分配的，譬如在内存的堆空间里new一个对象，这个我们一写OO的程序就必然会用到。同时我们面对的不仅仅是单个的基本类型或对象，对多个这样的数据我们通常采用的组织方式是什么？不错，是数组，这是伴随程序设计的一个古老概念。数组的优点显而易见，像根据下标检索元素这样的操作不费吹灰之力，但缺点也很明显：空间固定而不能动态增长（像Java这样的强类型语言对数组越界是及其敏感的），插入或删除元素比较费劲。因此数组不是解决一切集合问题的方便工具。我们可能需要一些新的工具，研究这些工具常常就是研究<SPAN class=style5><FONT color=#cc0000>数据结构</FONT></SPAN>，特别的，数组本身就是一种线性有序的数据结构。<BR>&nbsp;&nbsp;&nbsp;&nbsp;数据结构的数学基础是<SPAN class=style5><FONT color=#cc0000>集合论</FONT></SPAN>，为什么这么说呢？上面说过，现在我们要研究的不是单个的基本类型或对象，多个对象的整体不就是集合吗？从OO的角度上看，集合也是一种对象，但它是一种特殊的对象：对象的容器（注意，我们这里没有继续讨论基本类型的集合，因为基本类型和存储分配方式与对象有着本质的差别）。集合论的一个根本问题就是：给定一个元素，集合必须能够回答该元素是或者不是属于这个集合。还有一个问题也很重要，就是：如果元素是属于一个集合，那该元素在集合中的地位应该是唯一的，或者说它是<SPAN class=style5><FONT color=#cc0000>唯一确定</FONT></SPAN>的。当然还有其它问题，譬如查找、遍历、排序等等，这和具体的集合类型相关，后面将会讲到。 </FONT>
<UL>
<LI><STRONG><FONT face=Verdana size=2>无序集、有序集、映射</FONT></STRONG></LI></UL><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;谈到集合的类型，我们在高中所学的集合概念是其中的一种，叫做“<SPAN class=style5><FONT color=#cc0000>无序集</FONT></SPAN>”，也就是说集合的各个元素都是平等的，没有先后的区别，于是在无序集当中就决不允许出现一模一样的元素，否则当取到这个元素的时候就不知道应该取哪一个，这就违反了上面的“唯一确定”原则。<BR>&nbsp;&nbsp;&nbsp;&nbsp;等到我们上了大学，开始知道了另一种集合类型，叫做“<SPAN class=style5><FONT color=#cc0000>有序集</FONT></SPAN>”（或者叫“线性表”，区别于以后碰到的像“树”，“图”这样的非线性的数据结构），如果是计算机专业的，大概学过离散数学当中的“代数结构”，那你就更清楚的知道，“有序集”其实是一种“二元关系”，确切的说是“偏序关系”，它是可以包含相同元素的，因为两个的相同元素的“序号”可以不同，这样根据“序号”仍可以“唯一确定”一个元素，数组就是一种有序集，有序集的另一个特点就是任意两个元素可以确定他们的顺序。<BR>&nbsp;&nbsp;&nbsp;&nbsp;无序集，有序集，难道还有第三种可能？呵呵，它还是出现在我们的高中代数课本里，叫做“<SPAN class=style5><FONT color=#cc0000>映射</FONT></SPAN>”。映射也是集合？其实自从康托尔以来，集合论就认为“万物皆集合”（但也就是这个断言导致了集合论以后的尴尬境地，有兴趣可以看看罗素或哥德尔的一些结论，或</FONT><A href="http://www.google.com/" target=_blank><FONT face=Verdana color=#000080 size=2>google</FONT></A><FONT face=Verdana size=2>“集合论 悖论”）。映射其实是一种“元素对”的集合，就像<SPAN class=style6><FONT style="BACKGROUND-COLOR: #cccccc">f(a)=b, f(c)=d, ...</FONT></SPAN>等效于集合（无序集）<SPAN class=style6><FONT style="BACKGROUND-COLOR: #cccccc">{(a, b), (c, d), ...}</FONT></SPAN>，在“映射”中可以看作是(原象, 象)的集合，换一种说法就是(关键字key, 值value)的集合。所以我们可以在笛卡儿正交坐标平面上画出漂亮的函数图像，因为在集合论看来，函数（映射）就是二维平面上的一个个点，明白了？这样一来上面的“有序集”也好理解了，偏序关系<SPAN class=style6><FONT style="BACKGROUND-COLOR: #cccccc">a&gt;b&gt;c&gt;d&gt;...</FONT></SPAN>（不知道“偏序关系”就把它们看作是数组<SPAN class=style6><FONT style="BACKGROUND-COLOR: #cccccc">x[1]=a, x[2]=b, x[3]=c, x[4]=d ...</FONT></SPAN>好了）等效于无序集<SPAN class=style6><FONT style="BACKGROUND-COLOR: #cccccc">{(1, a), (2, b), (3, c), (4, d), ...}</FONT></SPAN>，于是乎，所有的集合都等效于无序集！所以高中只教了我们一种集合，呵呵…… </FONT>
<P></P>
<P><FONT size=2><FONT face=Verdana><STRONG><U><FONT color=#cc0000><SPAN class=style4><FONT size=3>JCF的全家福</FONT><BR></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;好啦好啦，这些我们都知道，又不是在上数学课，说了这么多废话，怎么还没扯到正题上来？JCF的影子我还没看见呢！列位看官别急，这就给您道来。其实上面的概念对理解JCF非常重要。<BR>&nbsp;&nbsp;&nbsp;&nbsp;JCF是个颇有点规模的家族，看看它的类层次关系图就知道了，下面这张图（图1）摘自著名的</FONT></FONT><A href="http://www.mindview.net/Books/TIJ/" target=_blank><FONT face=Verdana color=#000080 size=2>Thinking in Java</FONT></A><FONT face=Verdana size=2>：<BR></FONT><A name=fig1></A><IMG title="JCF 家族" height=435 alt=o_collection.gif src="http://www.blogjava.net/images/blogjava_net/jungleford/924/o_collection.gif" width=480 border=0><FONT face=Verdana size=2><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 图1 JCF层次结构<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;哇，这么多接口和类，真有点让人无从下手的感觉。其实我们真正需要记住的只是这么一个超超easy的结构（图2）：<BR><BR></FONT><A name=fig2></A><IMG title="简化的 JCF 骨架" height=145 alt=o_collection1.gif src="http://www.blogjava.net/images/blogjava_net/jungleford/924/o_collection1.gif" width=289 border=0><FONT face=Verdana size=2><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;图2<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;这张图看起来舒服多了吧？但它又能说明什么问题呢？它怎么就能够把握整个JCF呢？我们把</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html" target=_blank><FONT face=Verdana color=#000080 size=2>Collection</FONT></A><FONT face=Verdana size=2>接口置于最顶上，意思是想说：Collection其实是整个JCF家族中的“祖宗”，几乎所有的JCF成员都源自该接口，或者和它有密切的关系，Collection提供关于集合的一些通用操作的接口，包括插入（</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html#add(java.lang.Object)" target=_blank><FONT face=Verdana color=#000080 size=2>add</FONT></A><FONT face=Verdana size=2>()方法）、删除（</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html#remove(java.lang.Object)" target=_blank><FONT face=Verdana color=#000080 size=2>remove</FONT></A><FONT face=Verdana size=2>()方法）、判断一个元素是不是其成员（</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html#contains(java.lang.Object)" target=_blank><FONT face=Verdana color=#000080 size=2>contains</FONT></A><FONT face=Verdana size=2>()方法）、遍历（</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html#iterator()" target=_blank><FONT face=Verdana color=#000080 size=2>iterator</FONT></A><FONT face=Verdana size=2>()方法）等等。注意了，前面的“废话”在这里将得到体现：</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Set.html" target=_blank><FONT face=Verdana color=#000080 size=2>Set</FONT></A><FONT face=Verdana size=2>接口体现的是“无序集”的概念，它是不允许有重复元素出现的；</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html" target=_blank><FONT face=Verdana color=#000080 size=2>List</FONT></A><FONT face=Verdana size=2>接口代表“有序集”；而</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" target=_blank><FONT face=Verdana color=#000080 size=2>Map</FONT></A><FONT face=Verdana size=2>接口则是“映射”（在早期的Java版本中并不叫Map，我们在后面会看到），其实</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.Entry.html" target=_blank><FONT face=Verdana color=#000080 size=2>Map.Entry</FONT></A><FONT face=Verdana size=2>接口就是代表一个“元素对”我们可以通过Map的</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html#entrySet()" target=_blank><FONT face=Verdana color=#000080 size=2>entrySet</FONT></A><FONT face=Verdana size=2>()方法得到这样一个由“元素对”组成的Set对象。我们注意到Set和List都是从“祖宗”Collection派生的，而Map不是，毕竟对一对元素的操作与对单个元素的操作还是有区别的，但是如果你仔细对照一下Collection和Map的源代码，以及它们的直接后代</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractCollection.html" target=_blank><FONT face=Verdana color=#000080 size=2>AbstractCollection</FONT></A><FONT face=Verdana size=2>和</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractMap.html" target=_blank><FONT face=Verdana color=#000080 size=2>AbstractMap</FONT></A><FONT face=Verdana size=2>的源代码，你将会发现很多相似的地方，所以我们仍然可以把Map看成是和Collection有着血缘关系的接口，而和Set，List一起处于并列的位置。<BR>&nbsp;&nbsp;&nbsp;&nbsp;有了“无序集”，“有序集”和“映射”，我们就可以定义各种各样的抽象数据结构了，譬如图1所示的向量，链表，堆栈，哈希表，平衡二叉树等。但我们需要记住的，仅仅是图2，置于其它的成员，在用到的时候查一下API手册不就行了？不过一般初学者还是比较容易用到一些类，像Vector、ArrayList、HashMap，我在这里列了一张表，显示了常见的JCF成员及其关系： </FONT>
<TABLE cellSpacing=0 cellPadding=0 border=1>
<TBODY>
<TR>
<TD vAlign=top width=484 colSpan=5>
<P align=center><FONT face=Verdana size=2>集合框架的祖宗： </FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html" target=_blank><FONT face=Verdana color=#000080 size=2>Collection</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=118>
<P align=center><FONT face=Verdana size=2>历史集合 </FONT></P></TD>
<TD vAlign=top width=398 colSpan=4>
<P align=center><FONT face=Verdana size=2>新集合 </FONT></P></TD></TR>
<TR>
<TD vAlign=top width=195 colSpan=2>
<P align=center><FONT face=Verdana size=2>无序集： </FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Set.html" target=_blank><FONT face=Verdana color=#000080 size=2>Set</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=289 colSpan=3>
<P align=center><FONT face=Verdana size=2>有序集： </FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html" target=_blank><FONT face=Verdana color=#000080 size=2>List</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=118>
<P align=center><FONT face=Verdana size=2>映射：</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Dictionary.html" target=_blank><FONT face=Verdana color=#000080 size=2>Dictionary</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=398 colSpan=4>
<P align=center><FONT face=Verdana size=2>映射：</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" target=_blank><FONT face=Verdana color=#000080 size=2>Map</FONT></A><FONT face=Verdana size=2> </FONT></P></TD></TR>
<TR>
<TD vAlign=top width=101 rowSpan=2>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractSet.html" target=_blank><FONT face=Verdana color=#000080 size=2>AbstractSet</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=94 rowSpan=2>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/SortedSet.html" target=_blank><FONT face=Verdana color=#000080 size=2>SortedSet</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=147 colSpan=2>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractList.html" target=_blank><FONT face=Verdana color=#000080 size=2>AbstractList</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=143>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractSequentialList.html" target=_blank><FONT face=Verdana color=#000080 size=2>AbstractSequentialList</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=118 rowSpan=3>
<P align=center><BR><BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Hashtable.html" target=_blank><FONT face=Verdana color=#000080 size=2>Hashtable</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=321 colSpan=3>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractMap.html" target=_blank><FONT face=Verdana color=#000080 size=2>AbstractMap</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=77>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/SortedMap.html" target=_blank><FONT face=Verdana color=#000080 size=2>SortedMap</FONT></A><FONT face=Verdana size=2> </FONT></P></TD></TR>
<TR>
<TD vAlign=top width=78>
<P align=center><FONT face=Verdana size=2>历史集合 </FONT></P></TD>
<TD vAlign=top width=69>
<P align=center><FONT face=Verdana size=2>新集合 </FONT></P></TD>
<TD vAlign=top width=143 rowSpan=2>
<P align=center><BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/LinkedList.html" target=_blank><FONT face=Verdana color=#000080 size=2>LinkedList</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=100 rowSpan=2>
<P align=center><BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/WeakHashMap.html" target=_blank><FONT face=Verdana color=#000080 size=2>WeakHashMap</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=112 rowSpan=2>
<P align=center><BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/IdentityHashMap.html" target=_blank><FONT face=Verdana color=#000080 size=2>IdentityHashMap</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=109 rowSpan=2>
<P align=center><BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/HashMap.html" target=_blank><FONT face=Verdana color=#000080 size=2>HashMap</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=77 rowSpan=2>
<P align=center><BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/TreeMap.html" target=_blank><FONT face=Verdana color=#000080 size=2>TreeMap</FONT></A><FONT face=Verdana size=2> </FONT></P></TD></TR>
<TR>
<TD vAlign=top width=101>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/HashSet.html" target=_blank><FONT face=Verdana color=#000080 size=2>HashSet</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=94>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/TreeSet.html" target=_blank><FONT face=Verdana color=#000080 size=2>TreeSet</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=78>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Vector.html" target=_blank><FONT face=Verdana color=#000080 size=2>Vector</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=69>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html" target=_blank><FONT face=Verdana color=#000080 size=2>ArrayList</FONT></A><FONT face=Verdana size=2> </FONT></P></TD></TR>
<TR>
<TD vAlign=top width=101>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/LinkedHashSet.html" target=_blank><FONT face=Verdana color=#000080 size=2>LinkedHashSet</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=94><FONT face=Verdana size=2>&nbsp;</FONT></TD>
<TD vAlign=top width=78>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Stack.html" target=_blank><FONT face=Verdana color=#000080 size=2>Stack</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=69><FONT face=Verdana size=2>&nbsp;</FONT></TD>
<TD vAlign=top width=143><FONT face=Verdana size=2>&nbsp;</FONT></TD>
<TD vAlign=top width=118>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Properties.html" target=_blank><FONT face=Verdana color=#000080 size=2>Properties</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=100><FONT face=Verdana size=2>&nbsp;</FONT></TD>
<TD vAlign=top width=112><FONT face=Verdana size=2>&nbsp;</FONT></TD>
<TD vAlign=top width=109>
<P align=center><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/LinkedHashMap.html" target=_blank><FONT face=Verdana color=#000080 size=2>LinkedHashMap</FONT></A><FONT face=Verdana size=2> </FONT></P></TD>
<TD vAlign=top width=77><FONT face=Verdana size=2>&nbsp;</FONT></TD></TR></TBODY></TABLE><BR><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;可能有的概念您还不是太了解，譬如什么叫“历史集合”，Hashtable、HashMap、TreeMap三者之间有什么区别和联系，怎样实现对一个特定集合的快速遍历、元素查找或者排序，没关系，我们在下面将逐一进行研究。 </FONT></P>
<P><FONT size=2><FONT face=Verdana><STRONG><U><FONT color=#cc0000><SPAN class=style4><FONT size=3>细节考虑：目标与效率</FONT><BR></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;有了JCF的层次还不够，重要的是对集合所容纳的对象的具体操作，以前我们学数据结构的时候可能老师总会让你计算一个算法的时间复杂度，可能你会对这个O(f(n))很不耐烦，但事实上算法效率是一个重要的因素。</FONT></FONT></P>
<UL><STRONG><FONT face=Verdana size=2>1. 侧重点：遍历 vs. 查找</FONT></STRONG></UL>
<P><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;对集合的有两个主要的应用：我需要知道集合有哪些元素；根据条件找到一个特定的元素。在算法上通常称为“遍历”和“查找”。不要以为我们生活中不常用哦！譬如CCTV的“幸运52”里面，李咏让参赛者报出一款PDA的准确价位，他会怎么做？“2000”“高了”“1000”“低了”“1500”“低了”……直到答对为止。可能有很多人都会选择这个策略，无论他是不是计算机专业出身的，也不知道他是不是了解“数据结构”和“折半查找”，更不用说他是不是知道还有比在无初始代价下O(log n)的时间复杂度更快的算法了，但我们经常会自然而然地用这样的方法，这和一个人的行业无关，除非这个人的rp超强，呵呵……<BR>&nbsp;&nbsp;&nbsp;&nbsp;又讲了一堆题外话了，遍历和修改似乎是一对矛盾，一个可以高效率插入删除元素的数据结构通常遍历的性能并不是最优。于是JCF在这里根据用户的目标实现了两种定制的数据结构：哈希表（包括HashSet和HashMap）和平衡二叉树（包括TreeSet和TreeMap）。由于可排序性是一种独特的要求，所以引入了SortedSet和SortedMap，它们分别是AbstractSet和AbstractMap的子接口，而TreeSet和TreeMap又分别是他们的一种实现。熟悉数据结构的人可能比较了解，哈希表在进行插入、删除、查找这样的操作是很快的，其时间复杂度是常数级O(1)；平衡二叉树虽然插入、删除操作比较麻烦（需要O(log n)的代价），但进行遍历和排序却很快。选择完全在于用户的侧重点，但由于类型转换的方便性，通常我们用哈希表构造一个集合以后，再把它转换成相应的树集进行遍历，以获得较好的效果。 <BR></FONT>
<TABLE cellSpacing=0 cellPadding=5 bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=2><FONT face=Verdana><SPAN class=style7><STRONG><FONT color=#990000>Set</FONT></STRONG></SPAN> set1 = <SPAN class=style12><STRONG><FONT color=#ff0000>new</FONT></STRONG></SPAN> <SPAN class=style7><STRONG><FONT color=#990000>HashSet</FONT></STRONG></SPAN>();<BR>set1.add(elem1);</FONT></FONT><FONT size=2><FONT face=Verdana><FONT color=#006600><SPAN class=style10>// 通过插入元素构造集合</SPAN><BR></FONT>set1.add(elem2);<BR>set1.add(elem3);<BR><SPAN class=style7><STRONG><FONT color=#990000>Set</FONT></STRONG></SPAN> set2 = <SPAN class=style12><STRONG><FONT color=#ff0000>new</FONT></STRONG></SPAN> <SPAN class=style7><STRONG><FONT color=#990000>TreeSet</FONT></STRONG></SPAN>(set);<BR><SPAN class=style7><STRONG><FONT color=#990000>Iterator</FONT></STRONG></SPAN> all = set2.iterator();<BR><SPAN class=style12><STRONG><FONT color=#ff0000>while</FONT></STRONG></SPAN> (all.hasNext())<BR>{</FONT></FONT><FONT size=2><FONT face=Verdana><FONT color=#006600><SPAN class=style10>// 遍历集合</SPAN><BR></FONT>all.next();<BR>...<BR>}</FONT></FONT></TD></TR></TBODY></TABLE><BR></P>
<UL><STRONG><FONT face=Verdana size=2>2. 历史实现 vs. 新实现</FONT></STRONG></UL><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;历史实现（Legacy Implementations）是JCF的一个术语，准确的意义不是很清楚，但大致可以认为在Java 2（JDK 1.2）出现以前的老版本中JCF的一个雏形框架。在Java 2以后，JCF才开始完善健壮起来，新实现中出现了一些新的类用于替代老版本中的成员，但由于种种原因，老版本中很多类都代表了传统数据结构的精髓部分，以及一些安全原因，所以仍然被我们使用着。<BR><BR></FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Enumeration.html" target=_blank><FONT face=Verdana color=#000080 size=2>Enumeration</FONT></A><FONT face=Verdana size=2> vs. </FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Iterator.html" target=_blank><FONT face=Verdana color=#000080 size=2>Iterator</FONT></A><BR><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;Enumeration是一个传统的集合遍历工具，在新的JCF中使用的是Iterator，Iterator同样具有遍历功能，还包含一个</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Iterator.html#remove()" target=_blank><FONT face=Verdana color=#000080 size=2>remove</FONT></A><FONT face=Verdana size=2>()方法来删除当前得到的元素。<BR><BR></FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Dictionary.html" target=_blank><FONT face=Verdana color=#000080 size=2>Dictionary</FONT></A><FONT face=Verdana size=2> vs. </FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" target=_blank><FONT face=Verdana color=#000080 size=2>Map</FONT></A><BR><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;Dictionary是一个现在已经被标记为deprecated的类，实现了老版本中的映射功能，现在已经完全被Map取代。它们的区别是：Dictionary中key和value不能为null，但Map却允许空的关键字和值，这一点直接影响到它们的后代：Hashtable和HashMap。<BR><BR></FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Vector.html" target=_blank><FONT face=Verdana color=#000080 size=2>Vector</FONT></A><FONT face=Verdana size=2> vs. </FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html" target=_blank><FONT face=Verdana color=#000080 size=2>ArrayList</FONT></A><BR><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;Vector和ArrayList是数组在JCF中的体现，还记得前面讲过的数组的缺点么？Vector和ArrayList就是一种可以动态增长的数组。Vector是历史实现，它和ArrayList的主要区别在于，Vector是同步集合（或者说是线程安全的），但ArrayList并不是同步的，由于同步需要花一定的代价，所以ArrayList看起来要比Vector的存取访问效率更高。关于同步我们下面还将要谈到。<BR><BR></FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Hashtable.html" target=_blank><FONT face=Verdana color=#000080 size=2>Hashtable</FONT></A><FONT face=Verdana size=2> vs. </FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/HashMap.html" target=_blank><FONT face=Verdana color=#000080 size=2>HashMap</FONT></A><BR><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;Hashtable是Dictionary的子类，属于历史实现，而HashMap是Map的子类，是新实现。它们的区别除了上面所说的key和value是否可以为空之外，也有同步的差别，Hashtable是同步的，但HashMap不是。HashMap的一个经典的例子就是JSP的内置对象session。不过不要因为Hashtable是“老前辈”而瞧不起它哦，它的一个著名的子类</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Properties.html" target=_blank><FONT face=Verdana color=#000080 size=2>Properties</FONT></A><FONT face=Verdana size=2>我们可是经常会用到的。 </FONT>
<UL><STRONG><FONT face=Verdana size=2>3. 同步 vs. 不同步</FONT></STRONG></UL><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;从上面的描述中我们似乎可以得出这么一个印象：历史实现好像都是同步的，但新实现中却没有。需要同步操作的理由是，可能存在多个线程对同一个集合进行操作的情况：譬如一个线程正在对某集合进行遍历，但与此同时，另一个线程又在对该集合进行插入或删除，那么第一个线程的遍历结果将是不可预测的，对于同步集合，它将会抛出一个</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ConcurrentModificationException.html" target=_blank><FONT face=Verdana color=#000080 size=2>ConcurrentModificationException</FONT></A><FONT face=Verdana size=2>异常，JCF把这种机制成为“fail-fast”。我们对比一下Vector和ArrayList的源代码就可以发现Vector的很多方法都是有synchronized关键字修饰的，但ArrayList没有。 </FONT>
<UL><STRONG><FONT face=Verdana size=2>4. 容易遗忘的工具：</FONT></STRONG><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html" target=_blank><STRONG><FONT face=Verdana color=#000080 size=2>Collections</FONT></STRONG></A><STRONG><FONT face=Verdana size=2>和</FONT></STRONG><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Arrays.html" target=_blank><STRONG><FONT face=Verdana color=#000080 size=2>Arrays</FONT></STRONG></A><FONT face=Verdana size=2> </FONT></UL><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;在图1中右下角落里有两个类叫做Collections（注意，不是Collection！）和Arrays，这是JCF里面功能强大的工具，但初学者往往会忽视。按JCF文档的说法，这两个类提供了封装器实现（Wrapper Implementations）、数据结构算法和数组相关的应用。<BR>&nbsp;&nbsp;&nbsp;&nbsp;想必大家不会忘记上面谈到的“折半查找”、“排序”等经典算法吧，Collections类提供了丰富的静态方法帮助我们轻松完成这些在数据结构课上烦人的工作：<BR><BR></FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#binarySearch(java.util.List,%20java.lang.Object)" target=_blank><FONT face=Verdana color=#000080 size=2>binarySearch</FONT></A><FONT face=Verdana size=2>：折半查找。<BR></FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#sort(java.util.List)" target=_blank><FONT face=Verdana color=#000080 size=2>sort</FONT></A><FONT face=Verdana size=2>：排序，这里是一种类似于快速排序的方法，效率仍然是O(n * log n)，但却是一种稳定的排序方法。<BR></FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#reverse(java.util.List)" target=_blank><FONT face=Verdana color=#000080 size=2>reverse</FONT></A><FONT face=Verdana size=2>：将线性表进行逆序操作，这个可是从前数据结构的经典考题哦！<BR></FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#rotate(java.util.List,%20int)" target=_blank><FONT face=Verdana color=#000080 size=2>rotate</FONT></A><FONT face=Verdana size=2>：以某个元素为轴心将线性表“旋转”——哇，这个功能太酷了！<BR></FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#swap(java.util.List,%20int,%20int)" target=_blank><FONT face=Verdana color=#000080 size=2>swap</FONT></A><FONT face=Verdana size=2>：交换一个线性表中两个元素的位置。<BR>……<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;Collections还有一个重要功能就是“封装器”（Wrapper），它提供了一些方法可以把一个集合转换成一个特殊的集合：<BR><BR>unmodifiableXXX：转换成只读集合，这里XXX代表六种基本集合接口：Collection、List、Map、Set、SortedMap和SortedSet。如果你对只读集合进行插入删除操作，将会抛出</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/UnsupportedOperationException.html" target=_blank><FONT face=Verdana color=#000080 size=2>UnsupportedOperationException</FONT></A><FONT face=Verdana size=2>异常。<BR>synchronizedXXX：转换成同步集合。<BR></FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#singleton(java.lang.Object)" target=_blank><FONT face=Verdana color=#000080 size=2>singleton</FONT></A><FONT face=Verdana size=2>：创建一个仅有一个元素的集合，这里singleton生成的是单元素Set，</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#singletonList(java.lang.Object)" target=_blank><FONT face=Verdana color=#000080 size=2>singletonList</FONT></A><FONT face=Verdana size=2>和</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#singletonMap(java.lang.Object,%20java.lang.Object)" target=_blank><FONT face=Verdana color=#000080 size=2>singletonMap</FONT></A><FONT face=Verdana size=2>分别生成单元素的List和Map。<BR>空集：由Collections的静态属性</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#EMPTY_SET" target=_blank><FONT face=Verdana color=#000080 size=2>EMPTY_SET</FONT></A><FONT face=Verdana size=2>、</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#EMPTY_LIST" target=_blank><FONT face=Verdana color=#000080 size=2>EMPTY_LIST</FONT></A><FONT face=Verdana size=2>和</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#EMPTY_MAP" target=_blank><FONT face=Verdana color=#000080 size=2>EMPTY_MAP</FONT></A><FONT face=Verdana size=2>表示。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;此外，我们知道把集合转换成对象数组可以用Collection的</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html#toArray()" target=_blank><FONT face=Verdana color=#000080 size=2>toArray</FONT></A><FONT face=Verdana size=2>()方法，我们也可以方便地把一个对象数组转换成一个线性表（可不要告诉我你是一个一个地add哦）：Arrays.</FONT><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Arrays.html#asList(java.lang.Object%5B%5D)" target=_blank><FONT face=Verdana color=#000080 size=2>asList</FONT></A><FONT face=Verdana size=2>()。 </FONT>
<UL><STRONG><FONT face=Verdana size=2>5. 泛型</FONT></STRONG></UL><FONT face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;目前我们了解的JCF的一个重要特征是：所有加入到集合当中的对象都将在表面上失去它们自己的特性，而看上去仅仅只是一个Object对象而已，除非你把它强制类型转换成它们原来的对象。这一点很自然，集合嘛，对象的容器，它容纳的是各种各样的对象，而不仅仅是某种特定类型的对象。J2SE 5.0出现以后，JCF开始引入泛型的特性，譬如我们经常碰到这样的应用，就是把集合转换成特定的数组，虽然Collection有toArray()的方法，但可惜的是，这个数组的所有元素都是Object类型的，我们通常的做法是用一个for循环把数组的每个元素都进行强制类型转换，虽然可行，但看上去很笨拙，如果有了泛型，我们就可以预先指定要得到的类型，然后一次toArray就可以得到我们期望的数组，里面的元素全部都是指定类型了。惭愧的是，我对5.0还不是太了解，具体可以参考</FONT><A href="http://java.sun.com/j2se/1.5.0/docs/guide/collections/index.html" target=_blank><FONT face=Verdana color=#000080 size=2>J2SE 5.0的JCF文档</FONT></A><FONT face=Verdana size=2>。 </FONT>
<P></P>
<P><FONT size=2><FONT face=Verdana><STRONG><U><FONT color=#cc0000><SPAN class=style4><FONT size=3>小结</FONT><BR></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;我这里走马观花一样把JCF的一些主要概念罗嗦了一下，Java的老手们可能比较厌烦，新手们可能更觉得像回顾了一下高中的数学课和大学的数据结构，呵呵。这只是一个小小的例子，可见基础知识对现实当中的应用还是蛮有指导意义的。大师们看数学，觉得那是很唯美很艺术的一样东西，西方一直都把数学区别于其它自然科学，而认为它更靠近于哲学，像我等这样整天还在为找工作烦得要死的俗人还是没法入道啊，sigh……<BR></FONT></FONT><STRONG><U><FONT color=#cc0000><BR><FONT face=Verdana>参考资料</FONT></FONT></U></STRONG></P>
<P class=style4><FONT face=Verdana size=2></FONT></P>
<UL>
<LI><FONT face=Verdana size=2>IBM developerWorks教程：</FONT><A href="http://www-900.ibm.com/developerWorks/cn/cnedu.nsf/java-onlinecourse-bytitle/4475AC55462B0B9448256B3B001F279A?OpenDocument" target=_blank><FONT face=Verdana color=#000080 size=2> Java 集合框架</FONT></A><FONT face=Verdana size=2> </FONT>
<LI><FONT face=Verdana size=2>J2SE Documentation: </FONT><A href="http://java.sun.com/j2se/1.4.2/docs/guide/collections/index.html" target=_blank><FONT face=Verdana color=#000080 size=2>The Collections Framework </FONT></A>
<LI><A href="http://java.sun.com/docs/books/tutorial/collections/index.html" target=_blank><FONT face=Verdana color=#000080 size=2>The Java Tutorial</FONT></A><FONT face=Verdana size=2> </FONT>
<LI><A href="http://java.sun.com/j2se/1.4.2/docs/guide/collections/reference.html" target=_blank><FONT face=Verdana color=#000080 size=2>JCF API</FONT></A></LI></UL></DIV><img src ="http://www.blogjava.net/kapok/aggbug/2801.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-03 23:06 <a href="http://www.blogjava.net/kapok/archive/2005/04/03/2801.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java调试的变迁：从System.out.println到log4j </title><link>http://www.blogjava.net/kapok/archive/2005/04/03/2800.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 03 Apr 2005 15:04:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/03/2800.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/2800.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/03/2800.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/2800.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/2800.html</trackback:ping><description><![CDATA[<DIV class=postText><FONT face=Verdana size=2><A href="http://www.blogjava.net/jungleford/archive/2005/04/02/2760.html">http://www.blogjava.net/jungleford/archive/2005/04/02/2760.html</A><BR>&nbsp;<STRONG><U><FONT color=#cc0000><SPAN class=style2><FONT size=3>jungleford如是说 <IMG title=log4j的logo height=55 alt=o_log4j.jpg src="http://www.blogjava.net/images/blogjava_net/jungleford/924/o_log4j.jpg" width=82 border=0></FONT><BR></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;用惯了<A href="http://msdn.microsoft.com/visualc/" target=_blank><FONT color=#000080>VC</FONT></A>的人刚接触<A href="http://java.sun.com/" target=_blank><FONT color=#000080>Java</FONT></A>大概很不习惯代码的调试，的确，在<A href="http://www.microsoft.com/" target=_blank><FONT color=#000080>M$</FONT></A>的大部分IDE都做得相当出色，包括像VJ++这样一直被Java程序员称为是“垃圾”的类库（记得以前在<A href="http://bbs.ustc.edu.cn/" target=_blank><FONT color=#000080>瀚海星云</FONT></A>的<A href="http://bbs.ustc.edu.cn/cgi-bin/bbsdoc?board=Java" target=_blank><FONT color=#000080>Java版</FONT></A>提有关VJ问题的人是有可能被封的，^_^），它的开发工具在调试上都相当容易。Java也有命令行方式的调试和IDE的调试，但现在的像<A href="http://www.borland.com/jbuilder/" target=_blank><FONT color=#000080>JB</FONT></A>这样的玩意又是个庞然大物，低配置的机器可能就是个奢望，不像VC那样。怎么办呢，高手们说，“我的jdb用得贼熟练”，那我会报以景仰的目光，像我这样的菜鸟基本上就没使过jdb，还是老老实实在代码里面System.out.println(...)。直到1996年一个叫做“欧洲安全电子市场”（E.U. <A href="http://www.semper.org/" target=_blank><FONT color=#000080>SEMPER</FONT></A>）的项目启动，“调试”不再是一件“体力活”，而是一种软件设计的艺术，这个项目组开发的日志管理接口后来成为<A href="http://www.apache.org/" target=_blank><FONT color=#000080>Apache</FONT></A> <A href="http://jakarta.apache.org/" target=_blank><FONT color=#000080>Jakarta</FONT></A>项目中的一员，它就是现在我们所熟悉的<A href="http://logging.apache.org/" target=_blank><FONT color=#000080>log4j</FONT></A>。下面的文字将概要介绍与Java日志记录相关的一些技术，目的不是让您放弃老土的System.out.println(...)，而是说，在Java的世界里可以有许多种选择，你今天觉得掌握了一件高级武器，明天可能就是“过时”的了，呵呵。 
<P><STRONG><U><FONT color=#cc0000><SPAN class=style2><FONT size=3>始祖：System.out.println(...)</FONT><BR></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;为什么还是要一再提到它？毕竟我们的习惯不是那么容易改变的，而且<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/System.html" target=_blank><FONT color=#000080>System</FONT></A>.<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/System.html#out" target=_blank><FONT color=#000080>out</FONT></A>（别忘了还有System.<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/System.html#err" target=_blank><FONT color=#000080>err</FONT></A>）是一个直接和控制台打交道的<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/io/PrintStream.html" target=_blank><FONT color=#000080>PrintStream</FONT></A>对象，是终端显示的基础，高级的Logger要在终端显示日志内容，就必然会用到这个。一个小规模的程序调试，恰当地使用System.out.<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/io/PrintStream.html#println(java.lang.Object)" target=_blank><FONT color=#000080>println</FONT></A>(...)我认为仍然是一种最方便最有效的方法，所以我们仍把它放在最开始，以示不能“数典忘祖” :)</P>
<P><STRONG><U><FONT color=#cc0000><SPAN class=style2><FONT size=3>不常用的关键字：assert</FONT><BR></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;assert对多数人来讲可能还比较陌生，它也是一个调试工具，好像是<A href="http://java.sun.com/j2se/1.4.2/" target=_blank><FONT color=#000080>J2SE 1.4</FONT></A>才加进来的东东，一种常见的用法是： 
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=-1><SPAN class=style3><STRONG><FONT color=#ff0000>assert</FONT></STRONG></SPAN> (布尔表达式);</FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;当表达式为true时没有任何反映，如果为false系统将会抛出一个<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/AssertionError.html" target=_blank><FONT color=#000080>AssertionError</FONT></A>。如果你要使用assert，在编译时必须加上“<STRONG>-source 1.4</STRONG>”的选项，在运行时则要加上“<STRONG>-ea</STRONG>”选项。<BR></FONT></P>
<P><STRONG><U><FONT color=#cc0000><SPAN class=style2><FONT size=3>后生可畏：Java Logging API一瞥</FONT><BR></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(...)对于较高要求的用户是远远不够的，它还不是一个日志系统，一个比较完善的日志系统应当有输出媒介、优先级、格式化、日志过滤、日志管理、参数配置等功能。伴随J2SE 1.4一起发布的Java日志包<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/package-summary.html" target=_blank><FONT color=#000080>java.util.logging</FONT></A>适时地满足了我们的初步需求，在程序中按一定格式显示和记录丰富的调试信息已经是一件相当easy的事情。</P>
<P><STRONG>1. 日志记录器：Logger</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html" target=_blank><FONT color=#000080>Logger</FONT></A>是一个直接面向用户的日志功能调用接口，从用户的角度上看，它完成大部分日志记录工作，通常你得到一个Logger对象，只需要使用一些简单方法，譬如info，warning，log，logp，logrb等就能完成任务，简单到和System.out.println(...)一样只用一条语句，但后台可能在向控制台，向文件，向数据库，甚至向网络同时输出该信息，而这个过程对用户是完全透明的。<BR>&nbsp;&nbsp;&nbsp;&nbsp;在使用Logger之前，首先需要通过<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#getLogger(java.lang.String)" target=_blank><FONT color=#000080>getLogger</FONT></A>()或<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#getAnonymousLogger()" target=_blank><FONT color=#000080>getAnonymousLogger</FONT></A>()静态方法得到一个Logger对象（想想看，这里是不是设计模式当中的“工厂方法”的一个实实在在的应用？可以参考一下Logger的源代码，你就明白<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/LogManager.html" target=_blank><FONT color=#000080>LogManager</FONT></A>是“工厂类”而Logger是“产品类”，凡事都要学以致用嘛，呵呵）。这里我们需要了解的是Logger的“名字空间”（<STRONG>namespace</STRONG>）的概念：通常我们调试时需要清楚地知道某个变量是出现在什么位置，精确到哪个类的哪个方法，namespace就是这么个用处。我们用getLogger()得到Logger时需要指定这个Logger的名字空间，通常是一个包名，譬如“com.jungleford.test”等，如果是指定了namespace，那么将在一个全局对象LogManager中注册这个namespace，Logger会基于namespace形成层次关系，譬如namespace为“com.jungleford”的Logger就是namespace为“com.jungleford.test”的Logger的父，后者调用<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#getParent()" target=_blank><FONT color=#000080>getParent</FONT></A>()方法将返回前者，如果当前没有namespace为“com.jungleford”的Logger，则查找namespace为“com”的Logger，要是按照这个链找不到就返回根Logger，其namespace为""，根Logger的父是null。从理论上说，这个namespace可以是任意的，通常我们是按所调试的对象来定，但如果你是使用getAnonymousLogger()方法产生的Logger，那它就没有namespace，这个“匿名Logger”的父是根Logger。<BR>&nbsp;&nbsp;&nbsp;&nbsp;得到一个Logger对象后就可以记录日志了，下面是一些常用的方法： <BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#finest(java.lang.String)" target=_blank><FONT color=#000080>finest</FONT></A>、<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#finer(java.lang.String)" target=_blank><FONT color=#000080>finer</FONT></A>、<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#fine(java.lang.String)" target=_blank><FONT color=#000080>fine</FONT></A>、<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#info(java.lang.String)" target=_blank><FONT color=#000080>info</FONT></A>、<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#config(java.lang.String)" target=_blank><FONT color=#000080>config</FONT></A>、<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#warning(java.lang.String)" target=_blank><FONT color=#000080>warning</FONT></A>、<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#severe(java.lang.String)" target=_blank><FONT color=#000080>severe</FONT></A>：简洁的方法，输出的日志为指定的级别。关于日志级别我们在后面将会详细谈到。<BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#log(java.util.logging.Level,%20java.lang.String)" target=_blank><FONT color=#000080>log</FONT></A>：不仅可以指定消息和级别，还可以带一些参数，甚至可以直接是一个LogRecord对象（这些参数是LogRecord对象的重要组成部分）。<BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#logp(java.util.logging.Level,%20java.lang.String,%20java.lang.String,%20java.lang.String)" target=_blank><FONT color=#000080>logp</FONT></A>：更加精细了，不但具有log方法的功能，还可以不使用当前的namespace，定义新的类名和方法名。<BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#entering(java.lang.String,%20java.lang.String)" target=_blank><FONT color=#000080>entering</FONT></A>、<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#exiting(java.lang.String,%20java.lang.String)" target=_blank><FONT color=#000080>exiting</FONT></A>：这两个方法在调试的时候特别管用，用来观察一个变量变化的情况，就如同我们在VC的调试状态下watch一个变量，然后按F10，呵呵。</FONT></TD></TR></TBODY></TABLE><BR></P>
<P><STRONG>2. 输出媒介控制：Handler</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;日志的意义在于它可以以多种形式输出，尤其是像文件这样可以长久保存的媒介，这是System.out.println(...)所无法办到的。Logging API的<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Handler.html" target=_blank><FONT color=#000080>Handler</FONT></A>类提供了一个处理日志记录（<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/LogRecord.html" target=_blank><FONT color=#000080>LogRecord</FONT></A>，它是对一条日志消息的封装对象）的接口，包括几个已实现的API： <BR><FONT size=-1></FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/ConsoleHandler.html" target=_blank><FONT color=#000080>ConsoleHandler</FONT></A>：向控制台输出。<BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/FileHandler.html" target=_blank><FONT color=#000080>FileHandler</FONT></A>：向文件输出。<BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/SocketHandler.html" target=_blank><FONT color=#000080>SocketHandler</FONT></A>：向网络输出。</FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;这三个输出控制器都是<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/StreamHandler.html" target=_blank><FONT color=#000080>StreamHandler</FONT></A>的子类，另外Handler还有一个MemoryHandler的子类，它有特殊的用处，我们在后面将会看到。在程序启动时默认的Handler是ConsoleHandler，不过这个是可以配置的，下面会谈到logging配置文件的问题。<BR>&nbsp;&nbsp;&nbsp;&nbsp;此外用户还可以定制自己输出控制器，继承Handler即可，通常只需要实现Handler中三个未定义的抽象方法： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Handler.html#publish(java.util.logging.LogRecord)" target=_blank><FONT color=#000080>publish</FONT></A>：主要方法，把日志记录写入你需要的媒介。<BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Handler.html#flush()" target=_blank><FONT color=#000080>flush</FONT></A>：清除缓冲区并保存数据。<BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Handler.html#close()" target=_blank><FONT color=#000080>close</FONT></A>：关闭控制器。</FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;通过重写以上三个方法我们可以很容易就实现一个把日志写入数据库的控制器。<BR></FONT></P>
<P><STRONG>3. 自定义输出格式：Formatter</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;除了可以指定输出媒介之外，我们可能还希望有多种输出格式，譬如可以是普通文本、HTML表格、XML等等，以满足不同的查看需求。Logging API中的<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Formatter.html" target=_blank><FONT color=#000080>Formatter</FONT></A>就是这样一个提供日志记录格式化方法接口的类。默认提供了两种Formatter：<BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/SimpleFormatter.html" target=_blank><FONT color=#000080>SimpleFormatter</FONT></A>：标准日志格式，就是我们通常在启动一些诸如<A href="http://jakarta.apache.org/tomcat/" target=_blank><FONT color=#000080>Tomcat</FONT></A>、<A href="http://www.jboss.org/" target=_blank><FONT color=#000080>JBoss</FONT></A>之类的服务器的时候经常能在控制台下看到的那种形式，就像这样： <BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=-1>2004-12-20 23:08:52 org.apache.coyote.http11.Http11Protocol init<BR>信息: Initializing Coyote HTTP/1.1 on http-8080<BR><BR>2004-12-20 23:08:56 org.apache.coyote.http11.Http11Protocol init<BR>信息: Initializing Coyote HTTP/1.1 on http-8443 </FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/XMLFormatter.html" target=_blank><FONT color=#000080>XMLFormatter</FONT></A>：XML形式的日志格式，你的Logger如果add了一个new XMLFormatter()，那么在控制台下就会看到下面这样的形式，不过更常用的是使用上面介绍的FileHandler输出到XML文件中： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=-1>&lt;?xml version="1.0" encoding="GBK" standalone="no"?&gt;<BR>&lt;!DOCTYPE log SYSTEM "logger.dtd"&gt;<BR>&lt;log&gt;<BR>&lt;record&gt;<BR>&nbsp;&nbsp;&lt;date&gt;2004-12-20T23:47:56&lt;/date&gt;<BR>&nbsp;&nbsp;&lt;millis&gt;1103557676224&lt;/millis&gt;<BR>&nbsp;&nbsp;&lt;sequence&gt;0&lt;/sequence&gt;<BR>&nbsp;&nbsp;&lt;logger&gt;Test&lt;/logger&gt;<BR>&nbsp;&nbsp;&lt;level&gt;WARNING&lt;/level&gt;<BR>&nbsp;&nbsp;&lt;class&gt;Test&lt;/class&gt;<BR>&nbsp;&nbsp;&lt;method&gt;main&lt;/method&gt;<BR>&nbsp;&nbsp;&lt;thread&gt;10&lt;/thread&gt;<BR>&nbsp;&nbsp;&lt;message&gt;warning message&lt;/message&gt;<BR>&lt;/record&gt;</FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;与Handler类似，我们也可以编写自己的格式化处理器，譬如API里没有将日志输出为我们可通过浏览器查看的HTML表格形式的Formatter，我们只需要重写3个方法： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Formatter.html#format(java.util.logging.LogRecord)" target=_blank><FONT color=#000080>format</FONT></A>：格式化LogRecord中包含的信息。<BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Formatter.html#getHead(java.util.logging.Handler)" target=_blank><FONT color=#000080>getHead</FONT></A>：输出信息的头部。<BR><BR><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Formatter.html#getTail(java.util.logging.Handler)" target=_blank><FONT color=#000080>getTail</FONT></A>：输出信息的尾部。</FONT></TD></TR></TBODY></TABLE><FONT size=-1></FONT><BR></P>
<P><FONT size=-1><STRONG>4. 定义日志级别：Level</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;大家可能都知道Windows的“事件查看器”，里面有三种事件类型：“信息”、“警告”、“错误”。这其实就是日志级别的一种描述。Java日志级别用<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html" target=_blank><FONT color=#000080>Level</FONT></A>类表示，一个日志级别对应的是一个整数值，范围和整型值的范围是一致的，该整数值愈大，说明警戒级别愈高。Level有9个内置的级别，分别是： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="40%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TH><FONT size=-1>类型</FONT></TH>
<TH><FONT size=-1>对应的整数</FONT></TH></TR>
<TR>
<TD><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#OFF" target=_blank>OFF</A></FONT></TD>
<TD><FONT size=-1>最大整数（<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Integer.html" target=_blank><FONT color=#000080>Integer</FONT></A>.<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Integer.html#MAX_VALUE" target=_blank><FONT color=#000080>MAX_VALUE</FONT></A>）</FONT></TD></TR>
<TR>
<TD><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#SEVERE" target=_blank>SEVERE</A></FONT></TD>
<TD><FONT size=-1>1000</FONT></TD></TR>
<TR>
<TD><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#WARNING" target=_blank>WARNING</A></FONT></TD>
<TD><FONT size=-1>900</FONT></TD></TR>
<TR>
<TD><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#INFO" target=_blank>INFO</A></FONT></TD>
<TD><FONT size=-1>800</FONT></TD></TR>
<TR>
<TD><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#CONFIG" target=_blank>CONFIG</A></FONT></TD>
<TD><FONT size=-1>700</FONT></TD></TR>
<TR>
<TD><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#FINE" target=_blank>FINE</A></FONT></TD>
<TD><FONT size=-1>500</FONT></TD></TR>
<TR>
<TD><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#FINER" target=_blank>FINER</A></FONT></TD>
<TD><FONT size=-1>400</FONT></TD></TR>
<TR>
<TD><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#FINEST" target=_blank>FINEST</A></FONT></TD>
<TD><FONT size=-1>300</FONT></TD></TR>
<TR>
<TD><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#ALL" target=_blank>ALL</A></FONT></TD>
<TD><FONT size=-1>最小整数（Integer.<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Integer.html#MIN_VALUE" target=_blank><FONT color=#000080>MIN_VALUE</FONT></A>）</FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;你也可以定义自己的日志级别，但要注意的是，不是直接创建Level的对象（因为它的构造函数是protected的），而是通过继承Level的方式，譬如： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><SPAN class=style3><FONT color=#ff0000 size=-1><STRONG>class</STRONG></FONT></SPAN><FONT size=-1> AlertLevel <SPAN class=style3><STRONG><FONT color=#ff0000>extends</FONT></STRONG></SPAN> java.util.logging.Level<BR>{<BR>&nbsp;&nbsp;<SPAN class=style3><STRONG><FONT color=#ff0000>public</FONT></STRONG></SPAN> AlertLevel()<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;super(<SPAN class=style4><FONT color=#ff00ff>"ALERT"</FONT></SPAN>, <SPAN class=style5><FONT color=#0000ff>950</FONT></SPAN>);<BR>&nbsp;&nbsp;}<BR>}<BR>...<BR><SPAN class=style6><STRONG><FONT color=#990000>Logger</FONT></STRONG></SPAN> logger = <SPAN class=style6><STRONG><FONT color=#990000>Logger</FONT></STRONG></SPAN>.getAnonymousLogger();<BR>logger.log(<SPAN class=style3><STRONG><FONT color=#ff0000>new</FONT></STRONG></SPAN> AlertLevel(), <SPAN class=style4><FONT color=#ff00ff>"A dangerous action!"</FONT></SPAN>);</FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;上面定义了一个高于WARNING但低于SEVERE的日志级别。<BR>&nbsp;&nbsp;&nbsp;&nbsp;于是可能有朋友会兴冲冲地用以下的语句来记录一个事件： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><SPAN class=style6><FONT color=#990000 size=-1><STRONG>Logger</STRONG></FONT></SPAN><FONT size=-1> logger = <SPAN class=style6><STRONG><FONT color=#990000>Logger</FONT></STRONG></SPAN>.getAnonymousLogger();<BR>logger.fine(<SPAN class=style4><FONT color=#ff00ff>"Everything seems ok."</FONT></SPAN>);<BR><SPAN class=style7><FONT color=#006600>//或者是<BR>//logger.log(Level.FINE, "Everything seems ok.");</FONT></SPAN></FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;但是一程序运行，奇怪了，怎么没有打印出任何消息呢？下一小节我们就来谈这个问题。</FONT></P>
<P><FONT size=-1><STRONG>5. 日志过滤器：Filter</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;所谓过滤器是控制哪些日志该输出哪些不该输出的一种组件。上面你写的那条日志没有能在控制台显示出来，是因为logging API预先设定的缺省级别是INFO，也就是说只有级别不低于INFO（即其整数值不小于800）的日志才会被输出，这个就是Filter的功能。所以我们可以看到SEVERE、WARNING、INFO以及上面我们定义的ALERT消息，但看不到FINE、FINER和FINEST消息。当然，你尽可以用Logger的<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#setLevel(java.util.logging.Level)" target=_blank><FONT color=#000080>setLevel</FONT></A>方法或者修改配置文件的方法（什么是配置文件，我们后面将会看到）来重新定义Logger输出的最低级别。<BR>&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Filter.html" target=_blank><FONT color=#000080>Filter</FONT></A>不仅仅可以按日志级别过滤，你也可以定义自己的Filter，实现其中的<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Filter.html#isLoggable(java.util.logging.LogRecord)" target=_blank><FONT color=#000080>isLoggable</FONT></A>方法，随便按照LogRecord携带的任何信息进行过滤，譬如（顺便复习一下匿名类，呵呵）： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><SPAN class=style6><FONT color=#990000 size=-1><STRONG>Logger</STRONG></FONT></SPAN><FONT size=-1> logger = <SPAN class=style6><STRONG><FONT color=#990000>Logger</FONT></STRONG></SPAN>.getAnonymousLogger();<BR>logger.setFilter(<SPAN class=style3><STRONG><FONT color=#ff0000>new</FONT></STRONG></SPAN> <SPAN class=style6><STRONG><FONT color=#990000>Filter</FONT></STRONG></SPAN>()<BR>{<BR>&nbsp;&nbsp;<SPAN class=style3><STRONG><FONT color=#ff0000>public</FONT></STRONG></SPAN> <SPAN class=style3><STRONG><FONT color=#ff0000>boolean</FONT></STRONG></SPAN> isLoggable(<SPAN class=style6><STRONG><FONT color=#990000>LogRecord</FONT></STRONG></SPAN> rec)<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#006600><SPAN class=style7>//从LogRecord里得到过滤信息</SPAN><BR></FONT>&nbsp;&nbsp;}<BR>});</FONT></TD></TR></TBODY></TABLE><STRONG><BR>6. 预定义参数<BR></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;LogManager是一个实现了Singleton模式的全局对象（由于是一个唯一的对象，LogManager需要是线程安全的），它管理着程序启动以后所有已注册（包层次）或匿名的Logger，以及相关配置信息。这里的配置信息通常是从<STRONG>&lt;JAVA_HOME&gt;\jre\lib\logging.properties</STRONG>文件得到的。logging.properties对于logging API来说是一个很重要的文件，它的内容一般是：<BR><FONT size=-1></FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=-1>
<P>############################################################<BR># Default Logging Configuration File<BR>#<BR># You can use a different file by specifying a filename<BR># with the java.util.logging.config.file system property. <BR># For example java -Djava.util.logging.config.file=myfile<BR>############################################################</P>
<P>############################################################<BR># Global properties<BR>############################################################</P>
<P># "handlers" specifies a comma separated list of log Handler <BR># classes. These handlers will be installed during VM startup.<BR># Note that these classes must be on the system classpath.<BR># By default we only configure a ConsoleHandler, which will only<BR># show messages at the INFO and above levels.<BR>handlers= java.util.logging.ConsoleHandler</P>
<P># To also add the FileHandler, use the following line instead.<BR>#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler</P>
<P># Default global logging level.<BR># This specifies which kinds of events are logged across<BR># all loggers. For any given facility this global level<BR># can be overriden by a facility specific level<BR># Note that the ConsoleHandler also has a separate level<BR># setting to limit messages printed to the console.<BR>.level= INFO</P>
<P>############################################################<BR># Handler specific properties.<BR># Describes specific configuration info for Handlers.<BR>############################################################</P>
<P># default file output is in user's home directory.<BR>java.util.logging.FileHandler.pattern = %h/java%u.log<BR>java.util.logging.FileHandler.limit = 50000<BR>java.util.logging.FileHandler.count = 1<BR>java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter</P>
<P># Limit the message that are printed on the console to INFO and above.<BR>java.util.logging.ConsoleHandler.level = INFO<BR>java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter<BR></P>
<P>############################################################<BR># Facility specific properties.<BR># Provides extra control for each logger.<BR>############################################################</P>
<P># For example, set the com.xyz.foo logger to only log SEVERE<BR># messages:<BR>com.xyz.foo.level = SEVERE</P></FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;你可以通过修改这个配置文件来改变运行时Logger的行为，譬如：.level定义的是上面所说的默认输出的最低日志级别；XXXHandler相关属性定义了各种输出媒介等等。<BR>&nbsp;&nbsp;&nbsp;&nbsp;这里比较有意思的是关于日志文件，也就是FileHandler，当然，你可以在程序中创建一个FileHandler，然后添加到logger中： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><SPAN class=style6><FONT color=#990000 size=-1><STRONG>FileHandler</STRONG></FONT></SPAN><FONT size=-1> fhd = <SPAN class=style3><STRONG><FONT color=#ff0000>new</FONT></STRONG></SPAN> <SPAN class=style6><STRONG><FONT color=#990000>FileHandler</FONT></STRONG></SPAN>(<SPAN class=style4><FONT color=#ff00ff>"%h/java%u.log"</FONT></SPAN>, <SPAN class=style5><FONT color=#0000ff>5000</FONT></SPAN>, <SPAN class=style5><FONT color=#0000ff>1</FONT></SPAN>, <SPAN class=style3><STRONG><FONT color=#ff0000>true</FONT></STRONG></SPAN>);<BR>fhd.setLevel(<SPAN class=style6><STRONG><FONT color=#990000>Level</FONT></STRONG></SPAN>.ALL);<BR>fhd.setFormatter(<SPAN class=style3><STRONG><FONT color=#ff0000>new</FONT></STRONG></SPAN> <SPAN class=style6><STRONG><FONT color=#990000>XMLFormatter</FONT></STRONG></SPAN>());<BR>logger.addHandler(fhd);</FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;这段代码等价于上面logging.properties中的文字段： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=-1>java.util.logging.FileHandler.pattern = %h/java%u.log<BR>java.util.logging.FileHandler.limit = 50000<BR>java.util.logging.FileHandler.count = 1<BR>java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter</FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;这里的pattern代表用转义字符定义的一个日志文件名： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="50%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TH><FONT size=-1>转义字符串</FONT></TH>
<TH><FONT size=-1>含义</FONT></TH></TR>
<TR>
<TD><FONT size=-1>%t</FONT></TD>
<TD><FONT size=-1>临时目录</FONT></TD></TR>
<TR>
<TD><FONT size=-1>%h</FONT></TD>
<TD><FONT size=-1>用户目录，即系统属性“user.home”对应的值</FONT></TD></TR>
<TR>
<TD><FONT size=-1>%g</FONT></TD>
<TD><FONT size=-1>一个随机生成的数字，可以重复</FONT></TD></TR>
<TR>
<TD><FONT size=-1>%u</FONT></TD>
<TD><FONT size=-1>一个随机生成的非重复数字</FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;以上面的“%h/java%u.log”为例，在Windows 2000下代表日志文件可能就是：C:\Documents and Settings\Administrator\java<STRONG>x</STRONG>.log。这里x代表一个不重复的数字，如果是第一次，那么就是java0.log；如果在该目录下已经存在了一个java0.log的文件，那么logger就产生一个java1.log的新的日志文件。<BR>&nbsp;&nbsp;&nbsp;&nbsp;当然，你可以在别的地方使用自己写的配置文件，不过在启动程序时候需要指定<STRONG>java.logging.config.file</STRONG>属性： </FONT>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT size=-1>java -Djava.logging.config.file=...</FONT></TD></TR></TBODY></TABLE><BR><STRONG>7. 资源与本地化</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;Logger里还有个方法叫<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#logrb(java.util.logging.Level,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String)" target=_blank><FONT color=#000080>logrb</FONT></A>，可能初学者不太会用到。如果你安装的JDK是国际版的，那么你将会看到在中文Windows平台下日志输出的INFO、WARNING显示的是“信息”、“警告”等中文字样。因为logrb是一个和Java i18n/l10n相关的方法，你可以定义自己的“资源包”（Resource Bundle），然后在logrb方法中指定相应的资源名称，那么在输出日志中你就能看到用自己定义的本地语言、时间等显示的信息。如果你对i18n/l10n感兴趣，可以参考<A href="http://java.sun.com/j2se/1.4.2/docs/guide/intl/index.html" target=_blank><FONT color=#000080>Java Localization文档</FONT></A>。<BR><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;了解以上组件后，我们回顾一个完整的日志处理的工作过程：<BR>&nbsp;&nbsp;&nbsp;&nbsp;程序启动日志服务，创建Logger对象，LogManager按照namespace的层次结构组织Logger，在同一个namespace里子Logger将继承父Logger的属性；同时，LogManager从logging.properties中读取相应的属性对Logger进行初始化，如果在程序中设置了属性则使用新的配置。当应用程序产生一条日志，Logger将创建一个LogRecord对象，该对象封装了一条日志的全部信息。Logger需要根据当前设置的Filter来判断这条日志是否需要输出，并将有用的日志传给相应的Handler处理，而Handler根据当前设置的Formatter和Resource Bundle将日志消息转换成一定的显示格式，然后输出到预定的媒介（控制台、文件等）中去。整个过程大致如图1所示： <BR></P>
<P align=center><IMG title=日志处理流程 height=151 alt=o_logging1.gif src="http://www.blogjava.net/images/blogjava_net/jungleford/924/o_logging1.gif" width=632 border=0></P>
<P align=center>图1<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;前面我们在介绍Handler的时候提到过一个特殊的类叫<A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/MemoryHandler.html" target=_blank><FONT color=#000080>MemoryHandler</FONT></A>，这里我们要了解一下“Handler链”的概念，日志在输出之前可能经过多个Handler的处理，MemoryHandler在这种情况下就是一个中间角色，它维持一个内存中的日志缓冲区，当日志没有填满缓冲区时就将全部日志送到下一个Handler，否则新进来的日志将会覆盖最老的那些日志，因此，使用MemoryHandler可以维护一定容量的日志，另外，MemoryHandler也可以不需要使用Formatter来进行格式化，从而具有较高的效率。一个使用Handler链的例子如图2所示： </P>
<P></P>
<CENTER><IMG title="一个 Handler 链" height=149 alt=o_logging2.gif src="http://www.blogjava.net/images/blogjava_net/jungleford/924/o_logging2.gif" width=700 border=0><BR>图2</CENTER>
<P></P>
<P><STRONG><U><FONT color=#cc0000><SPAN class=style2><FONT size=3>青出于蓝：Apache Jakarta log4j日志工具包</FONT><BR></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;应付日常的日志需求，J2SE的Logging API可以说已经做得相当出色了，但追求完美的开发人员可能需要可扩展性更好的专业日志处理工具，log4j正是当前比较流行的一个工具包，它提供更多的输出媒介、输出格式和配置选择，你会发现原来在J2SE里一些仍需要自己手工构建的功能在log4j当中都已经为你实现了。关于log4j我可能谈得不会太多，可以看看文后所附的“<A href="http://www.smth.org/pc/pcmanage.php?userid=jungleford&amp;act=edit&amp;nid=105614#ref"><FONT color=#000080>参考资料</FONT></A>”，网上也有很详细的介绍，我在这里做的是一个对比，因为log4j和J2SE 1.4 Logging API的用法是很相似的，一些名称不同的组件你会发现他们所处的地位其实是一样的： <BR>
<TABLE cellSpacing=0 cellPadding=0 width="60%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TH>&nbsp;</TH>
<TH><FONT size=-1>J2SE 1.4中的类</FONT></TH>
<TH><FONT size=-1>log4j中的类</FONT></TH></TR>
<TR>
<TH><FONT size=-1>日志记录器</FONT></TH>
<TD><FONT size=-1>Logger</FONT></TD>
<TD><FONT color=#000080 size=-1><A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/Logger.html" target=_blank>Logger</A></FONT></TD></TR>
<TR>
<TH><FONT size=-1>日志管理器</FONT></TH>
<TD><FONT size=-1>LogManager</FONT></TD>
<TD><FONT color=#000080 size=-1><A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/LogManager.html" target=_blank>LogManager</A></FONT></TD></TR>
<TR>
<TH><FONT size=-1>日志对象</FONT></TH>
<TD><FONT size=-1>LogRecord</FONT></TD>
<TD><FONT color=#000080 size=-1><A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/spi/LoggingEvent.html" target=_blank>LoggingEvent</A></FONT></TD></TR>
<TR>
<TH><FONT size=-1>输出媒介控制</FONT></TH>
<TD><FONT size=-1>Handler</FONT></TD>
<TD><FONT color=#000080 size=-1><A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/Appender.html" target=_blank>Appender</A></FONT></TD></TR>
<TR>
<TH><FONT size=-1>格式化</FONT></TH>
<TD><FONT size=-1>Formatter</FONT></TD>
<TD><FONT color=#000080 size=-1><A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/Layout.html" target=_blank>Layout</A></FONT></TD></TR>
<TR>
<TH><FONT size=-1>级别</FONT></TH>
<TD><FONT size=-1>Level</FONT></TD>
<TD><FONT color=#000080 size=-1><A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/Layout.html" target=_blank>Level</A></FONT></TD></TR>
<TR>
<TH><FONT size=-1>过滤器</FONT></TH>
<TD><FONT size=-1>Filter</FONT></TD>
<TD><FONT color=#000080 size=-1><A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/spi/Filter.html" target=_blank>Filter</A></FONT></TD></TR></TBODY></TABLE><FONT size=-1><BR>&nbsp;&nbsp;&nbsp;&nbsp;log4j可以做到更精细更完善的控制，譬如J2SE里没有现成向数据库里写日志的方法，但log4j却有<A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/jdbc/JDBCAppender.html" target=_blank><FONT color=#000080>JDBCAppender</FONT></A>，它甚至还能向GUI图形界面（<A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/lf5/LF5Appender.html" target=_blank><FONT color=#000080>LF5Appender</FONT></A>，一种以JTree方式显示的层次结构）、Windows NT事件查看器（<A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/nt/NTEventLogAppender.html" target=_blank><FONT color=#000080>NTEventLogAppender</FONT></A>）、UNIX的syslogd服务（<A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/net/SyslogAppender.html" target=_blank><FONT color=#000080>SyslogAppender</FONT></A>）、电子邮箱（<A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/net/SMTPAppender.html" target=_blank><FONT color=#000080>SMTPAppender</FONT></A>）、Telnet终端（<A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/net/TelnetAppender.html" target=_blank><FONT color=#000080>TelnetAppender</FONT></A>）、JMS消息（<A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/net/JMSAppender.html" target=_blank><FONT color=#000080>JMSAppender</FONT></A>）输出日志，牛吧；J2SE里默认只能用%JAVA_HOME%\jre\lib\logging.properties做配置文件，但log4j却可以在代码中设置其它路径下的properties文件或XML格式的配置文件。log4j的其它方面同样很丰富，总之，log4j的最大的特点就是“灵活”，无论是Appender、Layout还是Configurator，你可以把日志轻松地弄成几乎任何你想要的形式。</FONT></P>
<P><STRONG><U><FONT color=#cc0000><SPAN class=style2><FONT size=3>框架与标准：JSR议案</FONT><BR></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;从时间顺序上讲，log4j要比J2SE Logging API来得早，很多概念都是log4j先有的，但成为一个标准，则是在<A href="http://www.jcp.org/en/jsr/detail?id=47" target=_blank><FONT color=#000080>JSR 47</FONT></A>的形成。可能有人还不太了解<A href="http://www.jcp.org/en/jsr/overview" target=_blank><FONT color=#000080>JSR</FONT></A>，这还要谈到<A href="http://www.jcp.org/en/home/index" target=_blank><FONT color=#000080>JCP</FONT></A>，即“Java Community Process”，它是一个于1998年成立的旨在为Java技术制定民间标准的开放组织，你可以通过<A href="http://www.jcp.org/en/participation/membership" target=_blank><FONT color=#000080>http://www.jcp.org/en/participation/membership</FONT></A>申请成为它的付费或免费会员，JCP的主要工作就是制定和发布JSR（Java Specification Requests），JSR对于Java的意义就相当于RFC对于网络技术的意义，由于JCP会员们的集思广益，使得JSR成为Java界的一个重要标准。JSR 47即“Logging API Specification”，制定了调试和日志框架，J2SE Logging API正是该框架的一个实现。由于种种原因，在JSR 47出来以前，log4j就已经成为一项成熟的技术，使得log4j在选择上占据了一定的优势，但不能因此就说JSR 47是过时的规范，标准总是在发展的嘛！</P>
<P><STRONG><U><FONT color=#cc0000><SPAN class=style2><FONT size=3>并不是全部：其它日志处理工具</FONT><BR></SPAN><BR></FONT></U></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;除了J2SE Logging API和log4j，日志处理方面还有别的技术：Jakarta的<A href="http://jakarta.apache.org/commons/" target=_blank><FONT color=#000080>commons</FONT></A>组件项目中的<A href="http://jakarta.apache.org/commons/logging/" target=_blank><FONT color=#000080>JCL</FONT></A>（Jakarta Commons Logging）是一个不错的选择，它有点类似于GSS-API（通用安全服务接口）中的思想，其日志服务机制是可以替换的，也就是说既可以用J2SE Logging API也可以用log4j，但JCL对开发人员提供一致的接口，这一点相当重要，组件可重用正是Jakarta Commons项目追求的一个目标；<A href="http://www.ibm.com/" target=_blank><FONT color=#000080>IBM</FONT></A>的<A href="http://www.alphaworks.ibm.com/tech/loggingtoolkit4j" target=_blank><FONT color=#000080>JLog</FONT></A>也是在J2SE Logging API之前推出的一个工具包，但JLog是一个商业产品。<BR>&nbsp;&nbsp;&nbsp;&nbsp;至于日志API的应用那可就多了，现在哪个大一点的工具或平台不用到日志模块呢？Tomcat、JBoss……<BR></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;说了这么多，我们无非需要知道的一件事就是，“调试”也是一门学问。在我们一个劲地用System.out.println(...)而且用得很爽的时候，也应该想想看，如何让这样一条菜鸟语句也能变得人性化和丰富多彩。</P>
<P><SPAN class=style2><A name=ref></A><STRONG><U><FONT color=#cc0000 size=3>参考资料</FONT></U></STRONG></SPAN> </P>
<UL>
<LI><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/guide/util/logging/index.html" target=_blank>Java Logging Documentation</A></FONT> 
<LI><FONT color=#000080 size=-1><A href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/package-summary.html" target=_blank>Java Logging APIs</A></FONT> 
<LI><FONT size=-1>J2SE进阶, by <A href="http://www.javaresearch.org/" target=_blank><EM><FONT color=#000080>www.javaresearch.org</FONT></EM></A></FONT> 
<LI><FONT size=-1><A href="http://logging.apache.org/log4j/docs/manual.html" target=_blank><FONT color=#000080>Short introduction to log4j</FONT></A>, by <EM>Ceki Gülcü</EM></FONT> 
<LI><FONT color=#000080 size=-1><A href="http://logging.apache.org/log4j/docs/api/index.html" target=_blank>log4j APIs</A></FONT> 
<LI><FONT color=#000080 size=-1><A href="http://logging.apache.org/log4j/docs/faq.html" target=_blank>FAQ about log4j</A></FONT></LI></UL></FONT></DIV><img src ="http://www.blogjava.net/kapok/aggbug/2800.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-03 23:04 <a href="http://www.blogjava.net/kapok/archive/2005/04/03/2800.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java Tree</title><link>http://www.blogjava.net/kapok/archive/2005/03/31/2657.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 31 Mar 2005 06:08:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/03/31/2657.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/2657.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/03/31/2657.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/2657.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/2657.html</trackback:ping><description><![CDATA[<TABLE class=tf width="98%" align=center border=0>
<TBODY>
<TR>
<TD class=bw width="100%"><FONT class=htd id=font_word style="FONT-SIZE: 14px; FONT-FAMILY: 宋体, Verdana, Arial, Helvetica, sans-serif"><A href="http://www.matrix.org.cn/article/335.html">http://www.matrix.org.cn/article/335.html</A><BR><BR>原创 作者-- joinme<BR><BR>Tree型结构，Tree图不管在哪个领域都是一个比较吃香的东东。在自己的网站或项目中有一个tree型图或tree型结构的权限（或角色，和数据库设计相关），总能有那么一点点成就感。^_^<BR>&nbsp;&nbsp;&nbsp;&nbsp;这篇文章就把我自己在这方面应用的实践整理一下拿出来。为您详细介绍了在Tree有关方面应用的完整实践。<BR>实例如下：<A href="http://www.matrix.org.cn/upload/forum/2003716130558.jpg" target=_blank><IMG height=375 alt="" src="http://www.matrix.org.cn/upload/forum/2003716130558.jpg" width=500 onload="javascript:if(this.width>screen.width-215)this.width=screen.width-215" align=center border=0 dypop="按此在新窗口浏览图片"></A><BR>左边的tree图的实现方式是通过javascript+jsp实现。如果是为了实现逻辑上的tree结构的维护，采用的是xml解决方案。<BR>现在这里说一下,javascript+jsp的表现层的实现。这里的javascript并不是唯一的选择，可以在网上下到类似的javascript(毕竟是共享的嘛。:)&nbsp;).点<A href="http://www.matrix.org.cn/upload/article/a2003716131846.rar" target=_blank>这里</A>可以下载相关代码.<BR>其中的test.htm是javascript例子代码:<BR>&lt;script&nbsp;type="text/javascript"&nbsp;src="include/dtree.js"&gt;&lt;/script&gt;<BR>&lt;div&nbsp;class="dtree"&gt;<BR>&nbsp;&lt;script&nbsp;type="text/javascript"&gt;<BR>&nbsp;&nbsp;&lt;!--<BR>&nbsp;&nbsp;d&nbsp;=&nbsp;new&nbsp;dTree('d');<BR>&nbsp;&nbsp;d.add(0,-1,'文章目录');<BR>d.add(3,0,'技术与方案','admin.php?module=article&amp;action=catelist&amp;cid=3','','_blank');<BR>d.add(5,0,'新闻中心','admin.php?module=article&amp;action=catelist&amp;cid=5');<BR>d.add(34,5,'新闻中心','admin.php?module=article&amp;action=catelist&amp;cid=5');<BR>d.add(35,34,'新闻中心','admin.php?module=article&amp;action=catelist&amp;cid=5');<BR>d.add(36,35,'新闻中心','admin.php?module=article&amp;action=catelist&amp;cid=5');<BR>d.add(37,36,'新闻中心','admin.php?module=article&amp;action=catelist&amp;cid=5');<BR>d.add(38,37,'新闻中心','admin.php?module=article&amp;action=catelist&amp;cid=5');<BR>d.add(7,6,'网络知识','admin.php?module=article&amp;action=catelist&amp;cid=7');<BR>d.add(8,6,'配置手册','admin.php?module=article&amp;action=catelist&amp;cid=8');<BR>d.add(9,6,'网络安全','admin.php?module=article&amp;action=catelist&amp;cid=9');<BR>d.add(10,3,'解决方案','admin.php?module=article&amp;action=catelist&amp;cid=10');<BR>d.add(11,10,'局域网','admin.php?module=article&amp;action=catelist&amp;cid=11');<BR>d.add(12,10,'宽带接入','admin.php?module=article&amp;action=catelist&amp;cid=12');<BR>d.add(6,3,'网络技术','admin.php?module=article&amp;action=catelist&amp;cid=6');<BR>document．write(d);<BR><BR>--&gt;<BR>&lt;/script&gt;<BR>&lt;/div&gt;<BR>"d.add(3,0,'技术与方案','admin.php?module=article&amp;action=catelist&amp;cid=3','','_blank');<BR>"中的"3"是当前节点，"0"是父节点,"技术与方案"是tree上显示的节点名,"admin.php?......"指向的url."_blank"是指向的窗口。<BR>从上可以看出，这个javascript已经把tree图实现的大部分工作已经做好了。只要我们传入对应的数据就可以了。<BR>首先是数据库结构。从上可以看出，有节点名，节点id,父节点id,节点url,节点target,还可以有个节点状态（比如是激活或休眠，很常用用一种用法），当然还可以有其它属性，那就要根据项目的需要了。其实只要抓住一点，有唯一节点id和唯一父节点id就可以了（面向对象）就可以了。<BR>有了数据库结构，然后要实现tree图就是根据一定的限制（比如权限，最后对应为tree结构的子集（当然也必须是完整的tree结构））取出符合条件并且完整的tree结构的数据（我采用的解决方法是把权限对应的节点的上溯父节点都取出来保证tree结构的完整性,具体可以看<A href="http://www.matrix.org.cn/article_view.asp?id=246" target=_blank>java&amp;xml心得(三)</A>）.<BR>假设我们已经完成了上述的工作，那么要实现tree图的代码应该如下:<BR>.........<BR>&lt;html&gt;<BR>&lt;head&gt;<BR>&lt;title&gt;Untitled&nbsp;document．lt;/title&gt;<BR>&lt;meta&nbsp;http-equiv="Content-Type"&nbsp;content="text/html;&nbsp;charset=gb2312"&gt;<BR>&lt;link&nbsp;rel="stylesheet"&nbsp;href="images/cj_styles.css"&nbsp;type="text/css"&gt;<BR>&lt;script&nbsp;type="text/javascript"&nbsp;src="include/dtree.js"&gt;&lt;/script&gt;<BR>&lt;/head&gt;<BR><BR>&lt;body&nbsp;bgcolor="#FFFFFF"&nbsp;text="#000000"&nbsp;leftmargin="2"&nbsp;topmargin="0"&gt;<BR>&lt;div&nbsp;class="dtree"&gt;<BR><BR>&lt;script&nbsp;type="text/javascript"&gt;<BR>&nbsp;&nbsp;&lt;!--<BR><BR>&nbsp;&lt;%<BR>&nbsp;if(!finds){<BR>&nbsp;&nbsp;out.println("document．write('没有权限');");<BR>&nbsp;}else{<BR>&nbsp;Result&nbsp;rsShow=ResultFactory.getResult("Vector");<BR>&nbsp;rsShow.setConnType("jdbc");<BR>&nbsp;rsShow.setSql(tempsqls);<BR>&nbsp;rsShow.Create();<BR>&nbsp;out.println("d=new&nbsp;dTree('d');");<BR>&nbsp;out.println("d.add(0,-1,'权限列表');");<BR>&nbsp;String&nbsp;t_tree_id,t_tree_id_p,t_tree_name,t_tree_address,t_tree_target;<BR>&nbsp;&nbsp;while(rsShow.next()){<BR>&nbsp;&nbsp;&nbsp;t_tree_id=rsShow.getString("tree_id");<BR>&nbsp;&nbsp;&nbsp;t_tree_id_p=rsShow.getString("tree_id_p");<BR>&nbsp;&nbsp;&nbsp;t_tree_name=rsShow.getString("tree_name");<BR>&nbsp;&nbsp;&nbsp;t_tree_address=rsShow.getString("tree_address");<BR>&nbsp;&nbsp;&nbsp;t_tree_address+="?tree_id="+rsShow.getString("tree_id")+"&amp;tree_name="+rsShow.getString("tree_name");&nbsp;//t_tree_address+="edit/list.jsp?tree_id="+rsShow.getString("tree_id")+"&amp;tree_name="+rsShow.getString("tree_name");<BR>&nbsp;&nbsp;&nbsp;t_tree_target=rsShow.getString("tree_target").length()&gt;0?rsShow.getString("tree_target"):"mainFrame";<BR>&nbsp;&nbsp;&nbsp;out.println("d.add("+t_tree_id+","+t_tree_id_p+",'"+t_tree_name+"','"+t_tree_address+"','','"+t_tree_target+"');");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;out.println("d.add(-2,0,'退出系统','/zdtadmin/content/login.jsp','','_top');");<BR>&nbsp;&nbsp;out.println("document．write(d);");<BR>&nbsp;rsShow.close();<BR>&nbsp;}<BR>&nbsp;%&gt;<BR><BR>&nbsp;&nbsp;--&gt;<BR>&nbsp;&lt;/script&gt;<BR><BR><BR>&lt;/div&gt;<BR><BR><BR>&lt;/body&gt;<BR>&lt;/html&gt;<BR>这样就实现了动态tree图。<BR>上面的这种实现是用jsp+javascript实现。也可以用xml+xsl来实现。不是必要，只是可以让新手也了解一下关于xml的应用。:)<BR>比如我们已经成功把数据库的数据导出成xml格式。代码如下：<BR>&lt;?xml&nbsp;version="1.0"&nbsp;?&gt;<BR>&lt;?xml:stylesheet&nbsp;type="text/xsl"&nbsp;href="ttx2.xsl"&nbsp;?&gt;<BR>&lt;table&nbsp;name="cm_tree"&gt;<BR>&lt;col&nbsp;id="1"&nbsp;label="闆嗗洟鏂伴椈"&nbsp;parentid="29"&nbsp;target="_blank"&nbsp;url="/zdtadmin/default.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="2"&nbsp;label="闆嗗洟瑕侀椈"&nbsp;parentid="1"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=2"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="3"&nbsp;label="缁煎悎淇℃伅"&nbsp;parentid="1"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=3"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="4"&nbsp;label="鏀跨瓥娉曡"&nbsp;parentid="29"&nbsp;target="_blank"&nbsp;url="/zdtadmin/default.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="5"&nbsp;label="娉曞緥娉曡"&nbsp;parentid="4"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=5"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="7"&nbsp;label="鏀跨瓥鐮旂┒"&nbsp;parentid="4"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=7"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="8"&nbsp;label="鐢熶骇缁忚惀"&nbsp;parentid="29"&nbsp;target="_blank"&nbsp;url="/zdtadmin/default.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="9"&nbsp;label="瀹夊叏鐢熶骇"&nbsp;parentid="8"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=9"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="10"&nbsp;label="缁忚惀鍔ㄦ€?&nbsp;parentid="8"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=10"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="11"&nbsp;label="宸ョ▼寤鸿"&nbsp;parentid="29"&nbsp;target="_blank"&nbsp;url="/zdtadmin/default.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="12"&nbsp;label="鐏數宸ョ▼"&nbsp;parentid="11"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=12"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="13"&nbsp;label="姘寸數宸ョ▼"&nbsp;parentid="11"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=13"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="14"&nbsp;label="鏍哥數宸ョ▼"&nbsp;parentid="11"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=14"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="17"&nbsp;label="鍥介檯鍚堜綔"&nbsp;parentid="29"&nbsp;target="_blank"&nbsp;url="/zdtadmin/default.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="19"&nbsp;label="鍚堜綔鍔ㄦ€?&nbsp;parentid="17"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=19"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="20"&nbsp;label="浼佷笟鏂囧寲"&nbsp;parentid="29"&nbsp;target="_blank"&nbsp;url="/zdtadmin/default.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="22"&nbsp;label="绮剧鏂囨槑"&nbsp;parentid="20"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=22"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="24"&nbsp;label="鍏朵粬淇℃伅"&nbsp;parentid="29"&nbsp;target="_blank"&nbsp;url="/zdtadmin/default.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="25"&nbsp;label="澶╂皵棰勬姤"&nbsp;parentid="24"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=25"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="26"&nbsp;label="浼佷笟鏂囧寲鍔ㄦ€?&nbsp;parentid="20"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=26"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="27"&nbsp;label="鏂囧寲娲诲姩"&nbsp;parentid="20"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=27"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="29"&nbsp;label="鍐呭绠＄悊"&nbsp;parentid="0"&nbsp;target="_blank"&nbsp;url="/zdtadmin/default.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="31"&nbsp;label="鏍忕洰缁存姢"&nbsp;parentid="0"&nbsp;target="_blank"&nbsp;url="/zdtadmin/treeadmin/tree_edit.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="32"&nbsp;label="瑙掕壊缁存姢"&nbsp;parentid="0"&nbsp;target="_blank"&nbsp;url="/zdtadmin/default.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="41"&nbsp;label="澶氱浜т笟"&nbsp;parentid="8"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=41"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="42"&nbsp;label="涓婂競鍏徃"&nbsp;parentid="8"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=42"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="44"&nbsp;label="娣诲姞瑙掕壊"&nbsp;parentid="32"&nbsp;target="_blank"&nbsp;url="/zdtadmin/peradmin/role_edit.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="45"&nbsp;label="瑙掕壊-鏍忕洰"&nbsp;parentid="32"&nbsp;target="_blank"&nbsp;url="/zdtadmin/peradmin/role_tree.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="46"&nbsp;label="瑙掕壊-鐢ㄦ埛"&nbsp;parentid="32"&nbsp;target="_blank"&nbsp;url="/zdtadmin/peradmin/role_user.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="47"&nbsp;label="鏂囦欢涓嬭浇"&nbsp;parentid="24"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/down_list.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="48"&nbsp;label="鐢ㄦ埛绠＄悊"&nbsp;parentid="0"&nbsp;target="_blank"&nbsp;url="/zdtadmin/useradmin/user_list.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="51"&nbsp;label="涓撴爮"&nbsp;parentid="29"&nbsp;target="_blank"&nbsp;url="/zdtadmin/default.jsp"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="52"&nbsp;label="鐢靛姏绉戞櫘"&nbsp;parentid="51"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=52"&gt;&lt;/col&gt;<BR>&lt;col&nbsp;id="53"&nbsp;label="鍩虹绠＄悊骞?&nbsp;parentid="51"&nbsp;target="_blank"&nbsp;url="/zdtadmin/newsadmin/list.jsp?tree_id=53"&gt;&lt;/col&gt;&lt;/table&gt;<BR>其中乱码是因为用到了utf8编码。就是节点名。在ie中可以正常显示。<BR>然后我们写一个xsl来表示它，把它转成一个html来显示。<BR>"&lt;?xml:stylesheet&nbsp;type="text/xsl"&nbsp;href="ttx2.xsl"&nbsp;?&gt;"指定了我们要用<BR>ttx2.xsl来表示它。代码如下：<BR><BR>&lt;?xml&nbsp;version='1.0'&nbsp;?&gt;<BR>&lt;xsl:stylesheet&nbsp;version='1.0'&nbsp;xmlns:xsl="<IMG src="http://www.matrix.org.cn/article/images/small/url.gif" align=absMiddle><A href='http://www.w3.org/TR/WD-xsl"' target=_blank>http://www.w3.org/TR/WD-xsl"</A>;&nbsp;&gt;<BR>&lt;xsl:template&nbsp;match="/"&gt;<BR>&lt;html&gt;<BR>&lt;head&gt;<BR>&lt;title&gt;Tree&nbsp;test&lt;/title&gt;<BR>&lt;/head&gt;<BR>&lt;body&gt;<BR>&lt;script&nbsp;type="text/javascript"&nbsp;src="include/dtree.js"&gt;&lt;/script&gt;<BR>&lt;script&nbsp;type="text/javascript"&gt;<BR>d=new&nbsp;dTree('d');<BR>d.add(0,-1,"list");<BR>&lt;xsl:apply-templates&nbsp;select="/table"/&gt;<BR>document．write(d);<BR>&lt;/script&gt;<BR>&lt;/body&gt;<BR>&lt;/html&gt;<BR>&lt;/xsl:template&gt;<BR>&lt;xsl:template&nbsp;match="/table"&gt;<BR>&lt;xsl:for-each&nbsp;select="col"&gt;<BR>d.add(&lt;xsl:value-of&nbsp;select="@id"/&gt;,&lt;xsl:value-of&nbsp;select="@parentid"/&gt;,'&lt;xsl:value-of&nbsp;select="@label"/&gt;','&lt;xsl:value-of&nbsp;select="@url"/&gt;','&lt;xsl:value-of&nbsp;select="@label"/&gt;','&lt;xsl:value-of&nbsp;select="@target"/&gt;');<BR>&lt;/xsl:for-each&gt;<BR>&lt;/xsl:template&gt;<BR>&lt;/xsl:stylesheet&gt;<BR>这些例子在打包文件里可以找到。我在IE&nbsp;6.0下测试通过。<BR>我在xsl方面也只是了解皮毛。所以用到的也只是很基础的东西。希望有人能写一些关于xml+xsl方面的专业的文章。^_^<BR>上面说的东西是b/s结构下tree图的表现层的实现。如果是逻辑上的结构变更和定义，就不能靠jsp+javascript来实现了。我建议的解决方法是用tree型xml来实现,如果直接操作数据库的话，要递归并要保证结构完整性，虽然能实现但是可能会很复杂，并且效力比较低。"tree型xml来实现"可以参考<A href="http://www.matrix.org.cn/article_view.asp?id=246" target=_blank>java&amp;xml心得(三)</A>.<BR>上面就是自己在Tree型结构&nbsp;数据-结构维护-结构交互-表现&nbsp;上自己的理解。当然还有很多不完善的地方。~_~&nbsp;希望能给大家一点帮助或启示。</FONT></TD></TR>
<TR>
<TD><BR><BR>参与论坛讨论:<A href="http://www.matrix.org.cn/javaforum.asp">http://www.matrix.org.cn/forum.asp</A><BR>更多技术文章:<A href="http://www.matrix.org.cn/javaarticle.asp">http://www.matrix.org.cn/article.asp</A><BR>Matrix java门户:<A href="http://www.matrix.org.cn/java.asp">http://www.matrix.org.cn</A></TD></TR>
<TR>
<TD>原文地址：<A href="http://www.matrix.org.cn/article/335.html">http://www.matrix.org.cn/article/335.html</A><BR></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/2657.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-03-31 14:08 <a href="http://www.blogjava.net/kapok/archive/2005/03/31/2657.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Exception 处理之最佳实践</title><link>http://www.blogjava.net/kapok/archive/2005/03/16/2127.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Wed, 16 Mar 2005 04:11:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/03/16/2127.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/2127.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/03/16/2127.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/2127.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/2127.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp; http://forum.javaeye.com/viewtopic.php?t=2038&amp;postdays=0&amp;postorder=asc&amp;start=0Exception 处理之最佳实践&nbsp;原文：http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html作者：Gunja...&nbsp;&nbsp;<a href='http://www.blogjava.net/kapok/archive/2005/03/16/2127.html'>阅读全文</a><img src ="http://www.blogjava.net/kapok/aggbug/2127.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-03-16 12:11 <a href="http://www.blogjava.net/kapok/archive/2005/03/16/2127.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java权威网站汇集</title><link>http://www.blogjava.net/kapok/archive/2005/03/13/2030.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 13 Mar 2005 13:56:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/03/13/2030.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/2030.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/03/13/2030.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/2030.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/2030.html</trackback:ping><description><![CDATA[Java权威网站汇集 <A href="http://www.open-v.com">www.open-v.com</A> 原创 [2004年8月] ©Copyright <A href="http://www.open-v.com">www.open-v.com</A>, all rights reserved 【阁下如果要转载本文，请保证内容的完整性，即 不要修改或删除原创内容的任何部分】 open-v.com给出了一些Java权威资料的链接和介绍。 􀂄 <A href="http://www.theserverside.com">http://www.theserverside.com</A> TheServerSide.com - News, Patterns, Reviews, Discussions, Articles, Books 几乎是第三方最权威的Java网站之一。其中汇集了Java领域的最新发展动态、新闻、论坛、图书样章、世界级Java专家访谈等精彩内容。 􀂄 <A href="http://javaboutique.internet.com/">http://javaboutique.internet.com/</A> Java(TM) Boutique - Free Java Applets, Games, Programming Tutorials, and Downloads - Applet Categories 定期有Java优秀文章，开发者可以订阅其邮件列表。 􀂄 <A href="http://www.devx.com/Java">http://www.devx.com/Java</A> DevX Java Zone Java优秀文章。 􀂄 <A href="http://www-900.ibm.com/developerWorks/cn/java/index.shtml">http://www-900.ibm.com/developerWorks/cn/java/index.shtml</A> developerWorks Java 专区 IBM相当精彩的网站。 􀂄 <A href="http://www.sys-con.com/java/">http://www.sys-con.com/java/</A> Java Developer's Journal JDJ，很不错的杂志。开发者可以免费注册，以获得电子版的JDJ。请注意，密码会定期更换。 􀂄 <A href="http://www.javashelf.com/">http://www.javashelf.com/</A> Java books - JavaShelf.com Your Java book store on the Web! <A href="http://www.open-v.com">http://www.open-v.com</A> 联系 E_mail: <A href="mailto:j2eebeans@yahoo.com.cn">j2eebeans@yahoo.com.cn</A> 1<BR>专注于企业级 Java 应用、培训以及咨询<BR>最新出版和以往出版的Java图书介绍。 􀂄 <A href="http://www.onjava.com/">http://www.onjava.com/</A> ONJava.com 著名Oreilly出版商的Java网站。 􀂄 <A href="http://java.sun.com/reference/blueprints/index.html">http://java.sun.com/reference/blueprints/index.html</A> Java BluePrints Guidelines, patterns, and code for end-to-end applications Sun Java权威业界实践。 􀂄 <A href="http://www.springframework.org">http://www.springframework.org</A> Spring - java-j2ee Application Framework 著名的Spring—分层的、Java/J2EE应用框架。 􀂄 <A href="http://jakarta.apache.org/tapestry/">http://jakarta.apache.org/tapestry/</A> Jakarta Tapestry - Welcome! 基于组件、OOP&amp;D的Web开发。 􀂄 <A href="http://www.hibernate.org/">http://www.hibernate.org/</A> Hibernate - Object-Relational Mapping and Transparent Object Persistence for Java and SQL Databases 著名的Hibernate。 􀂄 <A href="http://struts.apache.org/">http://struts.apache.org/</A> The Apache Struts Web Application Framework Struts。 􀂄 <A href="http://www.waferproject.org/index.html">http://www.waferproject.org/index.html</A> Wafer - Web Application Framework Research project 分析各种主流的框架。 􀂄 <A href="http://www.martinfowler.com/">http://www.martinfowler.com/</A> Martin Fowler Martin Fowler。 􀂄 <A href="http://www.jboss.org/index.html">http://www.jboss.org/index.html</A> JBoss Professional Open Source 著名的JBoss。 􀂄 <A href="http://eclipse.org/aspectj/">http://eclipse.org/aspectj/</A> <A href="http://www.open-v.com">http://www.open-v.com</A> 联系 E_mail: <A href="mailto:j2eebeans@yahoo.com.cn">j2eebeans@yahoo.com.cn</A> 2<BR>专注于企业级 Java 应用、培训以及咨询<BR>aspectj project AspectJ 􀂄 <A href="http://aopalliance.sourceforge.net/">http://aopalliance.sourceforge.net/</A> AOP Alliance AOP联盟。 注意：如果您有其他优秀在线资源，请推荐给我们。 <A href="http://www.open-v.com">http://www.open-v.com</A> 联系 E_mail: <A href="mailto:j2eebeans@yahoo.com.cn">j2eebeans@yahoo.com.cn</A> 3<img src ="http://www.blogjava.net/kapok/aggbug/2030.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-03-13 21:56 <a href="http://www.blogjava.net/kapok/archive/2005/03/13/2030.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>