﻿<?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-jinfeng_wang-随笔分类-ZZ</title><link>http://www.blogjava.net/jinfeng_wang/category/672.html</link><description>G-G-S,D-D-U!</description><language>zh-cn</language><lastBuildDate>Sun, 27 Apr 2008 08:23:50 GMT</lastBuildDate><pubDate>Sun, 27 Apr 2008 08:23:50 GMT</pubDate><ttl>60</ttl><item><title>Java Thread.interrupt 害人！ 中断JAVA线程（zz）</title><link>http://www.blogjava.net/jinfeng_wang/archive/2008/04/27/196477.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Sun, 27 Apr 2008 08:16:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2008/04/27/196477.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/196477.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2008/04/27/196477.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/196477.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/196477.html</trackback:ping><description><![CDATA[<table cellspacing="0" cellpadding="0" width="100%" border="0">
    <tbody>
        <tr>
            <td valign="top" width="344"><span class="text">程序是很简易的。然而，在编程人员面前，多线程呈现出了一组新的难题，如果没有被恰当的解决，将导致意外的行为以及细微的、难以发现的错误。<br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在本篇文章中，我们针对这些难题之一：<span class="STYLE1">如何中断一个正在运行的线程。&nbsp;</span><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;&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;<br />
            <span class="STYLE1">背景</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;中断（Interrupt）一个线程意味着在该线程完成任务之前停止其正在进行的一切，有效地中止其当前的操作。线程是死亡、还是等待新的任务或是继续运行至下一步，就取决于这个程序。虽然初次看来它可能显得简单，但是，你必须进行一些预警以实现期望的结果。你最好还是牢记以下的几点告诫。<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;首先，忘掉Thread.stop方法。虽然它确实停止了一个正在运行的线程，然而，这种方法是不安全也是不受提倡的，这意味着，在未来的JAVA版本中，它将不复存在。</span></td>
        </tr>
        <tr>
            <td colspan="2" height="20"><span class="text"><br />
            &nbsp;&nbsp;&nbsp;&nbsp;一些轻率的家伙可能被另一种方法Thread.interrupt所迷惑。尽管，其名称似乎在暗示着什么，然而，这种方法并不会中断一个正在运行的线程（待会将进一步说明），正如Listing&nbsp;A中描述的那样。它创建了一个线程，并且试图使用Thread.interrupt方法停止该线程。Thread.sleep()方法的调用，为线程的初始化和中止提供了充裕的时间。线程本身并不参与任何有用的操作。<br />
            <br />
            <pre>class Example1 extends Thread {
            boolean stop=false;
            public static void main( String args[] ) throws Exception {
            Example1 thread = new Example1();
            System.out.println( "Starting thread..." );
            thread.start();
            Thread.sleep( 3000 );
            System.out.println( "Interrupting thread..." );
            thread.interrupt();
            Thread.sleep( 3000 );
            System.out.println("Stopping application..." );
            //System.exit(0);
            }
            public void run() {
            while(!stop){
            System.out.println( "Thread is running..." );
            long time = System.currentTimeMillis();
            while((System.currentTimeMillis()-time &lt; 1000)) {
            }
            }
            System.out.println("Thread exiting under request..." );
            }
            }</pre>
            </span>
            <p class="text">如果你运行了Listing&nbsp;A中的代码，你将在控制台看到以下输出：<br />
            <br />
            Starting&nbsp;thread...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Interrupting&nbsp;thread...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Stopping&nbsp;application...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            ............................... <br />
            甚至，在Thread.interrupt()被调用后，线程仍然继续运行。<br />
            <br />
            <span class="STYLE1">真正地中断一个线程</span><br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;中断线程最好的，最受推荐的方式是，使用共享变量（shared&nbsp;variable）发出信号，告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量（尤其在冗余操作期间），然后有秩序地中止任务。Listing&nbsp;B描述了这一方式。<br />
            <br />
            Listing&nbsp;B<br />
            class&nbsp;Example2&nbsp;extends&nbsp;Thread&nbsp;{<br />
            <br />
            &nbsp;&nbsp;volatile&nbsp;boolean&nbsp;stop&nbsp;=&nbsp;false;<br />
            <br />
            &nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(&nbsp;String&nbsp;args[]&nbsp;)&nbsp;throws&nbsp;Exception&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;Example2&nbsp;thread&nbsp;=&nbsp;new&nbsp;Example2();<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Starting&nbsp;thread..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;thread.start();<br />
            <br />
            &nbsp;&nbsp;&nbsp;Thread.sleep(&nbsp;3000&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Asking&nbsp;thread&nbsp;to&nbsp;stop..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;thread.stop&nbsp;=&nbsp;true;<br />
            <br />
            &nbsp;&nbsp;&nbsp;Thread.sleep(&nbsp;3000&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Stopping&nbsp;application..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;//System.exit(&nbsp;0&nbsp;);<br />
            <br />
            &nbsp;&nbsp;}<br />
            <br />
            &nbsp;&nbsp;public&nbsp;void&nbsp;run()&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(&nbsp;!stop&nbsp;)&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Thread&nbsp;is&nbsp;running..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;long&nbsp;time&nbsp;=&nbsp;System.currentTimeMillis();<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(&nbsp;(System.currentTimeMillis()-time&nbsp;&lt;&nbsp;1000)&nbsp;&amp;&amp;&nbsp;(!stop)&nbsp;)&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;}<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Thread&nbsp;exiting&nbsp;under&nbsp;request..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;}<br />
            <br />
            }<br />
            &nbsp;<br />
            运行Listing&nbsp;B中的代码将产生如下输出（注意线程是如何有秩序的退出的）<br />
            <br />
            Starting&nbsp;thread...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Thread&nbsp;is&nbsp;running...<br />
            <br />
            Asking&nbsp;thread&nbsp;to&nbsp;stop...<br />
            <br />
            Thread&nbsp;exiting&nbsp;under&nbsp;request...<br />
            <br />
            Stopping&nbsp;application...<br />
            <br />
            &nbsp;&nbsp;&nbsp;虽然该方法要求一些编码，但并不难实现。同时，它给予线程机会进行必要的清理工作，这在任何一个多线程应用程序中都是绝对需要的。请确认将共享变量定义成volatile&nbsp;类型或将对它的一切访问封入同步的块/方法（synchronized&nbsp;blocks/methods）中。<br />
            <br />
            到目前为止一切顺利!但是，当线程等待某些事件发生而被阻塞，又会发生什么？当然，如果线程被阻塞，它便不能核查共享变量，也就不能停止。这在许多情况下会发生，例如调用Object.wait()、ServerSocket.accept()和DatagramSocket.receive()时，这里仅举出一些。<br />
            <br />
            他们都可能永久的阻塞线程。即使发生超时，在超时期满之前持续等待也是不可行和不适当的，所以，要使用某种机制使得线程更早地退出被阻塞的状态。<br />
            <br />
            很不幸运，不存在这样一种机制对所有的情况都适用，但是，根据情况不同却可以使用特定的技术。在下面的环节，我将解答一下最普遍的例子。<br />
            <span class="STYLE1"><br />
            使用Thread.interrupt()中断线程</span><br />
            <br />
            &nbsp;&nbsp;正如Listing&nbsp;A中所描述的，Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是，在线程受到阻塞时抛出一个中断信号，这样线程就得以退出阻塞的状态。更确切的说，如果线程被Object.wait,&nbsp;Thread.join和Thread.sleep三种方法之一阻塞，那么，它将接收到一个中断异常（InterruptedException），从而提早地终结被阻塞状态。<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;因此，如果线程被上述几种方法阻塞，正确的停止线程方式是设置共享变量，并调用interrupt()（注意变量应该先设置）。<span class="STYLE1">如果线程没有被阻塞，这时调用interrupt()将不起作用；否则，线程就将得到异常（该线程必须事先预备好处理此状况），接着逃离阻塞状态。</span>在任何一种情况中，最后线程都将检查共享变量然后再停止。Listing&nbsp;C这个示例描述了该技术。<br />
            <br />
            Listing&nbsp;C<br />
            class&nbsp;Example3&nbsp;extends&nbsp;Thread&nbsp;{<br />
            <br />
            &nbsp;&nbsp;volatile&nbsp;boolean&nbsp;stop&nbsp;=&nbsp;false;<br />
            <br />
            &nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(&nbsp;String&nbsp;args[]&nbsp;)&nbsp;throws&nbsp;Exception&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;Example3&nbsp;thread&nbsp;=&nbsp;new&nbsp;Example3();<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Starting&nbsp;thread..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;thread.start();<br />
            <br />
            &nbsp;&nbsp;&nbsp;Thread.sleep(&nbsp;3000&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Asking&nbsp;thread&nbsp;to&nbsp;stop..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;thread.stop&nbsp;=&nbsp;true;//如果线程阻塞，将不会检查此变量<br />
            <br />
            &nbsp;&nbsp;&nbsp;thread.interrupt();<br />
            <br />
            &nbsp;&nbsp;&nbsp;Thread.sleep(&nbsp;3000&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Stopping&nbsp;application..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;//System.exit(&nbsp;0&nbsp;);<br />
            <br />
            &nbsp;&nbsp;}<br />
            <br />
            &nbsp;&nbsp;public&nbsp;void&nbsp;run()&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(&nbsp;!stop&nbsp;)&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Thread&nbsp;running..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(&nbsp;1000&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;catch&nbsp;(&nbsp;InterruptedException&nbsp;e&nbsp;)&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Thread&nbsp;interrupted..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;}<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Thread&nbsp;exiting&nbsp;under&nbsp;request..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;}<br />
            <br />
            }<br />
            <br />
            一旦Listing&nbsp;C中的Thread.interrupt()被调用，线程便收到一个异常，于是逃离了阻塞状态并确定应该停止。运行以上代码将得到下面的输出：<br />
            <br />
            Starting&nbsp;thread...<br />
            <br />
            Thread&nbsp;running...<br />
            <br />
            Thread&nbsp;running...<br />
            <br />
            Thread&nbsp;running...<br />
            <br />
            Asking&nbsp;thread&nbsp;to&nbsp;stop...<br />
            <br />
            Thread&nbsp;interrupted...<br />
            <br />
            Thread&nbsp;exiting&nbsp;under&nbsp;request...<br />
            <br />
            Stopping&nbsp;application...<br />
            <br />
            <br />
            <span class="STYLE1">中断I/O操作</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;然而，如果线程在I/O操作进行时被阻塞，又会如何？I/O操作可以阻塞线程一段相当长的时间，特别是牵扯到网络应用时。例如，服务器可能需要等待一个请求（request），又或者，一个网络应用程序可能要等待远端主机的响应。<br />
            <br />
            如果你正使用通道（channels）（这是在Java&nbsp;1.4中引入的新的I/O&nbsp;API），那么被阻塞的线程将收到一个ClosedByInterruptException异常。如果情况是这样，其代码的逻辑和第三个例子中的是一样的，只是异常不同而已。<br />
            <br />
            但是，你可能正使用Java1.0之前就存在的传统的I/O，而且要求更多的工作。既然这样，Thread.interrupt()将不起作用，因为线程将不会退出被阻塞状态。Listing&nbsp;D描述了这一行为。尽管interrupt()被调用，线程也不会退出被阻塞状态<br />
            <br />
            Listing&nbsp;D<br />
            import&nbsp;java.io.*;<br />
            <br />
            class&nbsp;Example4&nbsp;extends&nbsp;Thread&nbsp;{<br />
            <br />
            &nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(&nbsp;String&nbsp;args[]&nbsp;)&nbsp;throws&nbsp;Exception&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;Example4&nbsp;thread&nbsp;=&nbsp;new&nbsp;Example4();<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Starting&nbsp;thread..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;thread.start();<br />
            <br />
            &nbsp;&nbsp;&nbsp;Thread.sleep(&nbsp;3000&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Interrupting&nbsp;thread..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;thread.interrupt();<br />
            <br />
            &nbsp;&nbsp;&nbsp;Thread.sleep(&nbsp;3000&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Stopping&nbsp;application..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;//System.exit(&nbsp;0&nbsp;);<br />
            <br />
            &nbsp;&nbsp;}<br />
            <br />
            &nbsp;&nbsp;public&nbsp;void&nbsp;run()&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;ServerSocket&nbsp;socket;<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket&nbsp;=&nbsp;new&nbsp;ServerSocket(7856);<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;catch&nbsp;(&nbsp;IOException&nbsp;e&nbsp;)&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Could&nbsp;not&nbsp;create&nbsp;the&nbsp;socket..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return;<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;}<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(&nbsp;true&nbsp;)&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Waiting&nbsp;for&nbsp;connection..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Socket&nbsp;sock&nbsp;=&nbsp;socket.accept();<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;catch&nbsp;(&nbsp;IOException&nbsp;e&nbsp;)&nbsp;{<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"accept()&nbsp;failed&nbsp;or&nbsp;interrupted..."&nbsp;);<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;}<br />
            <br />
            &nbsp;&nbsp;}<br />
            <br />
            }<br />
            <br />
            <br />
            &nbsp;&nbsp;&nbsp;很幸运，Java平台为这种情形提供了一项解决方案，即调用阻塞该线程的套接字的close()方法。在这种情形下，如果线程被I/O操作阻塞，该线程将接收到一个SocketException异常，这与使用interrupt()方法引起一个InterruptedException异常被抛出非常相似。<br />
            <br />
            唯一要说明的是，必须存在socket的引用（reference），只有这样close()方法才能被调用。这意味着socket对象必须被共享。Listing&nbsp;E描述了这一情形。运行逻辑和以前的示例是相同的。<br />
            <br />
            Listing&nbsp;E<br />
            import&nbsp;java.net.*;<br />
            import&nbsp;java.io.*;<br />
            class&nbsp;Example5&nbsp;extends&nbsp;Thread&nbsp;{<br />
            &nbsp;&nbsp;volatile&nbsp;boolean&nbsp;stop&nbsp;=&nbsp;false;<br />
            &nbsp;&nbsp;volatile&nbsp;ServerSocket&nbsp;socket;<br />
            &nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(&nbsp;String&nbsp;args[]&nbsp;)&nbsp;throws&nbsp;Exception&nbsp;{<br />
            &nbsp;&nbsp;&nbsp;&nbsp;Example5&nbsp;thread&nbsp;=&nbsp;new&nbsp;Example5();<br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Starting&nbsp;thread..."&nbsp;);<br />
            &nbsp;&nbsp;&nbsp;thread.start();<br />
            &nbsp;&nbsp;&nbsp;Thread.sleep(&nbsp;3000&nbsp;);<br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Asking&nbsp;thread&nbsp;to&nbsp;stop..."&nbsp;);<br />
            &nbsp;&nbsp;&nbsp;thread.stop&nbsp;=&nbsp;true;<br />
            &nbsp;&nbsp;&nbsp;thread.socket.close();<br />
            &nbsp;&nbsp;&nbsp;Thread.sleep(&nbsp;3000&nbsp;);<br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Stopping&nbsp;application..."&nbsp;);<br />
            &nbsp;&nbsp;&nbsp;//System.exit(&nbsp;0&nbsp;);<br />
            &nbsp;&nbsp;}<br />
            &nbsp;&nbsp;public&nbsp;void&nbsp;run()&nbsp;{<br />
            &nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{<br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket&nbsp;=&nbsp;new&nbsp;ServerSocket(7856);<br />
            &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;catch&nbsp;(&nbsp;IOException&nbsp;e&nbsp;)&nbsp;{<br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Could&nbsp;not&nbsp;create&nbsp;the&nbsp;socket..."&nbsp;);<br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return;<br />
            &nbsp;&nbsp;&nbsp;&nbsp;}<br />
            &nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(&nbsp;!stop&nbsp;)&nbsp;{<br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Waiting&nbsp;for&nbsp;connection..."&nbsp;);<br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{<br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Socket&nbsp;sock&nbsp;=&nbsp;socket.accept();<br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;catch&nbsp;(&nbsp;IOException&nbsp;e&nbsp;)&nbsp;{<br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"accept()&nbsp;failed&nbsp;or&nbsp;interrupted..."&nbsp;);<br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
            &nbsp;&nbsp;&nbsp;&nbsp;}<br />
            &nbsp;&nbsp;&nbsp;System.out.println(&nbsp;"Thread&nbsp;exiting&nbsp;under&nbsp;request..."&nbsp;);<br />
            &nbsp;&nbsp;}<br />
            }<br />
            以下是运行Listing&nbsp;E中代码后的输出：<br />
            <br />
            Starting&nbsp;thread...<br />
            <br />
            Waiting&nbsp;for&nbsp;connection...<br />
            <br />
            Asking&nbsp;thread&nbsp;to&nbsp;stop...<br />
            <br />
            accept()&nbsp;failed&nbsp;or&nbsp;interrupted...<br />
            <br />
            Thread&nbsp;exiting&nbsp;under&nbsp;request...<br />
            <br />
            Stopping&nbsp;application...<br />
            <br />
            多线程是一个强大的工具，然而它正呈现出一系列难题。其中之一是如何中断一个正在运行的线程。如果恰当地实现，使用上述技术中断线程将比使用Java平台上已经提供的内嵌操作更为简单。 </p>
            </td>
        </tr>
    </tbody>
</table>
============================================<br />
<br />
Writing multithreaded programs in Java, with its built-in support for threads, is fairly straightforward. However, multithreading presents a whole set of new challenges to the programmer that, if not correctly addressed, can lead to unexpected behavior and subtle, hard-to-find errors. In this article, we address one of those challenges: how to interrupt a running thread.<br />
<br />
<span class="subhead1">Background</span><br />
Interrupting a thread means stopping what it is doing before it has completed its task, effectively aborting its current operation. Whether the thread dies, waits for new tasks, or goes on to the next step depends on the application.<br />
<br />
Although it may seem simple at first, you must take some precautions in order to achieve the desired result. There are some caveats you must be aware of as well.<br />
<br />
First of all, forget the <em>Thread.stop</em> method. Although it indeed stops a running thread, the method is unsafe and was <a href="http://java.sun.com/j2se/1.3/docs/guide/misc/threadPrimitiveDeprecation.html" target="_target">deprecated</a>, which means it may not be available in future versions of the Java.<br />
<br />
Another method that can be confusing for the unadvised is <em>Thread.interrupt</em>. Despite what its name may imply, the method does not interrupt a running thread (more on this later), as <a onclick="window.open('http://techrepublic.com.com/html/tr/sidebars/5144546-0.html','','width=680,height=600,top=0,left=0,toolbar=no,location=yes,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes')" href="http://articles.techrepublic.com.com/5100-22-5144546.html#">Listing A</a> demonstrates. It creates a thread and tries to stop it using <em>Thread.interrupt</em>. The calls to <em>Thread.sleep</em><em>(</em><em>)</em> give plenty of time for the thread initialization and termination. The thread itself does not do anything useful.<br />
<br />
If you run the code in Listing A, you should see something like this on your console:<br />
<span class="code">Starting thread...</span><br />
<span class="code">Thread is running...</span><br />
<span class="code">Thread is running...</span><br />
<span class="code">Thread is running...</span><br />
<span class="code">Interrupting thread...</span><br />
<span class="code">Thread is running...</span><br />
<span class="code">Thread is running...</span><br />
<span class="code">Thread is running...</span><br />
<span class="code">Stopping application...</span><br />
<br />
Even after <em>Thread.interrupt</em><em>()</em> is called, the thread continues to run for a while.<br />
<br />
<span class="subhead1">Really interrupting a thread</span><br />
The best, recommended way to interrupt a thread is to use a shared variable to signal that it must stop what it is doing. The thread must check the variable periodically, especially during lengthy operations, and terminate its task in an orderly manner. <a onclick="window.open('http://techrepublic.com.com/html/tr/sidebars/5144546-1.html','','width=680,height=600,top=0,left=0,toolbar=no,location=yes,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes')" href="http://articles.techrepublic.com.com/5100-22-5144546.html#">Listing B</a> demonstrates this technique.<br />
<br />
Running the code in Listing B will generate output like this (notice how the thread exits in an orderly fashion):<br />
<span class="code">Starting thread...</span><br />
<span class="code">Thread is running...</span><br />
<span class="code">Thread is running...</span><br />
<span class="code">Thread is running...</span><br />
<span class="code">Asking thread to stop...</span><br />
<span class="code">Thread exiting under request...</span><br />
<span class="code">Stopping application...</span><br />
<br />
Although this method requires some coding, it is not difficult to implement and give the thread the opportunity to do any cleanup needed, which is an absolute requirement for any multithreaded application. Just be sure to declare the shared variable as <em>volatile</em> or enclose any access to it into <em>synchronized</em> blocks/methods.<br />
<br />
So far, so good! But what happens if the thread is blocked waiting for some event? Of course, if the thread is blocked, it can't check the shared variable and can't stop. There are plenty of situations when that may occur, such as calling <em>Object.wait</em><em>()</em>, <em>ServerSocket.accept</em><em>()</em>, and <em>DatagramSocket.receive</em><em>()</em>, to name a few.<br />
<br />
They all can block the thread forever. Even if a timeout is employed, it may not be feasible or desirable to wait until the timeout expires, so a mechanism to prematurely exit the blocked state must be used.<br />
<br />
Unfortunately there is no such mechanism that works for all cases, but the particular technique to use depends on each situation. In the following sections, I'll give solutions for the most common cases.<br />
<br />
<span class="subhead1">Interrupting a thread with <em>Thread.interrupt</em><em>()</em></span><br />
As demonstrated in Listing A, the method <em>Thread.interrupt</em><em>()</em> does not interrupt a running thread. What the method actually does is to throw an interrupt if the thread is blocked, so that it exits the blocked state. More precisely, if the thread is blocked at one of the methods <em>Object.wait</em>, <em>Thread.join</em>, or <em>Thread.sleep</em>, it receives an <em>InterruptedException</em>, thus terminating the blocking method prematurely.<br />
<br />
So, if a thread blocks in one of the aforementioned methods, the correct way to stop it is to set the shared variable and then call the <em>interrupt()</em> method on it (notice that it is important to set the variable first). If the thread is not blocked, calling <em>interrupt(</em><em>)</em> will not hurt; otherwise, the thread will get an exception (the thread must be prepared to handle this condition) and escape the blocked state. In either case, eventually the thread will test the shared variable and stop. <a onclick="window.open('http://techrepublic.com.com/html/tr/sidebars/5144546-2.html','','width=680,height=600,top=0,left=0,toolbar=no,location=yes,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes')" href="http://articles.techrepublic.com.com/5100-22-5144546.html#">Listing C</a> is a simple example that demonstrates this technique.<br />
<br />
As soon as <em>Thread.interrupt</em><em>()</em> is called in Listing C, the thread gets an exception so that it escapes the blocked state and determines that it should stop. Running this code produces output like this:<br />
<span class="code">Starting thread...</span><br />
<span class="code">Thread running...</span><br />
<span class="code">Thread running...</span><br />
<span class="code">Thread running...</span><br />
<span class="code">Asking thread to stop...</span><br />
<span class="code">Thread interrupted...</span><br />
<span class="code">Thread exiting under request...</span><br />
<span class="code">Stopping application...</span><br />
<br />
<span class="subhead1">Interrupting an I/O operation</span><br />
But what happens if the thread is blocked on an I/O operation? I/O can block a thread for a considerable amount of time, particularly if network communication is involved. For example, a server may be waiting for a request, or a network application may be waiting for an answer from a remote host.<br />
<br />
If you're using channels, available with the new I/O API introduced in Java 1.4, the blocked thread will get a <em>ClosedByInterruptException</em> exception. If that is the case, the logic is the same as that used in the third example—only the exception is different.<br />
<br />
But you might be using the traditional I/O available since Java 1.0, since the new I/O is so recent and requires more work. In this case, <em>Thread.interrupt</em><em>()</em> doesn't help, since the thread will not exit the blocked state. <a onclick="window.open('http://techrepublic.com.com/html/tr/sidebars/5144546-3.html','','width=680,height=600,top=0,left=0,toolbar=no,location=yes,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes')" href="http://articles.techrepublic.com.com/5100-22-5144546.html#">Listing D</a> demonstrates that behavior. Although the <em>interrupt()</em> method is called, the thread does not exit the blocked state.<br />
<br />
Fortunately, the Java Platform provides a solution for that case by calling the <em>close()</em> method of the socket the thread is blocked in. In this case, if the thread is blocked in an I/O operation, the thread will get a <em>SocketException</em> exception, much like the <em>interrupt()</em> method causes an <em>InterruptedException</em> to be thrown.<br />
<br />
The only caveat is that a reference to the socket must be available so that its <em>close()</em> method can be called. That means the socket object must also be shared. <a onclick="window.open('http://techrepublic.com.com/html/tr/sidebars/5144546-4.html','','width=680,height=600,top=0,left=0,toolbar=no,location=yes,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes')" href="http://articles.techrepublic.com.com/5100-22-5144546.html#">Listing E</a> demonstrates this case. The logic is the same as in the examples presented so far.<br />
<br />
And here's the sample output you can expect from running Listing E:<br />
<span class="code">Starting thread...</span><br />
<span class="code">Waiting for connection...</span><br />
<span class="code">Asking thread to stop...</span><br />
<span class="code">accept() failed or interrupted...</span><br />
<span class="code">Thread exiting under request...</span><br />
<span class="code">Stopping application...</span><br />
<br />
Multithreading is a powerful tool, but it presents its own set of challenges. One of these is how to interrupt a running thread. If properly implemented, these techniques make interrupting a thread no more difficult than using the built-in operations already provided by the Java Platform.
<img src ="http://www.blogjava.net/jinfeng_wang/aggbug/196477.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2008-04-27 16:16 <a href="http://www.blogjava.net/jinfeng_wang/archive/2008/04/27/196477.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Mylyn 2.0，第 1 部分: 集成的任务管理</title><link>http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188558.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 25 Mar 2008 10:16:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188558.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/188558.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188558.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/188558.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/188558.html</trackback:ping><description><![CDATA[http://www.ibm.com/developerworks/cn/java/j-mylyn1/
<img src ="http://www.blogjava.net/jinfeng_wang/aggbug/188558.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2008-03-25 18:16 <a href="http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188558.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Mylyn 2.0，第 2 部分: 自动上下文管理</title><link>http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188559.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 25 Mar 2008 10:16:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188559.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/188559.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188559.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/188559.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/188559.html</trackback:ping><description><![CDATA[http://www.ibm.com/developerworks/cn/java/j-mylyn2/
<img src ="http://www.blogjava.net/jinfeng_wang/aggbug/188559.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2008-03-25 18:16 <a href="http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188559.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>develop tools</title><link>http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188555.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 25 Mar 2008 10:10:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188555.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/188555.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188555.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/188555.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/188555.html</trackback:ping><description><![CDATA[<table cellspacing="5" cellpadding="0" width="100%" border="0">
    <tbody>
        <tr>
            <td valign="middle" width="19%"></td>
            <td class="fontblankbold" width="81%"><font face="Verdana, Arial, Helvetica, sans-serif">JIRA</font><br />
            <span class="fontbluesmall"><a class="fontbluesmall" href="http://www.fangwai.net/software/jira/default.htm">专业的缺陷跟踪与项目管理软件（<font face="Verdana, Arial, Helvetica, sans-serif">Issue Tracking</font>）</a></span></td>
        </tr>
        <tr>
            <td valign="middle" width="19%"><img height="46" src="http://www.fangwai.net/images/conflance.gif" width="43"  alt="" /></td>
            <td class="fontblankbold" width="81%"><font face="Verdana, Arial, Helvetica, sans-serif">Confluence</font><br />
            <span class="fontbluesmall"><a class="fontbluesmall" href="http://www.fangwai.net/software/confluence/default.htm">团队协作与知识共享（企业<font face="Verdana, Arial, Helvetica, sans-serif">Wiki</font>软件）</a></span></td>
        </tr>
        <tr>
            <td valign="middle"><img height="46" src="http://www.fangwai.net/images/bamboo.gif" width="43"  alt="" /></td>
            <td class="fontblankbold"><font face="Verdana, Arial, Helvetica, sans-serif">Bamboo</font><br />
            <span class="fontbluesmall"><a class="fontbluesmall" href="http://www.fangwai.net/software/bamboo/default.htm">持续集成服务器（<font face="Verdana, Arial, Helvetica, sans-serif">CI Build Server</font>）</a> </span></td>
        </tr>
        <tr>
            <td valign="middle"><img height="46" src="http://www.fangwai.net/images/crowd.gif" width="43"  alt="" /></td>
            <td class="fontblankbold"><font face="Verdana, Arial, Helvetica, sans-serif">Crowd</font><br />
            <span class="fontbluesmall"><a class="fontbluesmall" href="http://www.fangwai.net/software/crowd/default.htm">单点登录工具（<font face="Verdana, Arial, Helvetica, sans-serif">SSO - Simplified</font>）</a> </span></td>
        </tr>
        <tr>
            <td valign="middle"><img src="http://www.fangwai.net/images/clover.gif"  alt="" /></td>
            <td class="fontblankbold"><font face="Verdana, Arial, Helvetica, sans-serif">Clover</font><br />
            <span class="fontbluesmall"><a class="fontbluesmall" href="http://www.fangwai.net/software/clover/default.htm">强大并高度可配置的代码覆盖分析工具</a></span></td>
        </tr>
        <tr>
            <td valign="middle"><img src="http://www.fangwai.net/images/crucible.gif"  alt="" /></td>
            <td class="fontblankbold"><font face="Verdana, Arial, Helvetica, sans-serif">Crucible</font><br />
            <span class="fontbluesmall"><a class="fontbluesmall" href="http://www.fangwai.net/software/crucible/default.htm">有效的代码评审 <font face="Verdana, Arial, Helvetica, sans-serif"></font></a></span></td>
        </tr>
        <tr>
            <td valign="middle"><img src="http://www.fangwai.net/images/fisheye.gif"  alt="" /></td>
            <td class="fontblankbold"><font face="Verdana, Arial, Helvetica, sans-serif">FishEye</font><br />
            <span class="fontbluesmall"><a class="fontbluesmall" href="http://www.fangwai.net/software/fisheye/default.htm">源代码库深度查看</a> </span></td>
        </tr>
    </tbody>
</table>
<img src ="http://www.blogjava.net/jinfeng_wang/aggbug/188555.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2008-03-25 18:10 <a href="http://www.blogjava.net/jinfeng_wang/archive/2008/03/25/188555.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>XDoclet的build.xml写法 zz</title><link>http://www.blogjava.net/jinfeng_wang/archive/2008/03/20/187409.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Thu, 20 Mar 2008 01:52:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2008/03/20/187409.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/187409.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2008/03/20/187409.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/187409.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/187409.html</trackback:ping><description><![CDATA[http://gocom.primeton.com/showblogarticle.php?cat_id=all&amp;articleId=&amp;blogId=14478&amp;?PHPSESSID=0d485ace30025757c7b9a22da013c1a0&amp;page=4&amp;PHPSESSID=1370043e8b6d902ac0f6fdc3fe7776a8<br />
<br />
<br />
&nbsp;XDoclet的build.xml写法
<p><br />
</p>
<div>
<ol>
    <li>&lt;?xml version="1.0" encoding="utf-8"?&gt; </li>
    <li>&lt;project name="appgen" default="web-demo" basedir="."&gt; </li>
    <li>&nbsp;</li>
    <li>&lt;property file="build.properties" /&gt; </li>
    <li>&nbsp;</li>
    <li>&lt;path id="xdoclet.classpath"&gt; </li>
    <li>&lt;fileset dir="${lib}"&gt; </li>
    <li>&lt;include name="**/*.jar" /&gt; </li>
    <li>&lt;/fileset&gt; </li>
    <li>&lt;path location="${classes}" /&gt; </li>
    <li>&lt;/path&gt; </li>
    <li>&nbsp;</li>
    <li>&lt;target name="clean"&gt; </li>
    <li>&lt;delete&gt; </li>
    <li>&lt;fileset dir="${gen}"&gt; </li>
    <li>&lt;include name="*.xml" /&gt; </li>
    <li>&lt;/fileset&gt; </li>
    <li>&lt;/delete&gt; </li>
    <li>&lt;/target&gt; </li>
    <li>&nbsp;</li>
    <li>&lt;!-- 这个target可以生成，strutsconfigxml , strutsvalidationxml ,web.xml--&gt; </li>
    <li>&nbsp;</li>
    <li>&lt;target name="web-demo" depends="clean,Spring-service-beans,Spring-action-beans,Spring-dao-beans"&gt; </li>
    <li>&lt;taskdef name="webdoclet" classname="xdoclet.modules.web.WebDocletTask" classpathref="xdoclet.classpath" /&gt; </li>
    <li>&lt;webdoclet destDir="${gen}" mergeDir="${merge}" force="false"&gt; </li>
    <li>&lt;fileset dir="${src}"&gt; </li>
    <li>&lt;include name="**/*Action.java" /&gt; </li>
    <li>&lt;include name="**/*Form.java" /&gt; </li>
    <li>&lt;/fileset&gt; </li>
    <li>&nbsp;</li>
    <li>&lt;strutsconfigxml Version="1.2" destDir="${gen}" validateXML="true"/&gt; </li>
    <li>&lt;strutsvalidationxml destDir="${gen}" /&gt; </li>
    <li>&lt;deploymentdescriptor Servletspec="2.4" destDir="${gen}" /&gt; </li>
    <li>&nbsp;</li>
    <li>&lt;/webdoclet&gt; </li>
    <li>&lt;/target&gt; </li>
    <li>&nbsp;</li>
    <li>&lt;target name="ReplaceConfigFile" depends="web-demo"&gt; </li>
    <li>&lt;copy todir="${webinf}"&gt; </li>
    <li>&lt;fileset dir="${gen}"&gt; </li>
    <li>&lt;include name="struts-config.xml"/&gt; </li>
    <li>&lt;include name="validation.xml"/&gt; </li>
    <li>&lt;include name="action-beans.xml"/&gt; </li>
    <li>&lt;include name="dao-beans.xml"/&gt; </li>
    <li>&lt;include name="service-beans.xml"/&gt; </li>
    <li>&lt;/fileset&gt; </li>
    <li>&lt;/copy&gt; </li>
    <li>&lt;/target&gt; </li>
    <li>&lt;!-- 生成spring的xml文件 --&gt; </li>
    <li>&lt;target name="Spring-service-beans" description="Spring-application-beans"&gt; </li>
    <li>&lt;taskdef name="springdoclet" classname="xdoclet.modules.spring.SpringDocletTask" classpathref="xdoclet.classpath" /&gt; </li>
    <li><span>&lt;springdoclet
    excludedTags="@version,@author,@todo" destDir="gen" verbose="true"
    addedTags="@xdoclet-generated at ${TODAY},@copyright The XDoclet
    Team,@author XDoclet,@version ${version}"&gt; </span></li>
    <li>&lt;fileset dir="src" includes="**/*ServiceImpl.java"/&gt; </li>
    <li>&lt;springxml destinationFile="service-beans.xml" destDir="gen"/&gt; </li>
    <li>&lt;/springdoclet&gt; </li>
    <li>&lt;/target&gt; </li>
    <li>&nbsp;</li>
    <li>&nbsp;</li>
    <li>&lt;!-- 生成spring的xml文件 --&gt; </li>
    <li>&lt;target name="Spring-action-beans" description="Spring-servlet-beans"&gt; </li>
    <li>&lt;taskdef name="springdoclet" classname="xdoclet.modules.spring.SpringDocletTask" classpathref="xdoclet.classpath" /&gt; </li>
    <li><span>&lt;springdoclet
    excludedTags="@version,@author,@todo" destDir="gen" verbose="true"
    addedTags="@xdoclet-generated at ${TODAY},@copyright The XDoclet
    Team,@author XDoclet,@version ${version}"&gt; </span></li>
    <li>&lt;springxml destinationFile="action-beans.xml" destDir="gen"/&gt; </li>
    <li>&lt;fileset dir="src" includes="**/*Action.java"/&gt; </li>
    <li>&lt;/springdoclet&gt; </li>
    <li>&lt;/target&gt; </li>
    <li>&nbsp;</li>
    <li>&nbsp;</li>
    <li>&lt;!-- 生成spring的xml文件 --&gt; </li>
    <li>&lt;target name="Spring-dao-beans" description="Spring-servlet-beans"&gt; </li>
    <li>&lt;taskdef name="springdoclet" classname="xdoclet.modules.spring.SpringDocletTask" classpathref="xdoclet.classpath" /&gt; </li>
    <li><span>&lt;springdoclet
    excludedTags="@version,@author,@todo" destDir="gen" verbose="true"
    addedTags="@xdoclet-generated at ${TODAY},@copyright The XDoclet
    Team,@author XDoclet,@version ${version}"&gt; </span></li>
    <li>&lt;springxml destinationFile="dao-beans.xml" destDir="gen"/&gt; </li>
    <li>&lt;fileset dir="src" includes="**/*DAOIbatis.java"/&gt; </li>
    <li>&lt;/springdoclet&gt; </li>
    <li>&lt;/target&gt; </li>
    <li>&nbsp;</li>
    <li>&lt;/project&gt; </li>
    <li>&nbsp;</li>
</ol>
</div>
<p><br />
＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝</p>
<p><br />
</p>
<div>
<ol>
    <li>/** </li>
    <li>* @spring.bean id="UserDAO" </li>
    <li>* @spring.property name="dataSource" ref="dataSource" </li>
    <li>* @spring.property name="sqlMapClient" ref="sqlMapClient" </li>
    <li>* </li>
    <li>* @author Conan </li>
    <li>* </li>
    <li>*/ </li>
    <li>public class UserDAOIbatis extends BaseDAOIBatis implements UserDAO {....} </li>
</ol>
</div>
<p><br />
</p>
<p>-------------------------------------------</p>
<p><br />
</p>
<div>
<ol>
    <li>/** </li>
    <li>* @spring.bean id="UserService" </li>
    <li>* @spring.property name="userDao" ref="UserDAO" </li>
    <li>* </li>
    <li>* @author Conan </li>
    <li>* </li>
    <li>*/ </li>
    <li>public class UserServiceImpl extends BaseManager implements UserService {...} </li>
</ol>
</div>
<p><br />
<br />
&nbsp;---------------------------------------------</p>
<p><br />
</p>
<div>
<ol>
    <li>/** </li>
    <li>* </li>
    <li>* @struts.action path="/add" name="UserForm" input="/add.jsp" </li>
    <li>* type="org.springframework.web.struts.DelegatingActionProxy" </li>
    <li>* validate="true" scope="request" </li>
    <li>* </li>
    <li>* @struts.action-forward name="success" path="/success.jsp" redirect="true" </li>
    <li>* @struts.action-forward name="failure" path="/failure.jsp" redirect="true" </li>
    <li>* </li>
    <li>* @struts.action-form name="UserForm" </li>
    <li>* </li>
    <li>* @spring.bean </li>
    <li>* name="/add" </li>
    <li>* @spring.property </li>
    <li>* name="userService" </li>
    <li>* ref="UserService" </li>
    <li>* </li>
    <li>*/ </li>
    <li>public final class AddAction extends BaseAction {...} </li>
</ol>
</div>
<p><br />
</p>
<p>------------------------------------------</p>
<p><br />
</p>
<div>
<ol>
    <li>/** </li>
    <li>* @struts.form name="UserForm" </li>
    <li>*/ </li>
    <li>public class UserForm extends BaseForm{ </li>
    <li>&nbsp;</li>
    <li>/** </li>
    <li>* @struts.validator type="required" </li>
    <li>* @struts.validator type="mask" msgkey="error.age" </li>
    <li>* @struts.validator-var name="mask" value="^[0-9]*$" </li>
    <li>*/ </li>
    <li>public void setAge(Integer age) { </li>
    <li>this.age = age; </li>
    <li>} </li>
    <li>&nbsp;</li>
    <li>} </li>
</ol>
</div>
<p><br />
</p>
<p><br />
</p>
<br />
<img src ="http://www.blogjava.net/jinfeng_wang/aggbug/187409.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2008-03-20 09:52 <a href="http://www.blogjava.net/jinfeng_wang/archive/2008/03/20/187409.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2EE Web Application Events zz</title><link>http://www.blogjava.net/jinfeng_wang/archive/2008/03/18/187044.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 18 Mar 2008 09:02:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2008/03/18/187044.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/187044.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2008/03/18/187044.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/187044.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/187044.html</trackback:ping><description><![CDATA[http://www.stardeveloper.com/articles/display.html?article=2001111901&amp;page=1 <br />
<br />
<br />
<span class="heading">Overview :</span><br />
Web application events are new in Servlet 2.3 specification. They give
you greater degree of control over your web application. In this
article we will study two important application events :
<ul>
    <li>Application startup and shutdown</li>
    <li>Session creation and invalidation</li>
</ul>
<p>As their names suggest, application startup event occurs when your
web application is first loaded and started by the Servlet container
and application shutdown event occurs when the web application is
shutdown.</p>
<p>Session creation event occurs everytime a new session is created on
the server and similarly session invalidation event occurs everytime a
session is invalidated. <span class="highLight">To make use of these
web application events and to do something useful you'll have to create
and make use of special "listener" classes</span>. From here onwards, we'll look at what these listener classes are how you can use them.</p>
<p><span class="smallHeading">Listener Classes :</span><br />
These are simple Java classes which implement one of the two following interfaces :</p>
<ul type="square">
    <li>javax.servlet.ServletContextListener</li>
    <li>javax.servlet.http.HttpSessionListener</li>
</ul>
<p>If you want your class to listen for application startup and
shutdown events then implement ServletContextListener interface. If you
want your class to listen for session creation and invalidation events
then implement HttpSessionListener interface.</p>
<p>Let's see what are the different methods of these interfaces which you'll have to implement.</p>
<p><span class="smallHeading">ServletContextListener :</span><br />
This interface contains two methods :</p>
<ul>
    <li>public void contextInitialized(ServletContextEvent sce);</li>
    <li>public void contextDestroyed(ServletContextEvent sce);</li>
</ul>
<p>Once you implement any interface you have to implement all of it's
methods. So you if you want to make use of application startup and
shutdown events, then create a Java class and implement <code>ServletContextListener</code> interface. An example of such a class is as follows :</p>
<pre>/*<br />
File : ApplicationWatch.java<br />
*/<br />
<br />
import javax.servlet.ServletContextListener;<br />
import javax.servlet.ServletContextEvent;<br />
<br />
public class ApplicationWatch implements ServletContextListener	{<br />
<br />
public static long applicationInitialized =	0L;<br />
<br />
/* Application Startup Event */<br />
public void	contextInitialized(ServletContextEvent ce) {<br />
applicationInitialized = System.currentTimeMillis();<br />
}<br />
<br />
/* Application Shutdown	Event */<br />
public void	contextDestroyed(ServletContextEvent ce) {}<br />
}</pre>
<p>In the code above, a Java class; ApplicationWatch, implements
ServletContextListener interface. It implement both of it's methods but
it really uses only one of them and the second method's body remains
empty. This class notes down the time of application startup in a <code>public static</code> variable which can be called from other application classes to know what was the last time this application was started.</p>
<p>I'll explain how to tell the application server that we have this
listener class and we want to be told of these application events in a
moment, but first let's see what are the different methods of <code>HttpSessionListener</code> interface.</p>
<p><span class="smallHeading">HttpSessionListener :</span><br />
This interface also contains just two methods, for session creation and invalidation events respectively :</p>
<ul>
    <li>public void sessionCreated(HttpSessionEvent se);</li>
    <li>public void sessionDestroyed(HttpSessionEvent se);</li>
</ul>
<p>Like what we did in the case of ApplicationWatch above, we'll have
to create a Java class and implement HttpSessionListener interface. An
example of such a class is as follows :</p>
<pre>/*<br />
File : SessionCounter.java<br />
*/<br />
<br />
import javax.servlet.http.HttpSessionListener;<br />
import javax.servlet.http.HttpSessionEvent;<br />
<br />
public class SessionCounter	implements HttpSessionListener {<br />
<br />
private	static int activeSessions =	0;<br />
<br />
/* Session Creation	Event */<br />
public void	sessionCreated(HttpSessionEvent	se)	{<br />
activeSessions++;<br />
}<br />
<br />
/* Session Invalidation	Event */<br />
public void	sessionDestroyed(HttpSessionEvent se) {<br />
if(activeSessions &gt;	0)<br />
activeSessions--;<br />
}<br />
<br />
public static int getActiveSessions() {<br />
return activeSessions;<br />
}<br />
}</pre>
<p>In the code above, <code>SessionCounter</code> class implements
HttpSessionListener to count the number of active sessions. We will
learn more about counting active users in a separate article in more
detail.</p>
<p>Ok, we have learned what are web application events, what interfaces
are available to us and have also seen examples of classes implementing
those interfaces. Let's see how to tell the application server about
these listener classes.</p>
<p><span class="smallHeading">Web.xml :</span><br />
We do that by putting classpath of these classes in /WEB-INF/web.xml
file under special &lt;listener&gt; tags. An example of such a web.xml
file is given below :</p>
<pre>&lt;!-- Web.xml --&gt;<br />
&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;<br />
<br />
&lt;!DOCTYPE web-app<br />
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"<br />
"http://java.sun.com/j2ee/dtds/web-app_2.3.dtd"&gt;<br />
<br />
&lt;web-app&gt;<br />
<br />
&lt;!-- Listeners --&gt;<br />
&lt;listener&gt;<br />
&lt;listener-class&gt;<br />
com.stardeveloper.web.listener.SessionCounter<br />
&lt;/listener-class&gt;<br />
&lt;/listener&gt;<br />
&lt;listener&gt;<br />
&lt;listener-class&gt;<br />
com.stardeveloper.web.listener.ApplicationWatch<br />
&lt;/listener-class&gt;<br />
&lt;/listener&gt;<br />
<br />
&lt;/web-app&gt;</pre>
<p>As shown above it is quite easy to declare listener classes in
web.xml files. Now every time the server starts or shutdown, a session
is created or destroyed, your classes will be told as their specific
event methods will be called. It is that simple!</p>
<p><img src="http://www.stardeveloper.com/images/jug.gif" height="28" width="24"  alt="" /><span class="heading">Summary :</span><br />
In this article we learned what are web application events and how we
can make use of these events by creating special "listener" classes. We
then created two classes which implemented ServletContextListener and
HttpSessionListener interfaces respectively. We also learned how to
declare these listener classes in web.xml file by using special
&lt;listener&gt; and &lt;listener-class&gt; tags.</p>
<br />
<br />
<img src ="http://www.blogjava.net/jinfeng_wang/aggbug/187044.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2008-03-18 17:02 <a href="http://www.blogjava.net/jinfeng_wang/archive/2008/03/18/187044.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>web.xml Reference Guide for Tomcat zz</title><link>http://www.blogjava.net/jinfeng_wang/archive/2008/03/18/187022.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 18 Mar 2008 07:57:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2008/03/18/187022.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/187022.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2008/03/18/187022.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/187022.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/187022.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: http://wiki.metawerx.net/wiki/Web.xmlIntroductionThe web.xml Deployment Descriptor file describes how to deploy a web application in a servlet container such as Tomcat.This file is require...&nbsp;&nbsp;<a href='http://www.blogjava.net/jinfeng_wang/archive/2008/03/18/187022.html'>阅读全文</a><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/187022.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2008-03-18 15:57 <a href="http://www.blogjava.net/jinfeng_wang/archive/2008/03/18/187022.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>EasyMock 使用方法与原理剖析 zz</title><link>http://www.blogjava.net/jinfeng_wang/archive/2008/03/11/185422.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 11 Mar 2008 08:16:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2008/03/11/185422.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/185422.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2008/03/11/185422.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/185422.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/185422.html</trackback:ping><description><![CDATA[http://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/index.html<br />
<br />
<br />
<blockquote>EasyMock 是一套通过简单的方法对于指定的接口或类生成 Mock 对象的类库，它能利用对接口或类的模拟来辅助单元测试。本文将对
EasyMock 的功能和原理进行介绍，并通过示例来说明如何使用 EasyMock 进行单元测试。 </blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p>Mock 方法是单元测试中常见的一种技术，它的主要作用是模拟一些在应用中不容易构造或者比较复杂的对象，从而把测试与测试边界以外的对象隔离开。</p>
<p>编写自定义的 Mock 对象需要额外的编码工作，同时也可能引入错误。EasyMock 提供了根据指定接口动态构建 Mock 对象的方法，避免了手工编写
Mock 对象。本文将向您展示如何使用 EasyMock 进行单元测试，并对 EasyMock 的原理进行分析。</p>
<p><a name="N10086"><span class="atitle">1．Mock 对象与 EasyMock 简介</span></a></p>
<p><a name="N1008C"><span class="smalltitle">单元测试与 Mock 方法</span></a></p>
<p>单元测试是对应用中的某一个模块的功能进行验证。在单元测试中，我们常遇到的问题是应用中其它的协同模块尚未开发完成，或者被测试模块需要和一些不容易构造、比较复杂的对象进行交互。另外，由于不能肯定其它模块的正确性，我们也无法确定测试中发现的问题是由哪个模块引起的。</p>
<p>Mock 对象能够模拟其它协同模块的行为，被测试模块通过与 Mock 对象协作，可以获得一个孤立的测试环境。此外，使用 Mock
对象还可以模拟在应用中不容易构造（如 HttpServletRequest 必须在 Servlet 容器中才能构造出来）和比较复杂的对象（如 JDBC 中的
ResultSet 对象），从而使测试顺利进行。</p>
<p><a name="N10098"><span class="smalltitle">EasyMock 简介</span></a></p>
<p>手动的构造 Mock 对象会给开发人员带来额外的编码量，而且这些为创建 Mock 对象而编写的代码很有可能引入错误。目前，有许多开源项目对动态构建
Mock 对象提供了支持，这些项目能够根据现有的接口或类动态生成，这样不仅能避免额外的编码工作，同时也降低了引入错误的可能。</p>
<p>EasyMock 是一套用于通过简单的方法对于给定的接口生成 Mock
对象的类库。它提供对接口的模拟，能够通过录制、回放、检查三步来完成大体的测试过程，可以验证方法的调用种类、次数、顺序，可以令 Mock
对象返回指定的值或抛出指定异常。通过 EasyMock，我们可以方便的构造 Mock 对象从而使单元测试顺利进行。</p>
<p><a name="N100A4"><span class="smalltitle">安装 EasyMock</span></a></p>
<p>EasyMock 是采用 MIT license 的一个开源项目，您可以在 Sourceforge 上下载到相关的 zip 文件。目前您可以下载的
EasyMock 最新版本是2.3，它需要运行在 Java 5.0 平台上。如果您的应用运行在 Java 1.3 或 1.4 平台上，您可以选择
EasyMock1.2。在解压缩 zip 包后，您可以找到 easymock.jar 这个文件。如果您使用 Eclipse 作为 IDE，把
easymock.jar 添加到项目的 Libraries 里就可以使用了（如下图所示）。此外，由于我们的测试用例运行在 JUnit 环境中，因此您还需要
JUnit.jar（版本3.8.1以上）。</p>
<br />
<a name="classpath.gif"><strong>图1：Eclipse 项目中的
Libraries</strong></a><br />
<img alt="Eclipse 项目中的 Libraries" src="classpath.gif" height="493" width="572" /> <br />
<br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><img alt="" src="//www.ibm.com/i/v14/rules/blue_rule.gif" height="1" width="100%" /><br />
            <img alt="" src="//www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td><img alt="" src="//www.ibm.com/i/c.gif" height="4" width="100%" /><br />
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle"><img alt="" src="//www.ibm.com/i/v14/icons/u_bold.gif" border="0" height="16" width="16" /><br />
                        </td>
                        <td align="right" valign="top"><a class="fbox" href="#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="N100BC"><span class="atitle">2．使用 EasyMock 进行单元测试</span></a></p>
<p>通过 EasyMock，我们可以为指定的接口动态的创建 Mock 对象，并利用 Mock
对象来模拟协同模块或是领域对象，从而使单元测试顺利进行。这个过程大致可以划分为以下几个步骤：</p>
<ul>
    <li>使用 EasyMock 生成 Mock 对象；
    </li>
    <li>设定 Mock 对象的预期行为和输出；
    </li>
    <li>将 Mock 对象切换到 Replay 状态；
    </li>
    <li>调用 Mock 对象方法进行单元测试；
    </li>
    <li>对 Mock 对象的行为进行验证。 </li>
</ul>
<p>接下来，我们将对以上的几个步骤逐一进行说明。除了以上的基本步骤外，EasyMock 还对特殊的 Mock
对象类型、特定的参数匹配方式等功能提供了支持，我们将在之后的章节中进行说明。</p>
<p><a name="N100DA"><span class="smalltitle">使用 EasyMock 生成 Mock 对象</span></a></p>
<p>根据指定的接口或类，EasyMock 能够动态的创建 Mock 对象（EasyMock 默认只支持为接口生成 Mock 对象，如果需要为类生成 Mock
对象，在 EasyMock 的主页上有扩展包可以实现此功能），我们以 <code>ResultSet</code>
接口为例说明EasyMock的功能。<code>java.sql.ResultSet</code> 是每一个 Java
开发人员都非常熟悉的接口：</p>
<br />
<a name="code001"><strong>清单1：ResultSet 接口</strong></a><br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">                <br />
            public interface java.sql.ResultSet {<br />
            ......<br />
            public abstract java.lang.String getString(int arg0) throws java.sql.SQLException;<br />
            public abstract double getDouble(int arg0) throws java.sql.SQLException;<br />
            ......<br />
            }<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>通常，构建一个真实的 <code>RecordSet</code> 对象需要经过一个复杂的过程：在开发过程中，开发人员通常会编写一个
<code>DBUtility</code> 类来获取数据库连接 <code>Connection</code>，并利用
<code>Connection</code> 创建一个 <code>Statement</code>。执行一个 <code>Statement</code>
可以获取到一个或多个 <code>ResultSet</code>
对象。这样的构造过程复杂并且依赖于数据库的正确运行。数据库或是数据库交互模块出现问题，都会影响单元测试的结果。</p>
<p>我们可以使用 EasyMock 动态构建 <code>ResultSet</code> 接口的 Mock 对象来解决这个问题。一些简单的测试用例只需要一个
Mock 对象，这时，我们可以用以下的方法来创建 Mock 对象：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">ResultSet mockResultSet = createMock(ResultSet.class);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>其中 <code>createMock</code> 是 <code>org.easymock.EasyMock</code>
类所提供的静态方法，你可以通过 static import 将其引入（注：static import 是 java 5.0 所提供的新特性）。</p>
<p>如果需要在相对复杂的测试用例中使用多个 Mock 对象，EasyMock 提供了另外一种生成和管理 Mock 对象的机制：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">IMocksControl control = EasyMock.createControl();<br />
            java.sql.Connection mockConnection = control.createMock(Connection.class);<br />
            java.sql.Statement mockStatement = control.createMock(Statement.class);<br />
            java.sql.ResultSet mockResultSet = control.createMock(ResultSet.class);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p><code>EasyMock</code> 类的 <code>createControl</code> 方法能创建一个接口
<code>IMocksControl</code> 的对象，该对象能创建并管理多个 Mock 对象。如果需要在测试中使用多个 Mock
对象，我们推荐您使用这一机制，因为它在多个 Mock 对象的管理上提供了相对便捷的方法。</p>
<p>如果您要模拟的是一个具体类而非接口，那么您需要下载扩展包 EasyMock Class Extension 2.2.2。在对具体类进行模拟时，您只要用
<code>org.easymock.classextension.EasyMock</code> 类中的静态方法代替
<code>org.easymock.EasyMock</code> 类中的静态方法即可。</p>
<p><a name="N1014D"><span class="smalltitle">设定 Mock 对象的预期行为和输出</span></a></p>
<p>在一个完整的测试过程中，一个 Mock 对象将会经历两个状态：Record 状态和 Replay 状态。Mock 对象一经创建，它的状态就被置为
Record。在 Record 状态，用户可以设定 Mock 对象的预期行为和输出，这些对象行为被录制下来，保存在 Mock 对象中。</p>
<p>添加 Mock 对象行为的过程通常可以分为以下3步：
</p>
<ul>
    <li>对 Mock 对象的特定方法作出调用；
    </li>
    <li>通过 <code>org.easymock.EasyMock</code> 提供的静态方法 <code>expectLastCall</code>
    获取上一次方法调用所对应的 IExpectationSetters 实例；
    </li>
    <li>通过 <code>IExpectationSetters</code> 实例设定 Mock 对象的预期输出。 </li>
</ul>
<p><strong>设定预期返回值</strong> </p>
<p>Mock 对象的行为可以简单的理解为 Mock 对象方法的调用和方法调用所产生的输出。在 EasyMock 2.3 中，对 Mock
对象行为的添加和设置是通过接口 <code>IExpectationSetters</code> 来实现的。Mock
对象方法的调用可能产生两种类型的输出：（1）产生返回值；（2）抛出异常。接口 <code>IExpectationSetters</code>
提供了多种设定预期输出的方法，其中和设定返回值相对应的是 andReturn 方法：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">IExpectationSetters&lt;T&gt; andReturn(T value);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>我们仍然用 <code>ResultSet</code> 接口的 Mock 对象为例，如果希望方法
<code>mockResult.getString(1)</code> 的返回值为 "My return value"，那么你可以使用以下的语句：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">mockResultSet.getString(1);<br />
            expectLastCall().andReturn("My return value");<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>以上的语句表示 <code>mockResultSet</code> 的 <code>getString</code> 方法被调用一次，这次调用的返回值是
"My return value"。有时，我们希望某个方法的调用总是返回一个相同的值，为了避免每次调用都为 Mock
对象的行为进行一次设定，我们可以用设置默认返回值的方法：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">void andStubReturn(Object value);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>假设我们创建了 <code>Statement</code> 和 <code>ResultSet</code> 接口的 Mock 对象
mockStatement 和 mockResultSet，在测试过程中，我们希望 mockStatement 对象的
<code>executeQuery</code> 方法总是返回 mockResultSet，我们可以使用如下的语句
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">mockStatement.executeQuery("SELECT * FROM sales_order_table");<br />
            expectLastCall().andStubReturn(mockResultSet);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>EasyMock 在对参数值进行匹配时，默认采用 <code>Object.equals()</code> 方法。因此，如果我们以
<code>"select * from sales_order_table"</code> 作为参数，预期方法将不会被调用。如果您希望上例中的 SQL
语句能不区分大小写，可以用特殊的参数匹配器来解决这个问题，我们将在 "在 EasyMock 中使用参数匹配器" 一章对此进行说明。</p>
<p><strong>设定预期异常抛出</strong> </p>
<p>对象行为的预期输出除了可能是返回值外，还有可能是抛出异常。<code>IExpectationSetters</code> 提供了设定预期抛出异常的方法：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">IExpectationSetters&lt;T&gt; andThrow(Throwable throwable);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>和设定默认返回值类似，<code>IExpectationSetters</code> 接口也提供了设定抛出默认异常的函数：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">void andStubThrow(Throwable throwable);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p><strong>设定预期方法调用次数</strong> </p>
<p>通过以上的函数，您可以对 Mock
对象特定行为的预期输出进行设定。除了对预期输出进行设定，<code>IExpectationSetters</code>
接口还允许用户对方法的调用次数作出限制。在 <code>IExpectationSetters</code> 所提供的这一类方法中，常用的一种是
<code>times</code> 方法：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">IExpectationSetters&lt;T&gt;times(int count);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>该方法可以 Mock 对象方法的调用次数进行确切的设定。假设我们希望 mockResultSet 的 <code>getString</code>
方法在测试过程中被调用3次，期间的返回值都是 "My return value"，我们可以用如下语句：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">mockResultSet.getString(1);<br />
            expectLastCall().andReturn("My return value").times(3);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
注意到 <code>andReturn</code> 和
<code>andThrow</code> 方法的返回值依然是一个 <code>IExpectationSetters</code>
实例，因此我们可以在此基础上继续调用 <code>times</code> 方法。</p>
<p>除了设定确定的调用次数，<code>IExpectationSetters</code>
还提供了另外几种设定非准确调用次数的方法：<br />
<code>times(int minTimes, int maxTimes)</code>：该方法最少被调用
minTimes 次，最多被调用 maxTimes
次。<br />
<code>atLeastOnce()</code>：该方法至少被调用一次。<br />
<code>anyTimes()</code>：该方法可以被调用任意次。
</p>
<p>某些方法的返回值类型是 void，对于这一类方法，我们无需设定返回值，只要设置调用次数就可以了。以 <code>ResultSet</code> 接口的
<code>close</code> 方法为例，假设在测试过程中，该方法被调用3至5次：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">mockResultSet.close();<br />
            expectLastCall().times(3, 5);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>为了简化书写，EasyMock 还提供了另一种设定 Mock 对象行为的语句模式。对于上例，您还可以将它写成：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">expect(mockResult.close()).times(3, 5);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
这个语句和上例中的语句功能是完全相同的。 </p>
<p><a name="chap2.3"><span class="smalltitle">将 Mock 对象切换到 Replay 状态</span></a></p>
<p>在生成 Mock 对象和设定 Mock 对象行为两个阶段，Mock 对象的状态都是 Record 。在这个阶段，Mock
对象会记录用户对预期行为和输出的设定。</p>
<p>在使用 Mock 对象进行实际的测试前，我们需要将 Mock 对象的状态切换为 Replay。在 Replay 状态，Mock
对象能够根据设定对特定的方法调用作出预期的响应。将 Mock 对象切换成 Replay 状态有两种方式，您需要根据 Mock 对象的生成方式进行选择。如果
Mock 对象是通过 <code>org.easymock.EasyMock</code> 类提供的静态方法 createMock 生成的（第1节中介绍的第一种
Mock 对象生成方法），那么 <code>EasyMock</code> 类提供了相应的 replay 方法用于将 Mock 对象切换为 Replay 状态：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">replay(mockResultSet);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>如果 Mock 对象是通过 <code>IMocksControl</code> 接口提供的 <code>createMock</code>
方法生成的（第1节中介绍的第二种Mock对象生成方法），那么您依旧可以通过 <code>IMocksControl</code> 接口对它所创建的所有 Mock
对象进行切换：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">control.replay();<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>以上的语句能将在第1节中生成的 mockConnection、mockStatement 和 mockResultSet 等3个 Mock 对象都切换成
Replay 状态。</p>
<p><a name="chap2.4"><span class="smalltitle">调用 Mock 对象方法进行单元测试</span></a></p>
<p>为了更好的说明 EasyMock 的功能，我们引入 src.zip 中的示例来解释 Mock 对象在实际测试阶段的作用。其中所有的示例代码都可以在
src.zip 中找到。如果您使用的 IDE 是 Eclipse，在导入 src.zip 之后您可以看到 Workspace 中增加的
project（如下图所示）。</p>
<br />
<a name="classpath.gif"><strong>图2：导入 src.zip 后的
Workspace</strong></a><br />
<img alt="导入src.zip后的Workspace" src="workspace.gif" height="216" width="281" /> <br />
<p>下面是示例代码中的一个接口 <code>SalesOrder</code>，它的实现类 <code>SalesOrderImpl</code>
的主要功能是从数据库中读取一个 Sales Order 的 Region 和 Total Price，并根据读取的数据计算该 Sales Order 的
Price Level（完整的实现代码都可以在 src.zip 中找到）：</p>
<br />
<a name="code001"><strong>清单2：SalesOrder
接口</strong></a><br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">                <br />
            public interface SalesOrder<br />
            {<br />
            &#8230;&#8230;<br />
            public void loadDataFromDB(ResultSet resultSet) throws SQLException;	<br />
            public String getPriceLevel();<br />
            }<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>其实现类 <code>SalesOrderImpl</code> 中对 <code>loadDataFromDB</code>
的实现如下：</p>
<br />
<a name="code001"><strong>清单3：SalesOrderImpl 实现</strong></a><br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">                <br />
            public class SalesOrderImpl implements SalesOrder<br />
            {<br />
            ......<br />
            public void loadDataFromDB(ResultSet resultSet) throws SQLException<br />
            {<br />
            orderNumber = resultSet.getString(1);<br />
            region = resultSet.getString(2);<br />
            totalPrice = resultSet.getDouble(3);<br />
            }<br />
            ......<br />
            }<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>方法 <code>loadDataFromDB</code> 读取了 <code>ResultSet</code> 对象包含的数据。当我们将之前定义的
Mock 对象调整为 Replay 状态，并将该对象作为参数传入，那么 Mock 对象的方法将会返回预先定义的预期返回值。完整的 TestCase
如下：</p>
<br />
<a name="code001"><strong>清单4：完整的TestCase</strong></a><br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">                <br />
            public class SalesOrderTestCase extends TestCase {<br />
            public void testSalesOrder() {<br />
            IMocksControl control = EasyMock.createControl();<br />
            ......<br />
            ResultSet mockResultSet = control.createMock(ResultSet.class);<br />
            try {<br />
            ......<br />
            mockResultSet.next();<br />
            expectLastCall().andReturn(true).times(3);<br />
            expectLastCall().andReturn(false).times(1);<br />
            mockResultSet.getString(1);<br />
            expectLastCall().andReturn("DEMO_ORDER_001").times(1);<br />
            expectLastCall().andReturn("DEMO_ORDER_002").times(1);<br />
            expectLastCall().andReturn("DEMO_ORDER_003").times(1);<br />
            mockResultSet.getString(2);<br />
            expectLastCall().andReturn("Asia Pacific").times(1);<br />
            expectLastCall().andReturn("Europe").times(1);<br />
            expectLastCall().andReturn("America").times(1);<br />
            mockResultSet.getDouble(3);<br />
            expectLastCall().andReturn(350.0).times(1);<br />
            expectLastCall().andReturn(1350.0).times(1);<br />
            expectLastCall().andReturn(5350.0).times(1);<br />
            control.replay();<br />
            ......<br />
            int i = 0;<br />
            String[] priceLevels = { "Level_A", "Level_C", "Level_E" };<br />
            while (mockResultSet.next()) {<br />
            SalesOrder order = new SalesOrderImpl();<br />
            order.loadDataFromDB(mockResultSet);<br />
            assertEquals(order.getPriceLevel(), priceLevels[i]);<br />
            i++;<br />
            }<br />
            control.verify();<br />
            } catch (Exception e) {<br />
            e.printStackTrace();<br />
            }<br />
            }<br />
            }<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>在这个示例中，我们首先创建了 <code>ResultSet</code> 的 Mock 对象 moResultSet，并记录该 Mock
对象的预期行为。之后我们调用了 <code>control.replay()</code>，将 Mock 对象的状态置为 Replay
状态。在实际的测试阶段，Sales Order 对象的 <code>loadDataFromDB</code> 方法调用了 mockResultSet 对象的
<code>getString</code> 和 <code>getDouble</code> 方法读取 mockResultSet 中的数据。Sales
Order 对象根据读取的数据计算出 Price Level，并和预期输出进行比较。 </p>
<p><a name="chap2.5"><span class="smalltitle">对 Mock 对象的行为进行验证</span></a></p>
<p>在利用 Mock 对象进行实际的测试过程之后，我们还有一件事情没有做：对 Mock 对象的方法调用的次数进行验证。</p>
<p>为了验证指定的方法调用真的完成了，我们需要调用 <code>verify</code> 方法进行验证。和 <code>replay</code>
方法类似，您需要根据 Mock 对象的生成方式来选用不同的验证方式。如果 Mock 对象是由
<code>org.easymock.EasyMock</code> 类提供的 <code>createMock</code> 静态方法生成的，那么我们同样采用
<code>EasyMock</code> 类的静态方法 <code>verify</code> 进行验证：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">verify(mockResultSet);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>如果Mock对象是有 <code>IMocksControl</code> 接口所提供的 <code>createMock</code>
方法生成的，那么采用该接口提供的 <code>verify</code> 方法，例如第1节中的 <code>IMocksControl</code> 实例
control：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">control.verify();<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>将对 control 实例所生成的 Mock 对象 mockConnection、mockStatement 和 mockResultSet
等进行验证。如果将上例中 <code>expectLastCall().andReturn(false).times(1)</code> 的预期次数修改为2，在
Eclipse 中将可以看到：</p>
<br />
<a name="asserterror.gif"><strong>图3：Mock对象验证失败</strong></a><br />
<img alt="Mock对象验证失败" src="asserterror.gif" height="286" width="557" /> <br />
<p><a name="chap2.6"><span class="smalltitle">Mock 对象的重用</span></a></p>
<p>为了避免生成过多的 Mock 对象，EasyMock 允许对原有 Mock 对象进行重用。要对 Mock 对象重新初始化，我们可以采用 reset
方法。和 replay 和 verify 方法类似，EasyMock 提供了两种 reset 方式：（1）如果 Mock 对象是由
<code>org.easymock.EasyMock</code> 类中的静态方法 <code>createMock</code> 生成的，那么该 Mock
对象的可以用 <code>EasyMock</code> 类的静态方法 <code>reset</code> 重新初始化；（2）如果 Mock 方法是由
<code>IMocksControl</code> 实例的 <code>createMock</code> 方法生成的，那么该
<code>IMocksControl</code> 实例方法 <code>reset</code> 的调用将会把所有该实例创建的 Mock
对象重新初始化。</p>
<p>在重新初始化之后，Mock 对象的状态将被置为 Record 状态。</p>
<br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><img alt="" src="//www.ibm.com/i/v14/rules/blue_rule.gif" height="1" width="100%" /><br />
            <img alt="" src="//www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td><img alt="" src="//www.ibm.com/i/c.gif" height="4" width="100%" /><br />
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle"><img alt="" src="//www.ibm.com/i/v14/icons/u_bold.gif" border="0" height="16" width="16" /><br />
                        </td>
                        <td align="right" valign="top"><a class="fbox" href="#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="chap3"><span class="atitle">3．在 EasyMock 中使用参数匹配器</span></a></p>
<p><a name="chap3.1"><span class="smalltitle">EasyMock 预定义的参数匹配器</span></a></p>
<p>在使用 Mock 对象进行实际的测试过程中，EasyMock 会根据方法名和参数来匹配一个预期方法的调用。EasyMock 对参数的匹配默认使用
<code>equals()</code> 方法进行比较。这可能会引起一些问题。例如在上一章节中创建的mockStatement对象：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">mockStatement.executeQuery("SELECT * FROM sales_order_table");<br />
            expectLastCall().andStubReturn(mockResultSet);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>在实际的调用中，我们可能会遇到 SQL 语句中某些关键字大小写的问题，例如将 SELECT 写成 Select，这时在实际的测试中，EasyMock
所采用的默认匹配器将认为这两个参数不匹配，从而造成 Mock 对象的预期方法不被调用。EasyMock 提供了灵活的参数匹配方式来解决这个问题。如果您对
mockStatement 具体执行的语句并不关注，并希望所有输入的字符串都能匹配这一方法调用，您可以用
<code>org.easymock.EasyMock</code> 类所提供的 <code>anyObject</code> 方法来代替参数中的 SQL
语句：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">mockStatement.executeQuery( anyObject() );<br />
            expectLastCall().andStubReturn(mockResultSet);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p><code>anyObject</code> 方法表示任意输入值都与预期值相匹配。除了 <code>anyObject</code>
以外，EasyMock还提供了多个预先定义的参数匹配器，其中比较常用的一些有：</p>
<ul>
    <li><code>aryEq(X value)</code>：通过<code>Arrays.equals()</code>进行匹配，适用于数组对象；
    </li>
    <li><code>isNull()</code>：当输入值为Null时匹配；
    </li>
    <li><code>notNull()</code>：当输入值不为Null时匹配；
    </li>
    <li><code>same(X value)</code>：当输入值和预期值是同一个对象时匹配；
    </li>
    <li><code>lt(X value), leq(X value), geq(X value), gt(X
    value)</code>：当输入值小于、小等于、大等于、大于预期值时匹配，适用于数值类型；
    </li>
    <li><code>startsWith(String prefix), contains(String substring), endsWith(String
    suffix)</code>：当输入值以预期值开头、包含预期值、以预期值结尾时匹配，适用于String类型；
    </li>
    <li><code>matches(String regex)</code>：当输入值与正则表达式匹配时匹配，适用于String类型。 </li>
</ul>
<p><a name="chap3.2"><span class="smalltitle">自定义参数匹配器</span></a></p>
<p>预定义的参数匹配器可能无法满足一些复杂的情况，这时你需要定义自己的参数匹配器。在上一节中，我们希望能有一个匹配器对 SQL 中关键字的大小写不敏感，使用
<code>anyObject</code> 其实并不是一个好的选择。对此，我们可以定义自己的参数匹配器 SQLEquals。</p>
<p>要定义新的参数匹配器，需要实现 <code>org.easymock.IArgumentMatcher</code>
接口。其中，<code>matches(Object actual)</code> 方法应当实现输入值和预期值的匹配逻辑，而在
<code>appendTo(StringBuffer buffer)</code> 方法中，你可以添加当匹配失败时需要显示的信息。以下是 SQLEquals
实现的部分代码（完整的代码可以在 src.zip 中找到）：</p>
<br />
<a name="code001"><strong>清单5：自定义参数匹配器SQLEquals</strong></a><br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">                <br />
            public class SQLEquals implements IArgumentMatcher {<br />
            private String expectedSQL = null;<br />
            public SQLEquals(String expectedSQL) {<br />
            this.expectedSQL = expectedSQL;<br />
            }<br />
            ......<br />
            public boolean matches(Object actualSQL) {<br />
            if (actualSQL == null &amp;&amp; expectedSQL == null)<br />
            return true;<br />
            else if (actualSQL instanceof String)<br />
            return expectedSQL.equalsIgnoreCase((String) actualSQL);<br />
            else<br />
            return false;<br />
            }<br />
            }<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>在实现了 <code>IArgumentMatcher</code> 接口之后，我们需要写一个静态方法将它包装一下。这个静态方法的实现需要将
SQLEquals 的一个对象通过 <code>reportMatcher</code> 方法报告给EasyMock：</p>
<br />
<a name="code001"><strong>清单6：自定义参数匹配器 SQLEquals 静态方法</strong></a><br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">                <br />
            public static String sqlEquals(String in) {<br />
            reportMatcher(new SQLEquals(in));<br />
            return in;<br />
            }<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>这样，我们自定义的 sqlEquals 匹配器就可以使用了。我们可以将上例中的 <code>executeQuery</code> 方法设定修改如下：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">mockStatement.executeQuery(sqlEquals("SELECT * FROM sales_order_table"));<br />
            expectLastCall().andStubReturn(mockResultSet);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
在使用 <code>executeQuery("select * from
sales_order_table")</code> 进行方法调用时，该预期行为将被匹配。 </p>
<br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><img alt="" src="//www.ibm.com/i/v14/rules/blue_rule.gif" height="1" width="100%" /><br />
            <img alt="" src="//www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td><img alt="" src="//www.ibm.com/i/c.gif" height="4" width="100%" /><br />
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle"><img alt="" src="//www.ibm.com/i/v14/icons/u_bold.gif" border="0" height="16" width="16" /><br />
                        </td>
                        <td align="right" valign="top"><a class="fbox" href="#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="chap4"><span class="atitle">4．特殊的 Mock 对象类型</span></a></p>
<p>到目前为止，我们所创建的 Mock 对象都属于 EasyMock 默认的 Mock 对象类型，它对预期方法的调用次序不敏感，对非预期的方法调用抛出
AssertionError。除了这种默认的 Mock 类型以外，EasyMock 还提供了一些特殊的 Mock 类型用于支持不同的需求。</p>
<p><a name="chap4.1"><span class="smalltitle">Strick Mock 对象</span></a></p>
<p>如果 Mock 对象是通过 <code>EasyMock.createMock()</code> 或是
<code>IMocksControl.createMock()</code> 所创建的，那么在进行 verify
验证时，方法的调用顺序是不进行检查的。如果要创建方法调用的先后次序敏感的 Mock 对象（Strick Mock），应该使用
<code>EasyMock.createStrickMock()</code> 来创建，例如：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">ResultSet strickMockResultSet = createStrickMock(ResultSet.class);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p>类似于 createMock，我们同样可以用 <code>IMocksControl</code> 实例来创建一个 Strick Mock 对象：
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">IMocksControl control = EasyMock.createStrictControl();<br />
            ResultSet strickMockResultSet = control.createMock(ResultSet.class);<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
</p>
<p><a name="chap4.2"><span class="smalltitle">Nice Mock 对象</span></a></p>
<p>使用 <code>createMock()</code> 创建的 Mock 对象对非预期的方法调用默认的行为是抛出
AssertionError，如果需要一个默认返回0，null 或 false 等"无效值"的 "Nice Mock" 对象，可以通过
<code>EasyMock</code> 类提供的 <code>createNiceMock()</code> 方法创建。类似的，你也可以用
<code>IMocksControl</code> 实例来创建一个 Nice Mock 对象。</p>
<br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><img alt="" src="//www.ibm.com/i/v14/rules/blue_rule.gif" height="1" width="100%" /><br />
            <img alt="" src="//www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td><img alt="" src="//www.ibm.com/i/c.gif" height="4" width="100%" /><br />
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle"><img alt="" src="//www.ibm.com/i/v14/icons/u_bold.gif" border="0" height="16" width="16" /><br />
                        </td>
                        <td align="right" valign="top"><a class="fbox" href="#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="chap5"><span class="atitle">5．EasyMock 的工作原理</span></a></p>
<p>EasyMock 是如何为一个特定的接口动态创建 Mock 对象，并记录 Mock 对象预期行为的呢？其实，EasyMock 后台处理的主要原理是利用
<code>java.lang.reflect.Proxy</code> 为指定的接口创建一个动态代理，这个动态代理，就是我们在编码中用到的 Mock
对象。EasyMock 还为这个动态代理提供了一个 <code>InvocationHandler</code>
接口的实现，这个实现类的主要功能就是将动态代理的预期行为记录在某个映射表中和在实际调用时从这个映射表中取出预期输出。下图是 EasyMock
中主要的功能类：</p>
<br />
<a name="easymock_class.gif"><strong>图4：EasyMock主要功能类</strong></a><br />
<img alt="Mock对象验证失败" src="easymock_class.gif" height="377" width="572" /> <br />
<p>和开发人员联系最紧密的是 <code>EasyMock</code> 类，这个类提供了
<code>createMock、replay、verify</code> 等方法以及所有预定义的参数匹配器。</p>
<p>我们知道 Mock 对象有两种创建方式：一种是通过 <code>EasyMock</code> 类提供的 <code>createMock</code>
方法创建，另一种是通过 <code>EasyMock</code> 类的 <code>createControl</code> 方法得到一个
<code>IMocksControl</code> 实例，再由这个 <code>IMocksControl</code> 实例创建 Mock
对象。其实，无论通过哪种方法获得 Mock 对象，EasyMock 都会生成一个 <code>IMocksControl</code>
的实例，只不过第一种方式中的 <code>IMocksControl</code> 的实例对开发人员不可见而已。这个
<code>IMocksControl</code> 的实例，其实就是 <code>MocksControl</code>
类的一个对象。<code>MocksControl</code> 类提供了
<code>andReturn、andThrow、times、createMock</code> 等方法。</p>
<p><code>MocksControl</code> 类中包含了两个重要的成员变量，分别是接口 <code>IMocksBehavior</code> 和
<code>IMocksControlState</code> 的实例。其中，<code>IMocksBehavior</code> 的实现类
<code>MocksBehavior</code> 是 EasyMock 的核心类，它保存着一个
<code>ExpectedInvocationAndResult</code> 对象的一个列表，而
<code>ExpectedInvocationAndResult</code> 对象中包含着 Mock
对象方法调用和预期结果的映射。<code>MocksBehavior</code> 类提供了 <code>addExpected</code> 和
<code>addActual</code> 方法用于添加预期行为和实际调用。</p>
<p><code>MocksControl</code> 类中包含的另一个成员变量是 <code>IMocksControlState</code>
实例。<code>IMocksControlState</code> 拥有两个不同的实现类：<code>RecordState</code> 和
<code>ReplayState</code>。顾名思义，<code>RecordState</code> 是 Mock 对象在 Record
状态时的支持类，它提供了 <code>invoke</code> 方法在 Record 状态下的实现。此外，它还提供了
<code>andReturn、andThrow、times</code> 等方法的实现。<code>ReplayState</code> 是 Mock 对象在
Replay 状态下的支持类，它提供了 <code>invoke</code> 方法在 Replay 状态下的实现。在 ReplayState
中，<code>andReturn、andThrow、times</code> 等方法的实现都是抛出IllegalStateException，因为在
Replay 阶段，开发人员不应该再调用这些方法。</p>
<p>当我们调用 <code>MocksControl</code> 的 <code>createMock</code> 方法时，该方法首先会生成一个
<code>JavaProxyFactory</code> 类的对象。<code>JavaProxyFactory</code> 是接口
<code>IProxyFactory</code> 的实现类，它的主要功能就是通过 <code>java.lang.reflect.Proxy</code>
对指定的接口创建动态代理实例，也就是开发人员在外部看到的 Mock 对象。</p>
<p>在创建动态代理的同时，应当提供 <code>InvocationHandler</code>
的实现类。<code>MockInvocationHandler</code> 实现了这个接口，它的 <code>invoke</code>
方法主要的功能是根据 Mock 对象状态的不同而分别调用 <code>RecordState</code> 的 <code>invoke</code> 实现或是
<code>ReplayState</code> 的 <code>invoke</code> 实现。</p>
<p><a name="chap5.1"><span class="smalltitle">创建 Mock 对象</span></a></p>
<p>下图是创建 Mock 对象的时序图：</p>
<br />
<a name="createmock_seq.gif"><strong>图5：创建 Mock
对象时序图</strong></a><br />
<img alt="创建 Mock 对象时序图" src="createmock_seq.gif" height="234" width="572" /> <br />
<p>当 <code>EasyMock</code> 类的 <code>createMock</code> 方法被调用时，它首先创建一个
<code>MocksControl</code> 对象，并调用该对象的 <code>createMock</code> 方法创建一个
<code>JavaProxyFactory</code> 对象和一个 <code>MockInvocationHandler</code>
对象。<code>JavaProxyFactory</code> 对象将 <code>MockInvocationHandler</code>
对象作为参数，通过 <code>java.lang.reflect.Proxy</code> 类的 <code>newProxyInstance</code>
静态方法创建一个动态代理。</p>
<p><a name="chap5.2"><span class="smalltitle">记录 Mock 对象预期行为</span></a></p>
<p>记录 Mock 的预期行为可以分为两个阶段：预期方法的调用和预期输出的设定。在外部程序中获得的 Mock 对象，其实就是由
<code>JavaProxyFactory</code> 创建的指定接口的动态代理，所有外部程序对接口方法的调用，都会指向
<code>InvocationHandler</code> 实现类的 <code>invoke</code> 方法。在 EasyMock 中，这个实现类是
<code>MockInvocationHandler</code>。下图是调用预期方法的时序图：</p>
<br />
<a name="recordstate_seq1.gif"><strong>图6：调用预期方法时序图</strong></a><br />
<img alt="调用预期方法时序图" src="recordstate_seq1.gif" height="261" width="572" /> <br />
<p>当 <code>MockInvocationHandler</code> 的 <code>invoke</code> 方法被调用时，它首先通过
<code>reportLastControl</code> 静态方法将 Mock 对象对应的 <code>MocksControl</code> 对象报告给
<code>LastControl</code> 类，<code>LastControl</code> 类将该对象保存在一个 ThreadLocal
变量中。接着，<code>MockInvocationHandler</code> 将创建一个 Invocation 对象，这个对象将保存预期调用的 Mock
对象、方法和预期参数。</p>
<p>在记录 Mock 对象预期行为时，Mock 对象的状态是 Record 状态，因此 <code>RecordState</code> 对象的
<code>invoke</code> 方法将被调用。这个方法首先调用 <code>LastControl</code> 的
<code>pullMatchers</code> 方法获取参数匹配器。如果您还记得自定义参数匹配器的过程，应该能想起参数匹配器被调用时会将实现类的实例报告给
EasyMock，而这个实例最终保存在 <code>LastControl</code> 中。如果没有指定参数匹配器，默认的匹配器将会返回给
<code>RecordState</code>。</p>
<p>根据 <code>Invocation</code> 对象和参数匹配器，<code>RecordState</code> 将创建一个
<code>ExpectedInvocation</code> 对象并保存下来。</p>
<p>在对预期方法进行调用之后，我们可以对该方法的预期输出进行设定。我们以
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">expectLastCall().andReturn(X value).times(int times)<br />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
为例说明。如果 <code>times</code>
方法未被显式的调用，EasyMock 会默认作为 <code>times(1)</code> 处理。下图是设定预期输出的时序图： </p>
<br />
<a name="recordstate_seq2.gif"><strong>图7：设定预期输出时序图</strong></a><br />
<img alt="设定预期输出时序图" src="recordstate_seq2.gif" height="340" width="572" /> <br />
<p>在预期方法被调用时，Mock 对象对应的 <code>MocksControl</code> 对象引用已经记录在
<code>LastControl</code> 中，<code>expectLastCall</code> 方法通过调用
<code>LastControl</code> 的 <code>lastControl</code>
方法可以获得这个引用。<code>MocksControl</code> 对象的 <code>andReturn</code> 方法在 Mock 对象
Record 状态下会调用 <code>RecordState</code> 的 <code>andReturn</code> 方法，将设定的预期输出以
<code>Result</code> 对象的形式记录下来，保存在 <code>RecordState</code> 的 lastResult 变量中。</p>
<p>当 <code>MocksControl</code> 的 <code>times</code> 方法被调用时，它会检查
<code>RecordState</code> 的 lastResult 变量是否为空。如果不为空，则将 lastResult 和预期方法被调用时创建的
<code>ExpectedInvocation</code> 对象一起，作为参数传递给 <code>MocksBehavior</code> 的
<code>addExpected</code> 方法。<code>MocksBehavior</code> 的
<code>addExpected</code> 方法将这些信息保存在数据列表中。</p>
<p><a name="chap5.3"><span class="smalltitle">在 Replay 状态下调用 Mock
对象方法</span></a></p>
<p><code>EasyMock</code> 类的 <code>replay</code> 方法可以将 Mock 对象切换到 Replay 状态。在
Replay 状态下，Mock 对象将根据之前的设定返回预期输出。下图是 Replay 状态下 Mock 对象方法调用的时序图：</p>
<br />
<a name="replaystate_seq.gif"><strong>图8：调用 Mock 对象方法时序图</strong></a><br />
<img alt="调用 Mock 对象方法时序图" src="replaystate_seq.gif" height="244" width="572" /> <br />
<p>在 Replay 状态下，<code>MockInvocationHandler</code> 会调用 <code>ReplayState</code>
的 <code>invoke</code> 方法。该方法会把 Mock 对象通过 <code>MocksBehavior</code> 的
<code>addActual</code> 方法添加到实际调用列表中，该列表在 <code>verify</code>
方法被调用时将被用到。同时，<code>addActual</code> 方法会根据实际方法调用与预期方法调用进行匹配，返回对应的
<code>Result</code> 对象。调用 <code>Result</code> 对象的 <code>answer</code>
方法就可以获取该方法调用的输出。</p>
<br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><img alt="" src="//www.ibm.com/i/v14/rules/blue_rule.gif" height="1" width="100%" /><br />
            <img alt="" src="//www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td><img alt="" src="//www.ibm.com/i/c.gif" height="4" width="100%" /><br />
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle"><img alt="" src="//www.ibm.com/i/v14/icons/u_bold.gif" border="0" height="16" width="16" /><br />
                        </td>
                        <td align="right" valign="top"><a class="fbox" href="#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="chap6"><span class="atitle">6．使用 EasyMock 进行单元测试小结</span></a></p>
<p>如果您需要在单元测试中构建 Mock 对象来模拟协同模块或一些复杂对象，EasyMock 是一个可以选用的优秀框架。EasyMock 提供了简便的方法创建
Mock 对象：通过定义 Mock 对象的预期行为和输出，你可以设定该 Mock
对象在实际测试中被调用方法的返回值、异常抛出和被调用次数。通过创建一个可以替代现有对象的 Mock 对象，EasyMock 使得开发人员在测试时无需编写自定义的
Mock 对象，从而避免了额外的编码工作和因此引入错误的机会。</p>
<br />
<br />
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><img alt="" src="//www.ibm.com/i/v14/rules/blue_rule.gif" height="1" width="100%" /><br />
            <img alt="" src="//www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td><img alt="" src="//www.ibm.com/i/c.gif" height="4" width="100%" /><br />
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle"><img alt="" src="//www.ibm.com/i/v14/icons/u_bold.gif" border="0" height="16" width="16" /><br />
                        </td>
                        <td align="right" valign="top"><a class="fbox" href="#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><span class="atitle"><a name="download">下载</a></span></p>
<table class="data-table-1" border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <th scope="col">描述</th>
            <th scope="col">名字</th>
            <th scope="col">大小</th>
            <th scope="col">下载方法</th>
        </tr>
        <tr>
            <th class="tb-row" scope="row">本文用到的示例代码</th>
            <td nowrap="nowrap">src.zip</td>
            <td nowrap="nowrap">176KB</td>
            <td nowrap="nowrap"><a class="fbox" href="src.zip" cmimpressionsent="1"><strong>HTTP</strong></a></td>
        </tr>
    </tbody>
</table>
<table border="0" cellpadding="0" cellspacing="0">
    <tbody>
        <tr valign="top">
            <td colspan="5"><img alt="" src="//www.ibm.com/i/c.gif" border="0" height="12" width="12" /></td>
        </tr>
        <tr>
            <td><img alt="" src="//www.ibm.com/i/v14/icons/fw.gif" height="16" width="16" /></td>
            <td><a class="fbox" href="/developerworks/cn/whichmethod.html" cmimpressionsent="1">关于下载方法的信息</a></td>
            <td><img alt="" src="//www.ibm.com/i/c.gif" height="1" width="50" /></td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="resources"><span class="atitle">参考资料 </span></a></p>
<strong>学习</strong><br />
<ul>
    <li>如果您想要获得 EasyMock 完整的文档和 API，您可以访问 EasyMock 的主页：<a href="http://www.easymock.org/" cmimpressionsent="1">http://www.easymock.org/</a>。 <br />
    <br />
    </li>
    <li>您可以在 JUnit 的主页上找到完整的文档和相关下载：<a href="http://www.junit.org/index.htm" cmimpressionsent="1">http://www.junit.org/index.htm</a>。 <br />
    <br />
    </li>
    <li>通过 Developer Works 上的文章，您可以和其它的框架做比较：<a href="http://www.ibm.com/developerworks/cn/opensource/os-eclipse-rmock/" cmimpressionsent="1">利用 Eclipse 进行单元测试</a>。
    <br />
    <br />
    </li>
</ul>
<br />
<strong>获得产品和技术</strong><br />
<ul>
    <li>在Source Forge上，你可以下载到最新的 EasyMock 相关代码：<a href="http://sourceforge.net/project/showfiles.php?group_id=82958" cmimpressionsent="1">http://sourceforge.net/project/showfiles.php?group_id=82958</a>。
    <br />
    <br />
    </li>
    <li>Eclipse 的相关下载可以在 <a href="http://www.eclipse.org/" cmimpressionsent="1">http://www.eclipse.org/</a> 上找到。 </li>
</ul>
<br />
<br />
<br />
<img src ="http://www.blogjava.net/jinfeng_wang/aggbug/185422.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2008-03-11 16:16 <a href="http://www.blogjava.net/jinfeng_wang/archive/2008/03/11/185422.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用poi操作Excel的几点注意事项 zz</title><link>http://www.blogjava.net/jinfeng_wang/archive/2008/03/10/185165.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 10 Mar 2008 10:18:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2008/03/10/185165.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/185165.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2008/03/10/185165.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/185165.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/185165.html</trackback:ping><description><![CDATA[http://dev.w3pub.com/content/2007-5-1/2437.html<br />
<br />
<br />
上好的人肉包子新鲜出炉啦，各位妖魔鬼怪赶紧来尝尝鲜&#8230;&#8230;<br />
<br />
首先说说现在我所知道的java编辑Excel文件的两大开源工具：jakarta
poi和JavaExcelAPI（简称JXL），这两套工具我都试用了一这段时间，感觉各有优劣吧。poi在某些细节有些小Bug并且不支持写入图片，其他方面都挺不错的；JXL就惨了，除了支持写入图片外，我暂时看不到它比POI好的地方，我碰到的主要的问题就是对公式支持不是很好，很多带有公式的Excel文件用JXL打开后，公式就丢失了（比如now(),today()），在网上看到其他大虾评论说JXL写入公式也有问题，另外，JXL操作Excel文件的效率比POI低一点。经过比较后，我选择了poi开发我的项目。<br />
<br />
现在我要做的东西基本完成啦，我把这段时间使用poi的一些心得总结出来，希望能对和我遇到相同问题的朋友有所帮助，少熬几个夜，多点时间陪MM：），至于poi基本的使用方法，自己去看文档吧。<br />
<br />
1、设置分页符的bug。<br />
<br />
poi里的HSSFSheet类提供了setRowBreak方法可以设置Sheet的分页符。<br />
<br />
Bug：如果你要设置分页符的Sheet是本来就有的，并且你没有在里面插入过分页符，那么调用setRowBreak时POI会抛出空指针的异常。<br />
<br />
解决方法：在Excel里给这个sheet插入一个分页符，用POI打开后再把它删掉，然后你就可以随意插入分页符了。<br />
<br />
如果sheet是由poi生成的则没有这个问题。我跟踪了setRowBreak的源代码，发现是Sheet.java下的PageBreakRecord
rowBreaks这个变量在搞鬼，如果Sheet里原来没有分页符，开发这个模块的那位兄台忘了为这个对象new实例，所以只能我们先手工给Excel插入一个分页符来触发poi为rowBreaks创建实例。<br />
<br />
2、如何拷贝行。<br />
<br />
我在gmane.org的poi用户论坛翻遍了每个相关的帖子，找遍了api，也没看到一个拷贝行的方法，没办法，只能自己写：<br />
<br />
//注：this.fWorkbook是一个HSSHWorkbook，请自行在外部new<br />
public
void copyRows(String pSourceSheetName, String pTargetSheetName, int pStartRow,
int pEndRow, int pPosition)<br />
{<br />
HSSFRow sourceRow = null;<br />
HSSFRow
targetRow = null;<br />
HSSFCell sourceCell = null;<br />
HSSFCell targetCell =
null;<br />
HSSFSheet sourceSheet = null;<br />
HSSFSheet targetSheet =
null;<br />
Region region = null;<br />
int cType;<br />
int i;<br />
short
j;<br />
int targetRowFrom;<br />
int targetRowTo;<br />
<br />
if ((pStartRow ==
-1) || (pEndRow == -1))<br />
{<br />
return;<br />
}<br />
sourceSheet =
this.fWorkbook.getSheet(pSourceSheetName);<br />
targetSheet =
this.fWorkbook.getSheet(pTargetSheetName);<br />
//拷贝合并的单元格<br />
for (i = 0; i
&lt; sourceSheet.getNumMergedRegions(); i++)<br />
{<br />
region =
sourceSheet.getMergedRegionAt(i);<br />
if ((region.getRowFrom() &gt;= pStartRow)
&amp;&amp; (region.getRowTo() &lt;= pEndRow))<br />
{<br />
targetRowFrom =
region.getRowFrom() - pStartRow + pPosition;<br />
targetRowTo =
region.getRowTo() - pStartRow +
pPosition;<br />
region.setRowFrom(targetRowFrom);<br />
region.setRowTo(targetRowTo);<br />
targetSheet.addMergedRegion(region);<br />
}<br />
}<br />
//设置列宽<br />
for
(i = pStartRow; i &lt;= pEndRow; i++)<br />
{<br />
sourceRow =
sourceSheet.getRow(i);<br />
if (sourceRow != null)<br />
{<br />
for (j =
sourceRow.getFirstCellNum(); j &lt; sourceRow.getLastCellNum();
j++)<br />
{<br />
targetSheet.setColumnWidth(j,
sourceSheet.getColumnWidth(j));<br />
}<br />
break;<br />
}<br />
}<br />
//拷贝行并填充数据<br />
for
(;i &lt;= pEndRow; i++)<br />
{<br />
sourceRow = sourceSheet.getRow(i);<br />
if
(sourceRow == null)<br />
{<br />
continue;<br />
}<br />
targetRow =
targetSheet.createRow(i - pStartRow +
pPosition);<br />
targetRow.setHeight(sourceRow.getHeight());<br />
for (j =
sourceRow.getFirstCellNum(); j &lt; sourceRow.getLastCellNum();
j++)<br />
{<br />
sourceCell = sourceRow.getCell(j);<br />
if (sourceCell ==
null)<br />
{<br />
continue;<br />
}<br />
targetCell =
targetRow.createCell(j);<br />
targetCell.setEncoding(sourceCell.getEncoding());<br />
targetCell.setCellStyle(sourceCell.getCellStyle());<br />
cType
= sourceCell.getCellType();<br />
targetCell.setCellType(cType);<br />
switch
(cType)<br />
{<br />
case
HSSFCell.CELL_TYPE_BOOLEAN:<br />
targetCell.setCellValue(sourceCell.getBooleanCellValue());<br />
break;<br />
case
HSSFCell.CELL_TYPE_ERROR:<br />
targetCell.setCellErrorValue(sourceCell.getErrorCellValue());<br />
break;<br />
case
HSSFCell.CELL_TYPE_FORMULA:<br />
//parseFormula这个函数的用途在后面说明<br />
targetCell.setCellFormula(parseFormula(sourceCell.getCellFormula()));<br />
break;<br />
case
HSSFCell.CELL_TYPE_NUMERIC:<br />
targetCell.setCellValue(sourceCell.getNumericCellValue());<br />
break;<br />
case
HSSFCell.CELL_TYPE_STRING:<br />
targetCell.setCellValue(sourceCell.getStringCellValue());<br />
break;<br />
}<br />
}<br />
}<br />
}<br />
<br />
这个函数有两个问题暂时无法解决：<br />
<br />
a、只能在同一个Workbook里面使用，跨Workbook总是拷不过去，不知道为什么？<br />
<br />
b、由于在拷贝行时也把行高也拷过去了，如果往这些单元格里写入的数据长度超过单元格长度，那么他们不会自动调整行高！<br />
<br />
有哪位大侠知道上面两个问题任意一个的解决方法，请第一时间通知我！！！<br />
<br />
3、公式的问题。<br />
<br />
POI对Excel公式的支持是相当好的，但是我发现一个问题，如果公式里面的函数不带参数，比如now()或today()，那么你通过getCellFormula()取出来的值就是now(ATTR(semiVolatile))和today(ATTR(semiVolatile))，这样的值写入Excel是会出错的，这也是我上面copyRow的函数在写入公式前要调用parseFormula的原因，parseFormula这个函数的功能很简单，就是把ATTR(semiVolatile)删掉，我把它的代码贴出来：<br />
private
String parseFormula(String pPOIFormula)<br />
{<br />
final String
cstReplaceString = "ATTR(semiVolatile)"; //$NON-NLS-1$<br />
StringBuffer result
= null;<br />
int index;<br />
<br />
result = new StringBuffer();<br />
index =
pPOIFormula.indexOf(cstReplaceString);<br />
if (index &gt;=
0)<br />
{<br />
result.append(pPOIFormula.substring(0,
index));<br />
result.append(pPOIFormula.substring(index +
cstReplaceString.length()));<br />
}<br />
else<br />
{<br />
result.append(pPOIFormula);<br />
}<br />
<br />
return
result.toString();<br />
}<br />
至于为什么会出现ATTR(semiVolatile)，希望哪位大侠现身跟我解释一下。<br />
<br />
4、向Excel写入图片的问题。<br />
<br />
我上poi论坛查相关帖子，得到两种结论：1、不支持写入图片；2、支持写入图片，通过EscherGraphics2d这个Class实现。于是我就去查EscherGraphics2d这个Class，发现这个Class提供了N个drawImage方法，喜出望外的我开始写代码，结果调了一天，一直看不到效果，黔驴技穷的我在万般无奈下只好跟踪进drawImage这个函数内部，经过N个函数调用后在最底层函数发现了最终答案（偶当场暴走！！！）：<br />
<br />
public
boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int
sy1,<br />
int sx2, int sy2, Color bgColor, ImageObserver
imageobserver)<br />
{<br />
if (logger.check( POILogger.WARN
))<br />
logger.log(POILogger.WARN,"drawImage() not supported");<br />
return
true;<br />
}<br />
所以我强烈建议大家，以后使用第三方开发包一定尽量下载它的源代码，这样你在碰到问题时，看看它的的内部是怎么实现的，很多时候就可以不必重蹈我的覆辙了。既然POI不能写入图片，那我们只能把目光投向JXL，我用JXL写入图片功能是实现了，付出的代价是now()和today()这些函数丢失掉了，鱼与熊掌不能兼得吧，至于怎么解决JXL公式的问题，到下面这个帖子里和我一起守株待兔吧：<br />
<br />
http://community.csdn.net/Expert/topic/3569/3569340.xml?temp=.8480646<br />
<br />
好了我所知道得就这些，我接触poi也才几星期时间，上面有些内容可能说得并不正确，希望各位大虾砸砖指正！<br />
<br />
<img src ="http://www.blogjava.net/jinfeng_wang/aggbug/185165.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2008-03-10 18:18 <a href="http://www.blogjava.net/jinfeng_wang/archive/2008/03/10/185165.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用JXL读取Excel表格,拷贝、更新Excel工作薄 zz</title><link>http://www.blogjava.net/jinfeng_wang/archive/2008/03/10/185162.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 10 Mar 2008 10:15:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2008/03/10/185162.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/185162.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2008/03/10/185162.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/185162.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/185162.html</trackback:ping><description><![CDATA[http://www.javaresearch.org/article/45784.htm <br />
<br />
<br />
/**<br />
*&nbsp;&lt;p&gt;读取Excel表格,拷贝、更新Excel工作薄&nbsp;&lt;/p&gt;<br />
*&nbsp;&lt;p&gt;Description:&nbsp;可以读取Excel文件的内容,更新Excel工作薄<br />
*&nbsp;&lt;/p&gt;<br />
*&nbsp;&lt;p&gt;Copyright:&nbsp;Copyright&nbsp;(c)&nbsp;Corparation&nbsp;2005&lt;/p&gt;<br />
*&nbsp;&lt;p&gt;程序开发环境为eclipse&lt;/p&gt;<br />
*&nbsp;@author&nbsp;Walker<br />
*&nbsp;@version&nbsp;1.0<br />
*/<br />
package&nbsp;cn.com.yitong.xls;<br />
<br />
import&nbsp;java.io.File;<br />
import&nbsp;java.io.FileInputStream;<br />
import&nbsp;java.io.InputStream;<br />
import&nbsp;java.util.Vector;<br />
<br />
import&nbsp;cn.com.yitong.ChartImg;<br />
import&nbsp;cn.com.yitong.VireObj;<br />
import&nbsp;cn.com.yitong.platform.log.YTLogger;<br />
<br />
import&nbsp;jxl.CellType;<br />
import&nbsp;jxl.Workbook;<br />
import&nbsp;jxl.format.CellFormat;<br />
import&nbsp;jxl.format.Colour;<br />
import&nbsp;jxl.format.UnderlineStyle;<br />
import&nbsp;jxl.write.Formula;<br />
import&nbsp;jxl.write.Label;<br />
import&nbsp;jxl.write.Number;<br />
import&nbsp;jxl.write.WritableCell;<br />
import&nbsp;jxl.write.WritableCellFormat;<br />
import&nbsp;jxl.write.WritableFont;<br />
import&nbsp;jxl.write.WritableImage;<br />
import&nbsp;jxl.write.WritableSheet;<br />
import&nbsp;jxl.write.WritableWorkbook;<br />
import&nbsp;jxl.write.WriteException;<br />
import&nbsp;jxl.write.biff.RowsExceededException;<br />
<br />
public&nbsp;class&nbsp;XLSDemo<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;static&nbsp;final&nbsp;int&nbsp;TITLE_LENGTH&nbsp;=&nbsp;7;<br />
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;static&nbsp;final&nbsp;int&nbsp;SHEET_WIDTH&nbsp;=&nbsp;32;<br />
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;static&nbsp;final&nbsp;int&nbsp;SHEET_HEIGHT&nbsp;=&nbsp;116;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;/**<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;创建Excel<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;void&nbsp;makeXls()<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Workbook&nbsp;workbook&nbsp;=&nbsp;null;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;构建Workbook对象,&nbsp;只读Workbook对象<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;直接从本地文件创建Workbook,&nbsp;从输入流创建Workbook<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InputStream&nbsp;ins&nbsp;=&nbsp;new&nbsp;FileInputStream("D:/Workspace/testproj/source.xls");<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;workbook&nbsp;=&nbsp;Workbook.getWorkbook(ins);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n