﻿<?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-Vincent-随笔分类-Apache jakarta</title><link>http://www.blogjava.net/lijiajia418/category/14361.html</link><description>Vicent's blog</description><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 03:45:39 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 03:45:39 GMT</pubDate><ttl>60</ttl><item><title>Log4J学习笔记</title><link>http://www.blogjava.net/lijiajia418/archive/2006/09/01/67072.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Fri, 01 Sep 2006 05:25:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/09/01/67072.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/67072.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/09/01/67072.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/67072.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/67072.html</trackback:ping><description><![CDATA[
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<p align="left">一、简介<br />　　在程序中输出信息的目的有三：一是监视程序运行情况；一是将程序的运行情况记录到日志文件中，以备将来查看；一是做为调试器。但信息输出的手段不仅限于System.out.println()或System.out.print()，还有日志记录工具可以选择。与System.out.pringln()和System.out.print()相比，日志记录工具可以控制输出级别，并且可以在配置文件中对输出级别进行设置，这样开发阶段的信息在程序发布后就可以通过设置输出级别来消除掉，而无须对代码进行修正了。现在流行的日志记录工具很多， Log4J就是其中的佼佼者。<br />　　Log4J是由著名开源组织Apache推出的一款日志记录工具，供Java编码人员做日志输出之用，可以从网站<a href="http://logging.apache.org/log4j">http://logging.apache.org/log4j</a>上免费获得，最新版本1.2.11。获得logging-log4j-1.2.11.zip文件后，解压缩，需要的是其中的log4j-1.2.11.jar文件，将该文件放到特定的文件夹中备用，我放到了我机器的G:\YPJCCK\Log4J\lib文件夹中。<br />　　这里选择的IDE是Eclipse和JBuilder。Eclipse用的是3.0.1加语言包，可以到<a href="http://www.eclipse.org/">www.eclipse.org</a>网站上下载；JBuilder用的是JBuilder 2005。<br />二、配置类库<br />　　下面打开Eclipse或JBuilder。<br />　　如果使用的是Eclipse，那么在Eclipse打开后，点击菜单"文件"-&gt;"新建"-&gt;"项目"，打开"新建项目"对话框：</p>
										<p align="center">
												<a href="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl01.JPG" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="ZOOM: 110%" height="285" alt="" src="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl01.JPG" width="300" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
												</a>
										</p>
										<p align="left">请选中"Java项目"，点击"下一步"，进入"新建Java项目"对话框： </p>
										<p align="center">
												<a href="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl02.JPG" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" height="307" alt="" src="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl02.JPG" width="300" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
												</a>
										</p>
										<p align="left">在这个对话框中需要设置项目的名称以及项目所在目录，我为自己的项目起名为Log4JTest，目录为G:\YPJCCK\Log4J\Eclipse\ Log4JTest。设置好后点击"下一步"，进入下一个窗口。在这个窗口中选择名为"库"的选项卡，然后点击"添加外部JAR"按钮，将保存于特定文件夹中的log4j-1.2.11.jar文件引用进来。</p>
										<p align="center">
												<a href="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl03.JPG" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" height="307" alt="" src="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl03.JPG" width="300" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
												</a>
										</p>
										<p align="left">设置好后，点击"完成"，至此，已经具备了在Eclipse下使用Log4J的环境。<br />　　如果使用的是JBuilder，那么在JBuilder打开后，点击菜单"Tools"-&gt;"Configure" -&gt;"Libraries"，打开"Configure Libraries"对话框：</p>
										<p align="center">
												<a href="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl04.JPG" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" height="174" alt="" src="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl04.JPG" width="300" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
												</a>
										</p>
										<p align="left">点击"New"按钮，打开"New Library Wizard"对话框：</p>
										<p align="center">
												<a href="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl05.JPG" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" height="222" alt="" src="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl05.JPG" width="300" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
												</a>
										</p>
										<p align="left">使用"Add"按钮将保存于特定文件夹中的log4j-1.2.11.jar文件引用进来，并设置Name，即该类库的名字，我将Name设置为 Log4J。设置好后点击"OK"按钮，回到"Configure Libraries"对话框，再点击"OK"按钮，则JUnit类库已经被添加到JBuilder当中。<br />　　下面继续，在JBuilder中创建项目。点击菜单"File"-&gt;"New Project"，打开"Project Wizard"对话框：</p>
										<p align="center">
												<a href="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl06.JPG" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" height="212" alt="" src="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl06.JPG" width="300" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
												</a>
										</p>
										<p align="left">在这个窗口中设置项目名称及存放目录，我的项目名称仍为Log4JTest，路径为G:/YPJCCK/log4J/JBuilder/Log4JTest。点击"Next"进入下一个窗口：</p>
										<p align="center">
												<a href="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl07.JPG" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" height="242" alt="" src="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl07.JPG" width="300" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
												</a>
										</p>
										<p align="left">在这个窗口中选择"Required Libraries"选项卡，点击"Add"按钮，将刚才设置的JUnit库引用进来。然后点击"Next"按钮，进入下一个窗口：</p>
										<p align="center">
												<a href="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl08.JPG" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" height="263" alt="" src="http://blog.csdn.net/images/blog_csdn_net/didizyp/jl08.JPG" width="300" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
												</a>
										</p>
										<p align="left">在这个窗口中用鼠标点击Encoding下拉列表框，然后按一下"G"键，选中相应选项，此时该项目的字符集就被设置成GBK了。如果做的是国内项目，这绝对是个好习惯。最后点击"Finish"，项目创建完成。<br />三、编写一个简单的示例<br />　　在了解Log4J的使用方法之前，先编写一个简单的示例，以对Log4J有个感性认识。<br />如果使用的是Eclipse，请点击"文件"-&gt;"新建"-&gt;"类"，打开"新建Java类"对话框，设置包为 piv.zheng.log4j.test，名称为Test，并确保"public static void main(String[] args)"选项选中；如果使用的是JBuilder，请点击"File"-&gt;"New Class"，打开"Class Wizard"对话框，设置Package为piv.zheng.log4j.test，Class name为Test，并确保"Generate main method"选项选中。设置完成后，点击"OK"。代码如下：<br />　　package piv.zheng.log4j.test;<br />　　<br />　　import org.apache.log4j.Logger;<br />　　import org.apache.log4j.Level;<br />　　import org.apache.log4j.SimpleLayout;<br />　　import org.apache.log4j.ConsoleAppender;<br />　　<br />　　public class Test {<br />　　　　<br />　　　　public static void main(String[] args) {<br />　　　　　　SimpleLayout layout = new SimpleLayout();<br />　　　　　　<br />　　　　　　ConsoleAppender appender = new ConsoleAppender(layout);<br />　　　　　　<br />　　　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　　　log.addAppender(appender);<br />　　　　　　log.setLevel(Level.FATAL);<br />　　　　　　<br />　　　　　　log.debug("Here is DEBUG");<br />　　　　　　log.info("Here is INFO");<br />　　　　　　log.warn("Here is WARN");<br />　　　　　　log.error("Here is ERROR");<br />　　　　　　log.fatal("Here is FATAL");<br />　　　　}<br />　　}<br />至此，示例编写完成。请点击运行按钮旁边的倒三角，选择"运行为"-&gt;"2 Java应用程序"（Eclipse），或者在Test类的选项卡上点击鼠标右键，在调出的快捷菜单中点击"Run using defaults"（JBuilder），运行程序，观察从控制台输出的信息。<br />四、Log4J入门<br />　　看过程序的运行效果后可能会奇怪，为何控制台只输出了"FATAL - Here is FATAL"这样一条信息，而程序代码中的log.debug()、log.info()等方法也都设置了类似的内容，却没有被输出？其实答案很简单，但在公布之前，先来了解一下Log4J的使用。<br />　　请先看前边的示例代码，会发现，示例中用到了Logger、Level、 ConsoleAppender、SimpleLayout等四个类。其中Logger类使用最多，甚至输出的信息也是在其对象log的fatal方法中设置的，那么Logger究竟是做什么的呢？其实Logger就是传说中的日志记录器（在Log4J中称为Category），创建方法有三：<br />　　1．根Category，默认创建，获取方法：</p>
										<p align="center">Logger log = Logger.getRootLogger();</p>
										<p>　　2．用户创建的Category，方法：</p>
										<p align="center">Logger log = Logger.getLogger("test");</p>
										<p>其中字符串test是为Category设定的名称。Category的名称允许使用任何字符，但区分大小写，例如：</p>
										<p align="center">Logger l1 = Logger.getLogger("x");<br />Logger l2 = Logger.getLogger("X");</p>
										<p>l1和l2就是两个Category；而如果名称完全相同，例如：</p>
										<p align="center">Logger l1 = Logger.getLogger("x");<br />Logger l2 = Logger.getLogger("x");</p>
										<p>l1和l2就是同一个Category。此外，符号"."在Category的名称中有特殊作用，这一点将在后边介绍。<br />　　3．与方法2类似，只是参数由字符串换成了类对象，其目的是通过类对象获取类的全名。这个方法比较常用，示例中使用的就是这个方法。<br />　　那么Category是如何输出信息的呢？其实示例中用到的debug、info、warn、error、fatal等五个方法都是用来输出信息的。什么，怎么这么多？原因很简单，Log4J支持分级输出。Log4J的输出级别有五个，由低到高依次是DEBUG（调试）、INFO（信息）、WARN（警告）、ERROR（错误）和FATAL（致命），分别与以上方法对应。当输出级别设置为DEBUG时，以上方法都能够输出信息，当输出级别设置为INFO 时，则只有debug方法将不能再输出信息，依此类推，当输出级别设置为FATAL时，就只有fatal方法可以输出信息了。现在再回头看前边的问题，为何只有设置给fatal方法的信息被输出就不难理解了，示例中有这样一行代码：</p>
										<p align="center">log.setLevel(Level.FATAL);</p>
										<p>正是这行代码将log对象的输出级别设成了FATAL。在为log对象设置输出级别时用到了Level类，该类中定义了DEBUG、INFO、WARN、 ERROR、FATAL等五个静态对象，与五个输出级别相对应。此外，Level还有两个特殊的静态对象ALL和OFF，前者允许所有的方法输出信息，其级别其实比DEBUG还低；后者则会禁止所有的方法输出信息，其级别比FATAL要高。除前边示例中用到的五个方法，Logger还提供了这五个方法的重载，以在输出信息的同时抛出异常，以fatal方法为例：</p>
										<p align="center">log.fatal("Here is FATAL", new Exception("Exception"));</p>
										<p>执行后输出信息：<br />　　FATAL - Here is FATAL<br />　　java.lang.Exception: Exception<br />　　　　at piv.zheng.log4j.test.Test.main(Test.java:24)<br />其他方法类似。此外，Logger还提供了log方法，该方法不针对任何输出级别，需要在调用时设置，例如：</p>
										<p align="center">log.log(Level.FATAL, "Here is FATAL");<br />log.log(Level.FATAL, "Here is FATAL", new Exception("Exception"));</p>
										<p>虽然一般情况下log方法不如其它方法方便，但由于允许设置级别，因此log方法在很多时候反而比其它方法更灵活，甚至可以在输出级别为OFF时输出信息。不过log方法主要是给用户自定义的输出级别用的，而且设立OFF输出级别的目的也为了不输出任何信息，因此请不要在log方法中使用OFF来输出信息。<br />　　此外，Category的输出级别并非必须，若未设置，子Category会默认使用其父Category的输出级别，若父Category也没设置，就使用再上一级Category的设置，直到根Category为止。根Category默认输出级别为DEBUG，因此在示例中，若将 "log.setLevel(Level.FATAL);"一行注释掉，则所有方法都会输出信息。<br />　　下面简单介绍一下Log4J中 Category的继承关系。其实在Log4J中Category之间是存在继承关系的，根Category默认创建，是级别最高的Category，用户创建的Category均继承自它。而用户创建的Category之间也存在继承关系，例如：</p>
										<p align="center">Logger lx = Logger.getLogger("x");<br />Logger lxy = Logger.getLogger("xy");<br />Logger lx_y = Logger.getLogger("x.y");<br />Logger lx_z = Logger.getLogger("x.z");<br />Logger lx_y_z = Logger.getLogger("x.y.z");</p>
										<p>其中的lx_y、lx_z就是lx的子Category，而lx_y_z是lx_y的子Category。但lxy并不是lx的子Category。也许有点乱，下面来一个一个看。首先看与lx_y、lx_z对应的Category的名称"x.y"和"x.z"，"."前边的是什么，"x"，这说明与名称为 "x"的Category对应lx就是它们的父Category；而与lx_y_z对应的Category的名称"x.y.z"，最后一个"."前边的是什么，"x.y"，这说明lx_y是lx_y_z的父Category；至于lxy，由于与之对应的Category名称"xy"之间没有"."，因此它是一个与lx同级的Category，其父Category就是根Category器。此外还有一种情况，例如有一个名称为"a.b"的 Category，如果没有名称为"a"的Category，那么它的父Category也是根Category。前边说过，"."在Category名称中有特殊作用，其实它的作用就是继承。至此，为何使用类对象来创建Category也就不难理解了。<br />　　可是，仅有Category是无法完成信息输出的，还需要为Category添加Appender，即Category的输出源。前边的例子使用的是ConsoleAppender，即指定 Category将信息输出到控制台。其实Log4J提供的Appender有很多，这里选择几常用的进行介绍。<br />　　1．org.apache.log4j.WriterAppender，可以根据用户选择将信息输出到Writer或OutputStream。<br />　　示例代码：<br />　　　　SimpleLayout layout = new SimpleLayout ();<br />　　　　<br />　　　　//向文件中输出信息，OutputStream示例<br />　　　　WriterAppender appender1 = null;<br />　　　　try {<br />　　　　　　appender1 = new WriterAppender(layout, new FileOutputStream("test.txt"));<br />　　　　}<br />　　　　catch(Exception ex) {}<br />　　　　<br />　　　　//向控制台输出信息，Writer示例<br />　　　　WriterAppender appender2 = null;<br />　　　　try {<br />　　　　　　appender2 = new WriterAppender(layout, new OutputStreamWriter(System.out));<br />　　　　}<br />　　　　catch(Exception ex) {}<br />　　　　<br />　　　　//Category支持同时向多个目标输出信息<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender1);<br />　　　　log.addAppender(appender2);<br />　　　　<br />　　　　log.debug("output");<br />这个示例由第一个示例修改而来，没有设置输出级别，而且向Category中添加了两个输出源，运行后会在控制台中输出"DEBUG - output"，并在工程目录下生成test.txt文件，该文件中也记录着"DEBUG - output"。若要将test.txt文件放到其它路径下，例如f:，则将"test.txt"改为"f:/test.txt"，又如e:下的temp 文件夹，就改为"e:/temp/test.txt"。后边FileAppender、RollingFileAppender以及 DailyRollingFileAppender设置目标文件时也都可以这样来写。<br />　　2．org.apache.log4j.ConsoleAppender，向控制台输出信息，继承了WriterAppender，前边的示例使用的就是它。<br />　　3．org.apache.log4j.FileAppender，向文件输出信息，也继承了WriterAppender。<br />　　示例代码：<br />　　　　SimpleLayout layout = new SimpleLayout();<br />　　　　<br />　　　　//若文件不存在则创建文件，若文件已存在则向文件中追加信息<br />　　　　FileAppender appender = null;<br />　　　　try {<br />　　　　　　appender = new FileAppender(layout, "test.txt");<br />　　　　} catch(Exception e) {}<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />这个示例也由第一个示例修改而来，运行后会在工程目录下生成test.txt文件，该文件中记录着"DEBUG - output"。再次运行程序，查看文件，则"DEBUG - output"有两行。<br />　　另外，FileAppender还有一个构造：</p>
										<p align="center">FileAppender(Layout layout, String filename, boolean append)</p>
										<p>与示例的类似，只是多了一个boolean型的参数append。append参数是个开关，用来设置当程序重启，而目标文件已存在时，是向目标文件追加信息还是覆盖原来的信息，当值为true时就追加，这是FileAppender默认的，当值为false时则覆盖。此外，FileAppender还提供了setAppend方法来设置append开关。<br />　　4．org.apache.log4j.RollingFileAppender，继承了 FileAppender，也是向文件输出信息，但文件大小可以限制。当文件大小超过限制时，该文件会被转为备份文件或删除，然后重新生成。文件的转换或删除与设置的备份文件最大数量有关，当数量大于0时就转为备份文件，否则（小于等于0）删除，默认的备份文件数量是1。转换备份文件非常简单，就是修改文件名，在原文件名之后加上".1"，例如文件test.txt，转为备份文件后文件名为"test.txt.1"。但若同名的备份文件已存在，则会先将该备份文件删除或更名，这也与设置的备份文件最大数量有关，若达到最大数量就删除，否则更名。若备份文件更名时也遇到同样情况，则使用同样的处理方法，依此类推，直到达到设置的备份文件最大数量。备份文件更名也很简单，就是将扩展名加1，例如test.txt.1文件更名后变为test.txt.2， test.txt.2文件更名后变为test.txt.3。<br />　　示例代码：<br />　　　　SimpleLayout layout = new SimpleLayout();<br />　　　　<br />　　　　//若文件不存在则创建文件，若文件已存在则向文件中追加内容<br />　　　　RollingFileAppender appender = null;<br />　　　　try {<br />　　　　　　appender = new RollingFileAppender(layout, "test.txt");<br />　　　　} catch(Exception e) {}<br />　　　　//限制备份文件的数量，本例为2个<br />　　　　appender.setMaxBackupIndex(2);<br />　　　　//限制目标文件的大小，单位字节，本例为10字节<br />　　　　appender.setMaximumFileSize(10);<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　<br />　　　　log.debug("output0");<br />　　　　log.debug("output1");<br />　　　　log.debug("output2");<br />程序运行后，会在工程目录下生成test.txt、test.txt.1和test.txt.2三个文件，其中test.txt内容为空，而后两个文件则分别记录着"DEBUG - output2"和"DEBUG - output1"，这是怎么回事？原来由于目标文件大小被限制为10字节，而三次使用log.debug方法输出的信息都超过了10字节，这样就导致了三次备份文件转换，所以test.txt内容为空。而备份文件最大数量被设为2，因此第一次转换的备份文件就被删掉了，而后两次的则保存下来。此外，由于 test.txt转换备份文件时是先转为test.txt.1，再转为test.txt.2，因此最后test.txt.2的内容是"DEBUG - output1"，而test.txt.1是"DEBUG - output2"，这点千万别弄混了。<br />　　另外，RollingFileAppender还提供了两个方法：<br />　　（1）setMaxFileSize，功能与setMaximumFileSize一样，但参数是字符串，有两种情况：一是仅由数字组成，默认单位为字节，例如"100"，即表示限制文件大小为100字节；一是由数字及存储单位组成，例如"1KB"、"1MB"、"1GB"，其中单位不区分大小写，分别表示限制文件大小为1K、1M、1G。<br />　　（2）rollOver，手动将目标文件转换为备份文件，使用起来较灵活，适用于复杂情况。<br />　　示例代码：<br />　　　　SimpleLayout layout = new SimpleLayout();<br />　　　　<br />　　　　RollingFileAppender appender = null;<br />　　　　try {<br />　　　　　　appender = new RollingFileAppender(layout, "test.txt");<br />　　　　} catch(Exception e) {}<br />　　　　appender.setMaxBackupIndex(2);<br /><br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　<br />　　　　log.debug("output0");<br />　　　　appender.rollOver();<br />　　　　log.debug("output1");<br />　　　　appender.rollOver();<br />　　　　log.debug("output2");<br />　　　　appender.rollOver();<br />这里没限制目标文件大小，但程序运行后，效果与上例相同。<br />　　5．org.apache.log4j.DailyRollingFileAppender，也继承了FileAppender，并且也是向文件输出信息，但会根据设定的时间频率生成备份文件。<br />　　时间频率格式简介：<br />　　'.'yyyy-MM，按月生成，生成时间为每月最后一天午夜过后，例如test.txt在2005年7月31日午夜过后会被更名为test.txt.2005-07，然后重新生成。<br />　　'.'yyyy-ww，按周生成，生成时间为每周六午夜过后，例如test.txt在2005年8月13日午夜过后会被更名为test.txt.2005-33，33表示当年第33周。<br />　　'.'yyyy-MM-dd，按天生成，生成时间为每天午夜过后，例如2005年8月16日午夜过后，test.txt会被更名为test.txt.2005-08-16。<br />　　'.'yyyy-MM-dd-a，也是按天生成，但每天会生成两次，中午12:00过后一次，午夜过后一次，例如test.txt在2005年8月16 日12:00过后会被更名为test.txt.2005-8-16-上午，午夜过后会被更名为test.txt.2005-8-16-下午。<br />　　'.'yyyy-MM-dd-HH，按小时生成，例如test.txt在2005年8月16日12:00过后会被更名为test.txt.2005-8-16-11。<br />　　'.'yyyy-MM-dd-HH-mm，按分钟生成，例如test.txt在2005年8月16日12:00过后会被更名为test.txt.2005-8-16-11-59。<br />　　示例代码：<br />　　　　SimpleLayout layout = new SimpleLayout();<br />　　　　<br />　　　　DailyRollingFileAppender appender = null;<br />　　　　try {<br />　　　　　　appender = new DailyRollingFileAppender(layout, "test.txt", "'.'yyyy-MM-dd-HH-mm");<br />　　　　} catch(Exception e) {}<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />编码完成后运行程序，等一分钟后再次运行，由于我是在2005年8月17日15:42分第一次运行程序的，因此工程目录下最终有两个文件test.txt和test.txt.2005-08-17-15-42。<br />　　6．org.apache.log4j.AsyncAppender，用于管理不同类型的Appender，也能实现同时向多个源输出信息，但其执行是异步的。<br />　　示例代码：<br />　　　　SimpleLayout layout = new SimpleLayout();<br />　　　　<br />　　　　//向控制台输出<br />　　　　ConsoleAppender appender1 = null;<br />　　　　try {<br />　　　　　　appender1 = new ConsoleAppender(layout);<br />　　　　} catch(Exception e) {}<br />　　　　<br />　　　　//向文件输出<br />　　　　FileAppender appender2 = null;<br />　　　　try {<br />　　　　　　appender2 = new FileAppender(layout, "test.txt");<br />　　　　} catch(Exception e) {}<br />　　　　<br />　　　　//使用AsyncAppender实现同时向多个目标输出信息<br />　　　　AsyncAppender appender = new AsyncAppender();<br />　　　　appender.addAppender(appender1);<br />　　　　appender.addAppender(appender2);<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />此外，AsyncAppender和Logger都提供了更多的方法来管理Appender，例如getAppender、 getAllAppenders、removeAppender和removeAllAppenders，分别用来获取指定的Appender、获取全部 Appender、移除指定的Appender以及移除全部Appender。<br />　　7．org.apache.log4j.jdbc.JDBCAppender，将信息输出到数据库。<br />　　示例代码：<br />　　　　JDBCAppender appender = new JDBCAppender();<br />　　　　appender.setDriver("com.mysql.jdbc.Driver");<br />　　　　appender.setURL("jdbc:mysql://localhost:3306/zheng");<br />　　　　appender.setUser("root");<br />　　　　appender.setPassword("11111111");<br />　　　　appender.setSql("insert into log4j (msg) values ('%m')");<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />这里使用的数据库是MySQL 5.0.4beta，用户名root，密码11111111，我在其中建了一个库zheng，包含表log4j，该表只有一个字段msg，类型为varchar(300)。此外，本例用到的JDBC驱动可以从<a href="http://dev.mysql.com/downloads/connector/j/3.1.html">http://dev.mysql.com/downloads/connector/j/3.1.html</a>下载，版本3.1.8a，下载mysql-connector-java-3.1.8a.zip文件后解压缩，需要其中的mysql-connector- java-3.1.8-bin.jar文件。下面再来看代码。由于JDBCAppender内部默认使用PatternLayout格式化输出信息，因此这里没用到SimpleLayout，而appender.setSql所设置的SQL语句就是PatternLayout所需的格式化字符串，故此其中才有"%m"这样的字符，有关PatternLayout的具体内容后边介绍。执行后，表log4j增加一条记录，内容为"output"。<br />　　8．org.apache.log4j.nt.NTEventLogAppender，向Windows NT系统日志输出信息。<br />　　示例代码：<br />　　　　SimpleLayout layout = new SimpleLayout();<br />　　　　<br />　　　　NTEventLogAppender appender = new NTEventLogAppender("Java", layout);<br /><br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />注意，要完成此示例，还需向C:\WINNT\system32文件夹（我的操作系统装在了C:\）中复制一个名为 NTEventLogAppender.dll的文件。如果跟我一样用的是Log4J 1.2.11，实在对不住，Log4J 1.2.11并未提供该文件。虽然logging-log4j-1.2.11.zip文件解压缩后，其下的src\java\org\apache\ log4j\nt文件夹中有一个make.bat文件执行后可以编译出该文件，但还需要配置，很麻烦。还好，条条大道通罗马，1.2.11不行，就换 1.2.9，可以从<a href="http://apache.justdn.org/logging/log4j/1.2.9">http://apache.justdn.org/logging/log4j/1.2.9</a>下载，下载后解压缩logging-log4j-1.2.9.zip文件，在其下的src\java\org\apache\log4j\nt文件夹中找到 NTEventLogAppender.dll，复制过去就可以了。程序执行后，打开"事件查看器"，选择"应用程序日志"，其中有一条来源为Java的记录，这条记录就是刚才输出的信息了。<br />　　9．org.apache.log4j.lf5.LF5Appender，执行时会弹出一个窗口，信息在该窗口中以表格的形式显示。<br />　　示例代码：<br />　　　　LF5Appender appender = new LF5Appender();<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />由于LF5Appender不需要Layout格式化输出信息，因此这里没有设置。此外LF5Appender还提供了一个setMaxNumberOfRecords方法，用来限制信息在表格中显示的行数。<br />　　10．org.apache.log4j.net.SocketAppender，以套接字方式向服务器发送日志，然后由服务器将信息输出。<br />　　示例代码：<br />　　//指定要连接的服务器地址及端口，这里使用的是本机9090端口<br />　　SocketAppender appender = new SocketAppender("localhost", 9090);<br />　　Logger log = Logger.getLogger(Test.class);<br />　　log.addAppender(appender);<br />　　log.debug("output");<br />SocketAppender不需要设置Layout，因为SocketAppender不负责输出信息。那么如何看到信息输出的效果呢？这就需要SocketServer和SimpleSocketServer了。<br />　　示例代码1：<br />　　　　package piv.zheng.log4j.test;<br />　　　　<br />　　　　import org.apache.log4j.net.SocketServer;<br />　　　　<br />　　　　public class TestServer {<br />　　　　　　public static void main(String[] args) {<br />　　　　　　　　SocketServer.main(new String[]{"9090", "test.properties", "G:/YPJCCK/Log4J"});<br />　　　　　　}<br />　　　　}<br />这是SocketServer的示例。SocketServer只有一个静态方法main，该方法意味着SocketServer不仅可以在代码中被调用，也可以用java命令执行。main方法只有一个参数，是个字符串数组，但要求必须有三个元素：元素一用来指定端口，本例为9090；元素二用来指定输出信息时需要的配置文件，该文件放在工程目录下，本例使用的test.properties内容如下：<br />　　log4j.rootLogger=, console<br />　　log4j.appender.console =org.apache.log4j.ConsoleAppender<br />　　log4j.appender.console.layout=org.apache.log4j.SimpleLayout<br />该配置指定SocketServer使用ConsoleAppender以SimpleLayout格式输出信息；元素三用来指定一个路径，以存放.lcf 文件，我指定的是本机的G:/YPJCCK/Log4J文件夹。.lcf文件也是输出信息时使用的配置文件，格式与元素二所指定的配置文件一样，但 test.properties是默认配置文件，即当.lcf文件找不到时才使用。那么.lcf文件如何命名呢？其实.lcf文件的名称并不是随意起的，当SocketAppender与SocketServer建立连接时，SocketServer就会获得SocketAppender所在计算机的IP 地址与网络ID，并将其格式化成"网络ID/IP地址"这样的字符串，然后获取其中的网络ID作为.lcf文件的主名，例如 "zhengyp/127.0.0.1"，其中的"zhengyp"就是主文件名，而后再根据这个文件名来调用相应的.lcf文件。这意味着对不同的计算机可以提供不同的配置文件，使信息输出时有不同的效果。此外，SocketServer还默认了一个名为generic.lcf的文件，用于处理网络ID 获取不到或其他情况，本例是用的就是这个文件，内容如下：<br />　　log4j.rootLogger=, console<br />　　log4j.appender.console =org.apache.log4j.ConsoleAppender<br />　　log4j.appender.console.layout=org.apache.log4j.PatternLayout<br />　　log4j.appender.console.layout.ConversionPattern=%m%n<br />该配置指定SocketServer使用ConsoleAppender以PatternLayout格式输出信息。运行程序时请先运行 SocketServer，再运行SocketAppender。SocketAppender运行结束后，就可以从SocketServer的控制台看到输出的信息了。<br />　　示例代码2：<br />　　　　package piv.zheng.log4j.test;<br />　　　　<br />　　　　import org.apache.log4j.net.SimpleSocketServer;<br /><br />　　　　public class TestServer {<br />　　　　　　public static void main(String[] args) {<br />　　　　　　　　SimpleSocketServer.main(new String[]{"9090", "test.properties"});<br />　　　　　　}<br />　　　　}<br />这是SimpleSocketServer的示例，与SocketServer相比，只允许指定一个默认的配置文件，而无法对不同计算机使用不同的配置文件。<br />　　11．org.apache.log4j.net.SocketHubAppender，也是以套接字方式发送日志，但与SocketAppender相反，SocketHubAppender是服务器端，而不是客户端。<br />　　示例代码：<br />　　　　//指定服务器端口，这里使用的是本机9090端口<br />　　　　SocketHubAppender appender = new SocketHubAppender(9090);<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　while (true) {<br />　　　　　　Thread.sleep(1000);<br />　　　　　　log.debug("output"); //输出信息<br />　　　　}<br />由于SocketHubAppender一旦运行就开始发送消息，而无论有无接收者，因此这里使用了while语句并将条件设为true以保证程序持续运行。不过为了保证性能，这里还使用了Thread.sleep(1000)，这样程序每循环一次都休眠1秒，如果机器性能不好，还可以将值设的再大些。此外，由于SocketHubAppender也不负责输出信息，因此同样不需要设置Layout。那么如何看到信息输出的效果呢？这里我自己写了个客户端程序，代码如下：<br />　　package piv.zheng.log4j.test;<br />　　<br />　　import java.net.Socket;<br />　　import java.lang.Thread;<br />　　import org.apache.log4j.LogManager;<br />　　import org.apache.log4j.PropertyConfigurator;<br />　　import org.apache.log4j.net.SocketNode;<br />　　<br />　　public class TestClient {<br />　　　　public static void main(String[] args) throws Exception {<br />　　　　　　//创建客户端套接字对象<br />　　　　　　Socket s = new Socket("localhost", 9090);<br />　　　　　　//调用配置文件<br />　　　　　　PropertyConfigurator.configure("test.properties");<br />　　　　　　//从套接字中恢复Logger，并输出信息<br />　　　　　　new Thread(new SocketNode(s, LogManager.getLoggerRepository())).start();<br />　　　　}<br />　　}<br />由于SocketHubAppender与SocketAppender一样，发送的也是SocketNode对象，因此编写该程序时参考了 SocketServer的源码。此外，这里的配置文件直接使用了上例的test.properties文件。运行程序时请先运行 SocketHubAppender，再运行客户端程序，然后从客户端的控制台就可以看到效果了。<br />　　13．org.apache.log4j.net.TelnetAppender，与SocketHubAppender有些类似，也是作为服务器发送信息，但TelnetAppender发送的不是SocketNode对象，而是Category输出的结果。<br />　　示例代码：<br />　　　　SimpleLayout layout = new SimpleLayout();<br />　　　　<br />　　　　TelnetAppender appender = new TelnetAppender();<br />　　　　appender.setLayout(layout); //设置Layout<br />　　　　appender.setPort(9090); //设置端口号<br />　　　　appender.activateOptions(); //应用设置<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　<br />　　　　while (true) {<br />　　　　　　java.lang.Thread.sleep(1000);<br />　　　　　　log.debug("output"); //输出信息<br />　　　　}<br />　　　　//appender.close();<br />注意最后一行被注释掉的代码，若该行代码执行，则TelnetAppender的资源会被清理，从而导致TelnetAppender无法继续运行。那么如何看到信息输出的效果呢？这里提供两种方法：方法一，使用Telnet工具，我使用的就是Windows自带的Telnet。运行 TelnetAppender程序后，点击[开始]菜单-&gt;[运行]，在"运行"框中输入"telnet"，回车，telnet客户端弹出，这是一个命令行程序，输入命令"open localhost 9090"，回车，然后就可以看到效果了。方法二，自己写程序，代码如下：<br />　　package piv.zheng.log4j.test;<br />　　<br />　　import java.net.*;<br />　　import java.io.*;<br />　　<br />　　public class TestClient {<br />　　　　public static void main(String[] args) throws Exception {<br />　　　　　　//创建客户端套接字对象<br />　　　　　　Socket s = new Socket("localhost", 9090);<br />　　　　　　//将BufferedReader与Socket绑定，以输出Socket获得的信息<br />　　　　　　BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));<br />　　　　　　//获得信息并输出<br />　　　　　　String line = in.readLine();<br />　　　　　　while (line != null) {<br />　　　　　　　　System.out.println(line);<br />　　　　　　　　line = in.readLine();<br />　　　　　　}<br />　　　　}<br />　　}<br />　　13．org.apache.log4j.net.SMTPAppender，向指定的电子邮件发送信息，但只能发送ERROR和FATAL级别的信息，而且还没提供身份验证功能。<br />　　示例代码：<br />　　SimpleLayout loyout = new SimpleLayout();<br />　　<br />　　SMTPAppender appender = new SMTPAppender();<br />　　appender.setLayout(loyout); //设置Layout<br />　　appender.setFrom("zhengyp@126.com"); //设置发件人<br />　　appender.setSMTPHost("smtp.126.com"); //设置发送邮件服务器<br />　　appender.setTo("zhengyp@126.com"); //设置收件人<br />　　appender.setSubject("Log4J Test"); //设置邮件标题<br />　　appender.activateOptions(); //应用设置<br />　　<br />　　Logger log = Logger.getLogger(Test.class);<br />　　log.addAppender(appender);<br />　　log.debug("Here is DEBUG");<br />　　log.info("Here is INFO");<br />　　log.warn("Here is WARN");<br />　　log.error("Here is ERROR");<br />　　log.fatal("Here is FATAL");<br />要运行此示例，还需要JavaMail 和JAF，前者是Sun推出的电子邮件类库，可以从<a href="http://java.sun.com/products/javamail/downloads/index.html">http://java.sun.com/products/javamail/downloads/index.html</a>下载，最新版本1.3.3，下载javamail-1_3_3-ea.zip压缩包后需要其中的mail.jar文件；后者全称是JavaBeans Activation Framework，提供了对输入任意数据块的支持，并能相应地对其进行处理，可以从<a href="http://www.sun.com/download">http://www.sun.com/download</a>中找到，最新版本1.1，下载jaf-1_1-ea.zip压缩包后需要其中的activation.jar文件。不过，程序运行后会抛出两次异常，分别是log.error和log.fatal方法导致的，失败的原因很简单，我用的邮件服务器需要身份验证。<br />　　14．piv.zheng.log4j.test.SMTPAppender，自定义的，依照Log4J提供的SMTPAppender修改而来，增加了身份验证功能，并去掉了对级别的限制。由于代码太长，所以放到了另一篇文章《自定义SMTPAppender的源码》中，有兴趣的请自行去查看。<br />　　示例代码：<br />　　　　SimpleLayout layout = new SimpleLayout();<br />　　　　<br />　　　　SMTPAppender appender = new SMTPAppender(layout);<br />　　　　appender.setFrom("zhengyp@126.com"); //发件人<br />　　　　appender.setSMTPHost("smtp.126.com"); //发送邮件服务器<br />　　　　appender.setTo("zhengyp@126.com"); //收件人<br />　　　　appender.setSubject("Log4J Test"); //邮件标题<br />　　　　appender.setAuth("true"); //身份验证标识<br />　　　　appender.setUsername("zhengyp"); //用户名<br />　　　　appender.setPassword("1111111"); //密码<br />　　　　appender.activateOptions(); //应用设置<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />同样需要JavaMail 和JAF。程序运行后会发送一封邮件，快去查看一下自己的邮箱吧^_^<br />　　此外，Log4J还提供了SyslogAppender、JMSAppender（均在org.apache.log4j.net包下）以及更多的 Appender，或者用来向Unix操作系统的syslogd服务发送信息，或者通过JMS方式发送信息，或者以其他方式发送信息。由于条件有现，就不再介绍了。<br />　　不过，在前边的示例中还使用了SimpleLayout和PatternLayout来格式化输出的信息，这里也简单介绍一下。<br />　　1．org.apache.log4j.SimpleLayout，一直用的就是它，输出的格式比较简单，就是"级别 - 信息"。<br />　　2．org.apache.log4j.HTMLLayout，以HTML格式输出信息。<br />　　示例代码：<br />　　　　HTMLLayout layout = new HTMLLayout();<br />　　　　layout.setTitle("Log4J Test"); //HTML页标题<br />　　　　<br />　　　　FileAppender appender = null;<br />　　　　try {<br />　　　　　　appender = new FileAppender(layout, "test.html");<br />　　　　} catch(Exception e) {}<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />程序运行后会在工程目录下生成一个HTML页，可以用浏览器来查看。<br />　　3．org.apache.log4j.xml.XMLLayout，以XML格式输出信息。<br />　　示例代码：<br />　　　　XMLLayout layout = new XMLLayout();<br />　　　　<br />　　　　FileAppender appender = null;<br />　　　　try {<br />　　　　　　appender = new FileAppender(layout, "test.xml");<br />　　　　} catch(Exception e) {}<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />程序运行后会在工程目录下生成一个test.xml文件。<br />　　4．org.apache.log4j.TTCCLayout，输出信息的同时输出日志产生时间、相关线程及Category等信息。<br />　　示例代码：<br />　　　　TTCCLayout layout = new TTCCLayout();<br />　　　　//是否打印与TTCCLayout关联的Category的名称，默认为true，表示打印<br />　　　　layout.setCategoryPrefixing(true);<br />　　　　//是否打印当前线程，默认为true，表示打印<br />　　　　layout.setThreadPrinting(true);<br />　　　　//是否打印输出和当前线程相关的NDC信息，默认为true，表示打印<br />　　　　layout.setContextPrinting(true);<br />　　　　//设置日期时间格式<br />　　　　layout.setDateFormat("iso8601");<br />　　　　//设置时区<br />　　　　layout.setTimeZone("GMT+8:00");<br />　　　　//设置时区后需要调用此方法应用设置<br />　　　　layout.activateOptions();<br />　　　　<br />　　　　ConsoleAppender appender = new ConsoleAppender(layout);<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />注意，TTCCLayout输出的时间格式及时区是可以设置的：<br />　　（1）setDateFormat，设置日期时间格式，有五个常用值："NULL"，表示不输出；"RELATIVE"，输出信息所用的时间，以毫秒为单位，默认使用该值；"ABSOLUTE"，仅输出时间部分；"DATE"，按当前所在地区显示日期和时间；"ISO8601"，按ISO8601标准显示日期和时间。这些字符串不区分大小写。此外，还可以使用时间模式字符来格式化日期时间，详细内容请参考J2SE文档中的 java.text.SimpleDateFormat类。<br />　　（2）setTimeZone，设置时区，详细内容请参考J2SE文档中的java.util.TimeZone类和java.util.SimpleTimeZone类。但请注意，当日期格式为"RELATIVE"时，设置时区会造成冲突。<br />　　5．org.apache.log4j.PatternLayout，用模式字符灵活指定信息输出的格式。<br />　　示例代码：<br />　　　　String pattern = "Logger: %c %n" <br />　　　　　　　　+ "Date: %d{DATE} %n"<br />　　　　　　　　+ "Message: %m %n";<br />　　　　PatternLayout layout = new PatternLayout(pattern);<br />　　　　<br />　　　　ConsoleAppender appender = new ConsoleAppender(layout);<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　log.debug("output");<br />模式字符串简介：<br />　　%c：Category名称。还可以使用%c{n}的格式输出Category的部分名称，其中n为正整数，输出时会从Category名称的右侧起查 n个"."，然后截取第n个"."右侧的部分输出，例如Category的名称为"x.y.z"，指定格式为"%c{2}"，则输出"y.z"。<br />　　%C：输出信息时Category所在类的名称，也可以使用%C{n}的格式输出。<br />　　%d：输出信息的时间，也可以用%d{FormatString}的格式输出，其中FormatString的值请参考TTCCLayout的setDateFormat方法，但NULL和RELATIVE在%d中无法使用。<br />　　%F：输出信息时Category所在类文件的名称。<br />　　%l：输出信息时Category所在的位置，使用"%C.%M(%F:%L)"可以产生同样的效果。<br />　　%L：输出信息时Category在类文件中的行号。<br />　　%m：信息本身。<br />　　%M：输出信息时Category所在的方法。<br />　　%n：换行符，可以理解成回车。<br />　　%p：日志级别。<br />　　%r：输出信息所用的时间，以毫秒为单位。<br />　　%t：当前线程。<br />　　%x：输出和当前线程相关的NDC信息。<br />　　%X：输出与当前现成相关的MDC信息。<br />　　%%：输出%。<br />此外，还可以在%与模式字符之间加上修饰符来设置输出时的最小宽度、最大宽度及文本对齐方式，例如：<br />　　%30d{DATE}：按当前所在地区显示日期和时间，并指定最小宽度为30，当输出信息少于30个字符时会补以空格并右对齐。<br />　　%-30d{DATE}：也是按当前所在地区显示日期和时间，指定最小宽度为30，并在字符少于30时补以空格，但由于使用了"-"，因此对齐方式为左对齐，与默认情况一样。<br />　　%.40d{DATE}：也是按当前所在地区显示日期和时间，但指定最大宽度为40，当输出信息多于40个字符时会将左边多出的字符截掉。此外，最大宽度只支持默认的左对齐方式，而不支持右对齐。<br />　　%30.40d{DATE}：如果输出信息少于30个字符就补空格并右对齐，如果多于40个字符，就将左边多出的字符截掉。<br />　　%-30.40d{DATE}：如果输出信息少于30个字符就补空格并左对齐，如果多于40个字符，就将左边多出的字符截掉。<br />五、Log4J进阶<br />　　了解以上内容后，就已经初步掌握Log4J了，但要想灵活使用Log4J，则还需要了解其配置功能。这里简单介绍一下。<br />　　1．org.apache.log4j.BasicConfigurator，默认使用ConsoleAppender以PatternLayout （使用PatternLayout.TTCC_CONVERSION_PATTERN，即"%r [%t] %p %c %x - %m%n"格式）输出信息。<br />　　示例代码：<br />　　　　BasicConfigurator.configure();<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.debug("output");<br />注意，BasicConfigurator及其它Configurator其实都只对根Category进行配置，但由于用户创建的Category会继承根Category的特性（声明，许多资料介绍Category继承关系时都主要在讨论输出级别，而事实上，Category间继承的不仅是输出级别，所有特性都可以继承），因此输出时仍会显示BasicConfigurator配置的效果。此外，还可以使用configure方法指定Appender，以自定义输出。BasicConfigurator允许同时指定多个Appender。<br />　　示例代码：<br />　　　　SimpleLayout layout1 = new SimpleLayout();<br />　　　　ConsoleAppender appender1 = new ConsoleAppender(layout1);<br />　　　　BasicConfigurator.configure(appender1);<br />　　　　<br />　　　　String pattern = "Logger: %c %n"<br />　　　　　　　　+ "Date: %d{DATE} %n"<br />　　　　　　　　+ "Message: %m %n";<br />　　　　PatternLayout layout2 = new PatternLayout(pattern);<br />　　　　FileAppender appender2 = null;<br />　　　　try {<br />　　　　　　appender2 = new FileAppender(layout2, "test.log", false);<br />　　　　}<br />　　　　catch(Exception e){}<br />　　　　BasicConfigurator.configure(appender2);<br />　　　　<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.debug("output");<br />这里用BasicConfigurator指定了两个Appender，即ConsoleAppender和FileAppender，程序运行后信息会在以SimpleLayout输出到控制台的同时以PatternLayout输出到test.log文件。若要清除这些Appender，可以调用 BasicConfigurator的resetConfiguration方法。<br />　　2． org.apache.log4j.PropertyConfigurator，调用文本配置文件输出信息，通常使用.properties文件。配置文件以"键=值"的形式保存数据，注释以"#"开头。PropertyConfigurator和配置文件在介绍SocketAppender和 SocketHubAppender时曾提到过。使用PropertyConfigurator可以避免硬编码。<br />　　示例代码：<br />　　　　PropertyConfigurator.configure("test.properties");<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.debug("output");<br />要完成该示例，还需要在工程目录下创建一个test.properties文件，内容如下：<br />　　##设置根Category，其值由输出级别和指定的Appender两部分组成<br />　　#这里设置输出级别为DEBUG<br />　　log4j.rootLogger=DEBUG,appender<br />　　##输出信息到控制台<br />　　#创建一个名为appender的Appender，类型为ConsoleAppender<br />　　log4j.appender.appender=org.apache.log4j.ConsoleAppender<br />　　#设置appender以SimpleLayout输出<br />　　log4j.appender.appender.layout=org.apache.log4j.SimpleLayout<br />此外，PropertyConfigurator也允许同时指定多个Appender，例如：<br />　　#这里没有设置输出级别，但指定了两个Appender<br />　　log4j.rootLogger=,appender1,appender2<br />　　#输出信息到控制台<br />　　log4j.appender.appender1=org.apache.log4j.ConsoleAppender<br />　　log4j.appender.appender1.layout=org.apache.log4j.SimpleLayout<br />　　#输出信息到文件<br />　　log4j.appender.appender2=org.apache.log4j.FileAppender<br />　　log4j.appender.appender2.File=test.log<br />　　log4j.appender.appender2.Append=false<br />　　log4j.appender.appender2.layout=org.apache.log4j.PatternLayout<br />　　log4j.appender.appender2.layout.ConversionPattern=Logger: %c %nDate: %d{DATE} %nMessage: %m %n<br />关于更多配置，网上示例很多，这里不再赘述。但要说明一件事，就是配置文件中的键是怎么来的。参照后一个示例，查看 PropertyConfigurator源码，会发现"log4j.rootLogger"是定义好的，只能照写；而"log4j.appender" 字样也可以找到，与指定的Appender名称appender1、appender2联系起来，log4j.appender.appender1和 log4j.appender.appender2也就不难理解了；再看下去，还能找到"prefix + ".layout""，这样log4j.appender.appender1.layout也有了；可是 log4j.appender.appender2.File 和log4j.appender.appender2.Append呢？还记得前边介绍FileAppender时曾提到的setAppend方法吗？其实FileAppender还有个getAppend方法，这说明FileAppender具有Append属性。那么File呢？当然也是 FileAppender的属性了。至于log4j.appender.appender2.layout.ConversionPattern也一样，只不过FileAppender换成了PatternLayout。其实别的Appender和Layout的属性也都是这样定义成键来进行设置的。此外，定义键时，属性的首字母不区分大小写，例如"File"，也可以写成"file"。<br />　　3． org.apache.log4j.xml.DOMConfigurator，调用XML配置文件输出信息。其定义文档是log4j- 1.2.11.jar中org\apache\log4j\xml包下的log4j.dtd文件。与PropertyConfigurator相比， DOMConfigurator似乎是趋势。<br />　　示例代码：<br />　　　　DOMConfigurator.configure("test.xml");<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.debug("output");<br />要完成该示例，也需要在工程目录下创建一个test.xml文件，内容如下：<br />　　&lt;?xml version="1.0" encoding="UTF-8" ?&gt;<br />　　&lt;!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"&gt;<br />　　&lt;log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"&gt;<br />　　　　&lt;!-- 输出信息到控制台<br />　　　　创建一个名为appender的Appender，类型为ConsoleAppender --&gt;<br />　　　　&lt;appender name="appender" class="org.apache.log4j.ConsoleAppender"&gt;<br />　　　　　　&lt;!-- 设置appender以SimpleLayout输出 --&gt;<br />　　　　　　&lt;layout class="org.apache.log4j.SimpleLayout"/&gt;<br />　　　　&lt;/appender&gt;<br />　　　　&lt;!-- 设置根Category，其值由输出级别和指定的Appender两部分组成<br />　　　　这里设置输出级别为DEBUG --&gt;<br />　　　　&lt;root&gt;<br />　　　　　　&lt;priority value ="debug" /&gt;<br />　　　　　　&lt;appender-ref ref="appender"/&gt;<br />　　　　&lt;/root&gt;<br />　　&lt;/log4j:configuration&gt;<br />此外，DOMConfigurator也允许同时指定多个Appender，例如：<br />　　&lt;?xml version="1.0" encoding="UTF-8" ?&gt;<br />　　&lt;!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"&gt;<br />　　&lt;log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"&gt;<br />　　　　&lt;!-- 输出信息到控制台 --&gt;<br />　　　　&lt;appender name="appender1" class="org.apache.log4j.ConsoleAppender"&gt;<br />　　　　　　&lt;layout class="org.apache.log4j.SimpleLayout"/&gt;<br />　　　　&lt;/appender&gt;<br />　　　　&lt;!-- 输出信息到文件 --&gt;<br />　　　　&lt;appender name="appender2" class="org.apache.log4j.FileAppender"&gt;<br />　　　　　　&lt;param name="File" value="test.log"/&gt;<br />　　　　　　&lt;param name="Append" value="false"/&gt;<br />　　　　　　&lt;layout class="org.apache.log4j.PatternLayout"&gt;<br />　　　　　　　　&lt;param name="ConversionPattern" value="Logger: %c %nDate: %d{DATE} %nMessage: %m %n"/&gt;<br />　　　　　　&lt;/layout&gt;<br />　　　　&lt;/appender&gt;<br />　　　　&lt;!-- 这里没有设置输出级别，但指定了两个Appender --&gt;<br />　　　　&lt;root&gt;<br />　　　　　　&lt;appender-ref ref="appender1"/&gt;<br />　　　　　　&lt;appender-ref ref="appender2"/&gt;<br />　　　　&lt;/root&gt;<br />　　&lt;/log4j:configuration&gt;<br />由于以上两个示例是在PropertyConfigurator的两个示例基础上改的，而且也写了注释，因此这里只简单介绍一下&lt;param&gt; 标记。&lt;param&gt;标记有两个属性，name和value，前者的值也是Appender或Layout的属性名，作用与 log4j.appender.appender2.File这样的键一样。设置时，首字母同样不区分大小写，例如"File"也可以写成"file"。此外还请注意，使用这两段XML代码时应将中文注释去掉，或者把&lt;?xml version="1.0" encoding="UTF-8" ?&gt;中的UTF-8改成GBK或GB2312，否则会导致错误。这里使用的UTF-8是XML默认的字符集。<br />　　4． org.apache.log4j.lf5.DefaultLF5Configurator，默认使用LF5Appender来输出信息，需要调用 log4j-1.2.11.jar中org\apache\log4j\lf5\config包下的defaultconfig.properties文件。<br />　　示例代码：<br />　　　　try {<br />　　　　　　DefaultLF5Configurator.configure();<br />　　　　}<br />　　　　catch(Exception e){}<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.debug("output");<br />　　下面讨论另外一个话题：Diagnostic Context。Diagnostic Context意为诊断环境，针对于多用户并发环境，在这种环境下，通常需要对每个客户端提供独立的线程以处理其请求，此时若要在日志信息中对客户端加以区分，为每个线程分别创建Category是个办法。但这样做并不高效，反而会导致大量资源被占用。Diagnostic Context所要解决的就是这个问题。Diagnostic Context会为当前线程提供一定空间，然后将信息保存到该空间供Category调用。与创建一个Category相比，这点信息所占的资源自然要少得多。<br />　　1．org.apache.log4j.NDC。NDC是Nested Diagnostic Context的简写，意为嵌套诊断环境，使用时提供一个堆栈对象来保存信息。堆栈的特点是数据后进先出、先进后出，即清理堆栈时，后保存的数据会被先清掉，而先保存的数据则被后清掉。<br />　　示例代码：<br />　　　　PatternLayout layout = new PatternLayout("%m %x%n");<br />　　　　ConsoleAppender appender = new ConsoleAppender(layout);<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　<br />　　　　String tmp = "zhengyp"; //模拟从客户端获取的信息<br />　　　　log.debug("Start");<br />　　　　NDC.push(tmp); //添加信息到堆栈中<br />　　　　log.debug("Before");<br />　　　　NDC.pop(); //将信息从堆栈中移除<br />　　　　log.debug("After");<br />　　　　NDC.remove(); //将当前线程移除，退出NDC环境<br />　　　　log.debug("End");<br />这里使用了PatternLayout来格式化信息，其模式字符%x就是用来输出NDC信息的。程序运行后会输出如下内容：<br />　　Start<br />　　Before zhengyp<br />　　After<br />　　End<br />可以看到，第二行输出时由于已向堆栈中添加了信息，因此"zhengyp"也会同时输出；而第三行输出时由于信息已被移除，因此就没再输出"zhengyp"。不过这个示例仅简单演示了NDC的用法，而没有显示出NDC的堆栈特性，所以下面再提供一个示例，代码如下：<br />　　TTCCLayout layout = new TTCCLayout();<br />　　ConsoleAppender appender = new ConsoleAppender(layout);<br />　　Logger log = Logger.getLogger(Test.class);<br />　　log.addAppender(appender);<br />　　<br />　　log.debug("Start");<br />　　NDC.push("zhengyp"); //添加信息到堆栈中<br />　　log.debug("Test1");<br />　　NDC.push("192.168.0.1"); //向堆栈中追加信息<br />　　log.debug("Test2");<br />　　NDC.pop(); //从堆栈中移除信息，但移除的只是最后的信息<br />　　log.debug("Test3");<br />　　NDC.pop(); //再次从堆栈中移除信息<br />　　log.debug("Test4");    <br />　　log.debug("End");<br />这里格式化输出信息使用的是TTCCLayout，还记得其setContextPrinting方法吗？程序运行后，从输出的信息就可以看到效果了。此外，NDC还提供了其他方法：<br />　　（1）get，获取堆栈中的全部信息。以上例为例，当输出Test2时，使用该方法会获得"zhengyp 192.168.0.1"。<br />　　（2）peek，获取堆栈中最后的信息。仍以上例为例，当输出Test1时会获得"zhengyp"，Test2时为"192.168.0.1"，而当输出Test3时由于"192.168.0.1"已被移除，"zhengyp"又成了最后的信息，因此获得的仍是"zhengyp"。<br />　　（3）clear，清空堆栈中的全部信息。<br />　　（4）setMaxDepth，设置堆栈的最大深度，即当前的信息可以保留多少，对之后追加的信息没有影响。当需要一次清掉多条信息时，使用setMaxDepth会比多次调用pop方便。<br />　　2．org.apache.log4j.MDC。MDC是Mapped Diagnostic Context的简写，意为映射诊断环境，提供了一个Map对象来保存信息。Map对象使用Key、Value的形式保存值。<br />　　示例代码：<br />　　　　PatternLayout layout = new PatternLayout("%m %X{name} %X{ip}%n");<br />　　　　ConsoleAppender appender = new ConsoleAppender(layout);<br />　　　　Logger log = Logger.getLogger(Test.class);<br />　　　　log.addAppender(appender);<br />　　　　<br />　　　　log.debug("Start");<br />　　　　//添加信息到Map中<br />　　　　MDC.put("name", "zhengyp1");<br />　　　　MDC.put("ip", "192.168.1.1");<br />　　　　log.debug("Test1");<br />　　　　<br />　　　　//添加信息到Map中，若Key重复，则覆盖之前的值<br />　　　　MDC.put("name", "zhengyp2");<br />　　　　MDC.put("ip", "192.168.1.2");<br />　　　　log.debug("Test2");<br />　　　　<br />　　　　//将信息从Map中移除，此时信息不再输出<br />　　　　MDC.remove("name");<br />　　　　MDC.remove("ip");<br />　　　　log.debug("End");<br />这个示例演示了MDC的基本用法，格式化信息用的也是PatternLayout，模式字符为"%X"，其格式必须为"%X{Key}"。其中Key就是向 Map对象添加信息时put方法所用的Key，这里为name和ip。由于可以使用"%X{Key}"输出信息，因此MDC使用起来会比NDC更灵活。此外，MDC还提供了get方法来获取指定Key的信息。<br />六、小结<br />　　用了近半个月，终于大概掌握了Log4J。由于本文是边学边写的，目的是将Log4J的用法记录下来，而非提供一份中文参考，因此内容并不细致，但尽量提供了示例。不过到最后才发现，示例存在问题，其实Logger做为类的static成员比较恰当，而我为了图方便，竟直接写到了main方法中，这一点还请注意。<br />　　此外，这里再推荐一下《The Complete log4j Manual》，是对Log4J较详细的介绍，在网上可以找到，只不过是英文的。</p>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
<img src ="http://www.blogjava.net/lijiajia418/aggbug/67072.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-09-01 13:25 <a href="http://www.blogjava.net/lijiajia418/archive/2006/09/01/67072.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用Jakarta Commons Pool处理对象池化</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65600.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:49:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65600.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65600.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65600.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65600.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65600.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 恰当地使用对象池化技术，可以有效地减少对象生成和初始化时的消耗，提高系统的运行效率。Jakarta Commons Pool组件提供了一整套用于实现对象池化的框架，以及若干种各具特色的对象池实现，可以有效地减少处理对象池化时的工作量，为其它重要的工作留下更多的精力和时间。								创建新的对象并初始化的操作，可能会消耗很多的时间。在这种对象的初始化工作包含了一些费时的操作（例...&nbsp;&nbsp;<a href='http://www.blogjava.net/lijiajia418/archive/2006/08/24/65600.html'>阅读全文</a><img src ="http://www.blogjava.net/lijiajia418/aggbug/65600.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:49 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65600.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Apache Commons Chain简明手册</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/22/64999.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Tue, 22 Aug 2006 02:47:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/22/64999.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/64999.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/22/64999.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/64999.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/64999.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 基本对象																																								1．										  																												Command																				接口。它是														Commons Chain												中...&nbsp;&nbsp;<a href='http://www.blogjava.net/lijiajia418/archive/2006/08/22/64999.html'>阅读全文</a><img src ="http://www.blogjava.net/lijiajia418/aggbug/64999.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-22 10:47 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/22/64999.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>开始使用Commons Chain （第一部分）</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/22/64990.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Tue, 22 Aug 2006 02:40:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/22/64990.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/64990.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/22/64990.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/64990.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/64990.html</trackback:ping><description><![CDATA[
		<a border="" src="http://www.matrix.org.cn/resource/upload/forum/2005_12_14_001758_XrhGVLRcZQ.gif" alt="" align="" hspace="" vspace="" width="" height="">
				<span id="LblContent">作为程序开发人员，我们经常需要对一个实际上程序性的系统应用面向对象的方法。商业分析家和管理人员描述这样的系统时通常不使用类层次和序列图，而是使用流程图和工作流图表。但是不论如何，使用面向对象的方法解决这些问题时会带来更多的灵活性。面向对象的设计模式提供了有用的结构和行为来描述这种顺序的处理，比如模版方法（Template Method）[GoF]和责任链（Chain of Responsibility）[GoF]。<br /><br />Jakarta Commons的子项目Chain将上述两个模式组合成一个可复用的Java框架用于描述顺序的处理流程。这个在Jakarta Commons project社区中开发的框架，已经被广泛的接受并且使用于许多有趣的应用中，特别的是他被Struts和Shale应用框架作为处理HTTP请求处理的基础机制。你可以在需要定义和执行一组连续的步骤时使用Commons Chain。<br /><br />至于经典设计模式，开发者和架构师普遍使用模版方法（Template Method）造型顺序处理。模版方法（Template Method）中使用一个抽象的父类定义使用的算法：处理的步骤，具体实现交给子类。当然，父类也可以为算法所使用的方法提供一个缺省实现。<br /><br />由于模版方法（Template Method）依赖继承——子类必须继承定义了算法的父类——因此使用这个模式的软件表现出紧耦合而且缺少灵活性。又由于实现类添加自己的行为前必须扩展父类，沟每⑷嗽北幌拗朴诶嗖愦沃校佣拗屏顺绦蛏杓频牧榛钚浴ommons Chain使用配置文件定义算法，在程序运行时解析配置文件，从而很好的解决了这个问题。<br /><br />现在来看一下Commons Chain是怎样工作的，我们从一个人造的例子开始：二手车销售员的商业流程。下面是销售流程的步骤：<br />1.        得到用户信息<br />2.        试车<br />3.        谈判销售<br />4.        安排财务<br />5.        结束销售<br /><br />现在假设使用模版方法（Template Method）造型这个流程。首先建立一个定义了算法的抽象类：<br /><br /><b>清单1</b><br /><pre class="overflow" title="pre code">public abstract class SellVehicleTemplate {<br />        public void sellVehicle() {<br />        getCustomerInfo();<br />        testDriveVehicle();<br />        negotiateSale();<br />        arrangeFinancing();<br />        closeSale();<br />        }<br /><br />        public abstract void getCustomerInfo();<br />        public abstract void testDriveVehicle();<br />        public abstract void negotiateSale();<br />        public abstract void arrangeFinancing();<br />        public abstract void closeSale();        <br />}</pre><br /><br />现在来看一下怎样用Commons Chain实现这个流程。首先，下载Commons Chain。你可以直接下载最新的zip或tar文件，也可以从CVS或者SubVersion源码库检出Commons Chain模块得到最新的代码。解压缩打包文件，将commons-chain.jar放入你的classpath中。<br /><br />使用Commons Chain实现这个商业流程，必须将流程中的每一步写成一个类，这个类需要有一个public的方法execute()。这和传统的命令模式（Command pattern）实现相同。下面简单实现了“得到用户信息”：<br /><br /><b>清单2</b><br /><pre class="overflow" title="pre code">package com.jadecove.chain.sample;<br /><br />import org.apache.commons.chain.Command;<br />import org.apache.commons.chain.Context;<br /><br />public class GetCustomerInfo implements Command {<br />        public boolean execute(Context ctx) throws Exception {<br />                System.out.println("Get customer info");<br />                ctx.put("customerName","George Burdell");<br />                return false;<br />        }<br />}</pre><br /><br />由于只是演示，这个类并没有做很多工作。这里将用户名放入了Context对象ctx中。这个Context对象连接了各个命令。暂时先将这个对象想象成根据关键字存取值的哈希表。所有后来的命令可以通过它访问刚才放入的用户名。TestDriveVehicle，NegotiateSale和 ArrangeFinancing命令的实现只是简单的打印了将执行什么操作。<br /><br /><b>清单3</b><br /><pre class="overflow" title="pre code">package com.jadecove.chain.sample;<br /><br />import org.apache.commons.chain.Command;<br />import org.apache.commons.chain.Context;<br /><br />public class TestDriveVehicle implements Command {<br />        public boolean execute(Context ctx) throws Exception {<br />                System.out.println("Test drive the vehicle");<br />                return false;<br />        }<br />}<br /><br />public class NegotiateSale implements Command {<br />        public boolean execute(Context ctx) throws Exception {<br />                System.out.println("Negotiate sale");<br />                return false;<br />        }<br />}<br /><br />public class ArrangeFinancing implements Command {<br />        public boolean execute(Context ctx) throws Exception {<br />                System.out.println("Arrange financing");<br />                return false;<br />        }<br />}</pre><br /><br />CloseSale从Context对象中取出GetCustomerInfo放入的用户名，并将其打印。<br /><b>清单4</b><br /><pre class="overflow" title="pre code">package com.jadecove.chain.sample;<br /><br />import org.apache.commons.chain.Command;<br />import org.apache.commons.chain.Context;<br /><br />public class CloseSale implements Command {<br />        public boolean execute(Context ctx) throws Exception {<br />                System.out.println("Congratulations "<br />                  +ctx.get("customerName")<br />                        +", you bought a new car!");<br />                return false;<br />        }<br />}</pre><br /><br />现在你可以将这个流程定义成一个序列（或者说“命令链”）。<br /><b>清单5</b><br /><pre class="overflow" title="pre code">package com.jadecove.chain.sample;<br /><br />import org.apache.commons.chain.impl.ChainBase;<br />import org.apache.commons.chain.Command;<br />import org.apache.commons.chain.Context;<br />import org.apache.commons.chain.impl.ContextBase;<br /><br />public class SellVehicleChain extends ChainBase {<br />        public SellVehicleChain() {<br />                super();<br />                addCommand(new GetCustomerInfo());<br />                addCommand(new TestDriveVehicle());<br />                addCommand(new NegotiateSale());<br />                addCommand(new ArrangeFinancing());<br />                addCommand(new CloseSale());<br />        }<br />        public static void main(String[] args) throws Exception {<br />                Command process = new SellVehicleChain();<br />                Context ctx = new ContextBase();<br />                process.execute(ctx);<br />        }<br />}</pre><br /><br />运行这个类将会输出以下结果：<br /><span style="COLOR: blue">Get customer info<br />Test drive the vehicle<br />Negotiate sale<br />Arrange financing<br />Congratulations George Burdell, you bought a new car!</span><br /><br />在进一步深入之前，让我们来看一下我们使用了的Commons Chain的类和接口。<br /><br /></span>
		</a>
		<img src="http://www.matrix.org.cn/resource/upload/forum/2005_12_14_001758_XrhGVLRcZQ.gif" />
		<br />Command 类和Chain类的关系就是组合模式（Composite pattern）[GoF]的例子：Chain不仅由多个Command组成，而且自己也是Command。这使你可以非常简单得将单个命令（Command）替换成由多个命令（Command）组成的链（Chain）。这个由Command对象唯一操作定义的方法代表了一个直接的命令：<br /><br />public boolean execute(Context context);<br /><br />参数context仅仅是一个存放了名称-值对的集合。接口Context在这里作为一个标记接口：它扩展了java.util.Map但是没有添加任何特殊的行为。于此相反，类ContextBase不仅提供了对Map的实现而且增加了一个特性：属性-域透明。这个特性可以通过使用Map的put和get 方法操作JavaBean的域，当然这些域必须使用标准的getFoo和setFoo方法定义。那些通过JavaBean的“setter”方法设置的值，可以通过对应的域名称，用Map的get方法得到。同样，那些用Map的put方法设置的值可以通过JavaBean的“getter”方法得到。<br /><br />例如，我们可以创建一个专门的context提供显式的customerName属性支持。<br /><b>清单6</b><br /><pre class="overflow" title="pre code">package com.jadecove.chain.sample;<br /><br />import org.apache.commons.chain.impl.ContextBase;<br /><br />public class SellVehicleContext extends ContextBase {<br /><br />        <br />        private String customerName;<br /><br />        public String getCustomerName() {<br />                return customerName;<br />        }<br />        <br />        public void setCustomerName(String name) {<br />                this.customerName = name;<br />        }<br />}</pre><br /><br />现在你既可以进行Map的一般属性存取操作同时也可以使用显式的JavaBean的访问和修改域的方法，这两个将产生同样的效果。但是首先你需要在运行SellVehicleChain时实例化SellVehiceContext而不是ContextBase。<br /><b>清单7</b><br /><pre class="overflow" title="pre code">public static void main(String[] args) throws Exception {<br />                Command process = new SellVehicleChain();<br />                Context ctx = new SellVehicleContext();<br />                process.execute(ctx);<br />        }</pre><br /><br />尽管你不改变GetCustomerInfo中存放用户名的方法——仍然使用ctx.put("customerName", "George Burdell")——你可以在CloseSale中使用getCustomerName()方法得到用户名。<br /><b>清单8</b><br /><pre class="overflow" title="pre code">        public boolean execute(Context ctx) throws Exception {<br />            SellVehicleContext myCtx = (SellVehicleContext) ctx;<br />            System.out.println("Congratulations "<br />                                   + myCtx.getCustomerName()<br />                                  + ", you bought a new car!");<br />            return false;<br />        }</pre><br /><br />那些依赖类型安全和context的显式域的命令（Command）可以利用标准的getter和setter方法。当一些新的命令（Command）被添加时，它们可以不用考虑context的具体实现，直接通过Map的get和put操作属性。不论采用何种机制，ContextBase类都可以保证命令（Command）间可以通过context互操作。<br /><br />下面这个例子展示了如何使用Commons Chain的API建立并执行一组顺序的命令。当然，和现在大多数Java软件一样，Commons Chain可以使用XML文件作为配置文件。你可以将“汽车销售”流程的步骤在XML文件中定义。这个文件有个规范的命名chain- config.xml。<br /><br /><b>清单9</b><br /><pre class="overflow" title="pre code">&lt;catalog&gt;<br />  &lt;chain name="sell-vehicle"&gt;<br />    &lt;command   id="GetCustomerInfo"<br />        className="com.jadecove.chain.sample.GetCustomerInfo"/&gt;<br />    &lt;command   id="TestDriveVehicle"<br />        className="com.jadecove.chain.sample.TestDriveVehicle"/&gt;<br />    &lt;command   id="NegotiateSale"<br />        className="com.jadecove.chain.sample.NegotiateSale"/&gt;<br />    &lt;command   id="ArrangeFinancing"<br />        className="com.jadecove.chain.sample.ArrangeFinancing"/&gt;<br />    &lt;command   id="CloseSale"<br />        className="com.jadecove.chain.sample.CloseSale"/&gt;<br />  &lt;/chain&gt;<br />&lt;/catalog&gt;</pre><br /><br />Chain的配置文件可以包含多个链定义，这些链定义可以集合进不同的编目中。在这个例子中，链定义在一个默认的编目中定义。事实上，你可以在这个文件中定义多个名字的编目，每个编目可拥有自己的链组。<br /><br />现在你可以使用Commons Chain提供的类载入编目并得到指定的链，而不用像SellVehicleChain中那样自己在程序中定义一组命令：<br /><b>清单10</b><br /><pre class="overflow" title="pre code">package com.jadecove.chain.sample;<br /><br />import org.apache.commons.chain.Catalog;<br />import org.apache.commons.chain.Command;<br />import org.apache.commons.chain.Context;<br />import org.apache.commons.chain.config.ConfigParser;<br />import org.apache.commons.chain.impl.CatalogFactoryBase;<br /><br />public class CatalogLoader {<br />        private static final String CONFIG_FILE = <br />                "/com/jadecove/chain/sample/chain-config.xml";<br />        private ConfigParser parser;<br />        private Catalog catalog;<br />        <br />        public CatalogLoader() {<br />                parser = new ConfigParser();<br />        }<br />        public Catalog getCatalog() throws Exception {<br />                if (catalog == null) {<br />                <br />        parser.parse(this.getClass().getResource(CONFIG_FILE));                <br />        <br />                }<br />                catalog = CatalogFactoryBase.getInstance().getCatalog();<br />                return catalog;<br />        }<br />        public static void main(String[] args) throws Exception {<br />                CatalogLoader loader = new CatalogLoader();<br />                Catalog sampleCatalog = loader.getCatalog();<br />                Command command = sampleCatalog.getCommand("sell-vehicle");<br />                Context ctx = new SellVehicleContext();<br />                command.execute(ctx);<br />        }<br />}</pre><br /><br />Chain 使用Commons Digester来读取和解析配置文件。因此你需要将Commons Digester.jar加入classpath中。我使用了1.6版本并且工作得很好。Digester使用了Commons Collectios（我使用的版本是3.1），Commons Logging（版本1.0.4），Commons BeanUtils（1.7.0），因此你也需要将它们的jar文件加入classpath中。在加入这些jar后，CatalogLoader就可以被编译和运行，它的输出和另外两个测试完全相同。<br /><br />现在你可以在XML文件中定义链，并可以在程序中得到这个链（别忘了链也是命令），这样扩展的可能性和程序的灵活性可以说是无限的。假设过程“安排财务”实际上由一个完全分离的商业部门处理。这个部门希望为这种销售建立自己的工作流程。 Chain提供了嵌套链来实现这个要求。因为链本身就是命令，因此你可以用指向另一个链的引用替换一个单一用途的命令。下面是增加了新流程的链的定义：<br /><b>清单11</b><br /><pre class="overflow" title="pre code">&lt;catalog name="auto-sales"&gt;<br />   &lt;chain name="sell-vehicle"&gt;<br />         &lt;command   id="GetCustomerInfo"<br />                 className="com.jadecove.chain.sample.GetCustomerInfo"/&gt;<br />         &lt;command   id="TestDriveVehicle"<br />                 className="com.jadecove.chain.sample.TestDriveVehicle"/&gt;<br />         &lt;command   id="NegotiateSale"<br />                 className="com.jadecove.chain.sample.NegotiateSale"/&gt;<br />         &lt;command<br />                 className="org.apache.commons.chain.generic.LookupCommand"<br />             catalogName="auto-sales"<br />                      name="arrange-financing"<br />                  optional="true"/&gt;<br />         &lt;command   id="CloseSale"<br />                 className="com.jadecove.chain.sample.CloseSale"/&gt;<br />   &lt;/chain&gt;<br />   &lt;chain name="arrange-financing"&gt;<br />         &lt;command   id="ArrangeFinancing"<br />                 className="com.jadecove.chain.sample.ArrangeFinancing"/&gt;<br />   &lt;/chain&gt;<br />&lt;/catalog&gt;</pre><br /><br />Commons Chain提供了一个常用的命令LookupCommand来查找和执行另一个链。属性optional用于控制当指定的嵌套链没有找到时如何处理。 optional=true时，即使链没找到，处理也会继续。反之，LookupCommand将抛出 IllegalArgumentException，告知指定的命令未找到。<br /><br />在下面三种情况下，命令链将结束：<br />1.        命令的execute方法返回true<br />2.        运行到了链的尽头<br />3.        命令抛出异常<br /><br />当链完全处理完一个过程后，命令就返回true。这是责任链模式（Chain of Responsibility）的基本概念。处理从一个命令传递到另一个命令，直到某个命令（Command）处理了这个命令。如果在到达命令序列尽头时仍没有处理返回true，也假设链已经正常结束。<br /><br />当有命令抛出错误时链就会非正常结束。在Commons Chain中，如果有命令抛出错误，链的执行就会中断。不论是运行时错误（runtime exception）还是应用错误（application exception），都会抛出给链的调用者。但是许多应用都需要对在命令之外定义的错误做明确的处理。Commons Chain提供了Filter接口来满足这个要求。Filter继承了Command，添加了一个名为postprocess的方法。<br /><br />public boolean postprocess(Context context, Exception exception);<br />只要Filter的execute方法被调用，不论链的执行过程中是否抛出错误，Commons Chain都将保证Filter的postprocess方法被调用。和servlet的过滤器（filter）相同，Commons Chain的Filter按它们在链中的顺序依次执行。同样，Filter的postprocess方法按倒序执行。你可以使用这个特性实现自己的错误处理。下面是一个用于处理我们例子中的错误的Filter：<br /><b>清单12</b><br /><pre class="overflow" title="pre code">package com.jadecove.chain.sample;<br /><br />import org.apache.commons.chain.Context;<br />import org.apache.commons.chain.Filter;<br /><br />public class SellVehicleExceptionHandler implements Filter {<br /><br />        public boolean execute(Context context) throws Exception {<br />                System.out.println("Filter.execute() called.");<br />                return false;<br />        }<br /><br />        public boolean postprocess(Context context,<br />                                 Exception exception) {<br />                if (exception == null) return false;<br />                System.out.println("Exception "<br />                              + exception.getMessage()<br />                              + " occurred.");<br />                return true;<br />        }<br />}</pre><br /><br />Filter在配置文件中的定义就和普通的命令（Command）定义相同：<br /><b>清单13</b><br /><pre class="overflow" title="pre code">&lt;chain name="sell-vehicle"&gt;<br />  &lt;command   id="ExceptionHandler"<br />     className =<br />           "com.jadecove.chain.sample.SellVehicleExceptionHandler"/&gt;<br />  &lt;command   id="GetCustomerInfo"<br />      className="com.jadecove.chain.sample.GetCustomerInfo"/&gt;</pre><br /><br />Filter 的execute方法按定义的序列调用。然而，它的postprocess方法将在链执行完毕或抛出错误后执行。当一个错误被抛出时， postprocess方法处理完后会返回true，表示错误处理已经完成。链的执行并不会就此结束，但是本质上来说这个错误被捕捉而且不会再向外抛出。如果postprocess方法返回false，那错误会继续向外抛出，然后链就会非正常结束。<br /><br />让我们假设ArrangeFinancing因为用户信用卡损坏抛出错误。SellVehicleExceptionHandler就能捕捉到这个错误，程序输出如下：<br /><span style="COLOR: blue">Filter.execute() called.<br />Get customer info<br />Test drive the vehicle<br />Negotiate sale<br />Exception Bad credit occurred.</span><br /><br />结合了过滤器（filter）和子链技术后，你就可以造型很复杂的工作流程。<br /><br />Commons Chain是一个很有前途的框架，现在仍在开发，新的功能被频繁地添加到其中。在下一篇关于Commons Chain的文章中，我们将研究Struts 1.3中是如何使用Commons Chain的。<br /><br />Struts 1.3中用完全使用Commons Chain的类替换了原来的处理HTTP请求的类。如果你以前自己定制过Struts的请求处理（request processor），你将发现处理这个问题时Commons Chain为程序带来了很好的灵活性。<img src ="http://www.blogjava.net/lijiajia418/aggbug/64990.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-22 10:40 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/22/64990.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>