﻿<?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-Java Stop Here-文章分类-tec</title><link>http://www.blogjava.net/faithwind/category/7035.html</link><description>Love Java ,because you are my first lady !^_^</description><language>zh-cn</language><lastBuildDate>Thu, 01 Mar 2007 00:25:38 GMT</lastBuildDate><pubDate>Thu, 01 Mar 2007 00:25:38 GMT</pubDate><ttl>60</ttl><item><title>JAVAEE 常见性能问题解决手册</title><link>http://www.blogjava.net/faithwind/articles/61766.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Fri, 04 Aug 2006 07:24:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/61766.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/61766.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/61766.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/61766.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/61766.html</trackback:ping><description><![CDATA[
		<strong>
				<span style="FONT-SIZE: 16px">概要</span>
				<br />
				<br />
		</strong>这篇文章，是PRO JAVA EE 5 Performance Management and Optimization 的一个章节，作者Steven Haines分享了他在调优企业级JAVA应用时所遇到的常见问题。<br /><br />Java EE（Java企业开发平台）应用程序，无论应用程序服务器如何部署，所面对的一系列问题大致相同。作为一个JAVAEE问题解决专家，我曾经面对过众多的环境同时也写了不少常见问题的观察报告。在这方面，我觉得我很象一个汽车修理工人：你告诉修理工人发动机有声音，他就会询问你一系列的问题，帮你回忆发动机运行的情形。从这些信息中，他寻找到可能引起问题的原因。<br /><br />众多解决问题的方法思路基本相同，第一天我同要解决问题的客户接触，接触的时候，我会寻找已经出现的问题以及造成的负面的影响。了解应用程序的体系结构和问题表现出的症状，这些工作很够很大程度上提高我解决问题的几率。在这一节，我分享我在这个领域遇过的常见问题和他们的症状。希望这篇文章能成为你JAVAEE的故障检测手册。<br /><br /><span style="COLOR: red">版权声明：任何获得Matrix授权的网站，转载时请务必保留以下作者信息和链接</span><br />作者:Steven Haines;<a href="http://www.matrix.org.cn/user.shtml?username=tsr106" target="_new">tsr106</a><br />原文:<a href="http://www.javaworld.com/javaworld/jw-06-2006/jw-0619-tuning.html" target="_new">http://www.javaworld.com/javaworld/jw-06-2006/jw-0619-tuning.html</a><br />Matrix:<a href="http://www.matrix.org.cn/resource/article/44/44575_JAVAEE+Performance+Tuning.html" target="_new">http://www.matrix.org.cn/resource/article/44/44575_JAVAEE+Performance+Tuning.html</a><br />关键字:VTJAVAEE;Performance Tuning<br /><br /><b><span style="FONT-SIZE: 16px">内存溢出错误</span></b><br /><br />最常见的折磨着企业级应用程序的错误是让人恐惧的outofmemoryError(内存溢出错误)<br />这个错误引起下面这些典型的症状：<br />----应用服务器崩溃<br />----性能下降<br />----一个看起来好像无法结束的死循环在重复不断的执行垃圾收集，它会导致程序停止运行，并且经常导致应用服务器崩溃<br />不管症状是什么，如果你想让程序恢复正常运行，你一般都需要重新启动应用服务器。<br /><br /><b>引发out-of-memory 错误的原因</b><br />在你打算解决out-of-memory 错误之前，首先了解为什么会引发这个错误对你有很大的帮助。如果JVM里运行的程序, 它的内存堆和持久存储区域的都满了，这个时候程序还想创建对象实例的话，垃圾收集器就会启动，试图释放足够的内存来创建这个对象。这个时候如果垃圾收集器没有能力释放出足够的内存，它就会抛出OutOfMemoryError内存溢出错误。<br /><br />Out-of-memory错误一般是JAVA内存泄漏引起的。回忆上面所讨论的内容，内存泄漏的原因是一个对象虽然不被使用了，但是依然还有对象引用他。当一个对象不再被使用时，但是依然有一个或多个对象引用这个对象，因此垃圾收集器就不会释放它所占据的内存。这块内存就被占用了，堆中也就少了块可用的空间。在WEB REQUESTS中这种类型的的内存泄漏很典型，一两个内存对象的泄漏可能不会导致程序服务器的崩溃，但是10000或者20000个就可能会导致这个恶果。而且，大多数这些泄漏的对象并不是象DOUBLE或者INTEGER这样的简单对象，而可能是存在于堆中一系列相关的对象。例如，你可能在不经意间引用了一个Person对象，但是这个对象包含一个Profile对象，此对象还包含了许多拥有一系列数据的PerformanceReview对象。这样不只是丢失了那个Person对象所占据的100 bytes的内存，你丢失了这一系列相关对象所占据的内存空间，可能是高达500KB甚至更多。<br /><br />为了寻找这个问题的真正根源，你需要判断是内存泄漏还是以OutOfMemoryError形式出现的其他一些故障。我使用以下2种方法来判断：<br />----深入分析内存数据<br />----观察堆的增长方式<br /><br />不同JVM（JAVA虚拟机）的调整程序的运作方式是不相同的，例如SUN和IBM的JVM，但都有相同的的地方。<br /><br /><b>SUN JVM的内存管理方式</b><br />SUN的JVM是类似人类家族，也就是在一个地方创建对象，在它长期占据空间之前给它多次死亡的机会。<br />SUN JVM会划分为：<br />1        年轻的一代（Young generation），包括EDEN和2个幸存者空间（出发地和目的地the From space and the To space）<br />2        老一代(Old generation)<br />3        永久的一代(Permanent generation)<br /><br />图1   解释了SUN 堆的家族和空间的详细分类<br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_110800_fyIrjoBPur.jpg" onload="javascript:imgLoad(this);" border="0" /><br /><br />对象在EDEN出生就是被创建，当EDEN满了的时候，垃圾收集器就把所有在EDEN中的对象扫描一次，把所有有效的对象拷贝到第一个幸存者空间，同时把无效的对象所占用的空间释放。当EDEN再次变满了的时候，就启动移动程序把EDEN中有效的对象拷贝到第二个幸存者空间，同时，也将第一个幸存者空间中的有效对象拷贝到第二个幸存者空间。如果填充到第二个生存者空间中的有效对象被第一个生存者空间或EDEN中的对象引用，那么这些对象就是长期存在的（也就是说，他们被拷贝到老一代）。若垃圾收集器依据这种小幅度的调整收集（minor collection）不能找出足够的空间，就是象这样的拷贝收集（copy collection），就运行大幅度的收集，就是让所有的东西停止（stop-the-world collection）。运行这个大幅度的调整收集时，垃圾收集器就停止所有在堆中运行的线程并执行清除动作（mark-and-sweep collection），把新一代空间释放空并准备重启程序。<br /><br />图2和图3展示的是了小幅度收集如何运行<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_111025_uWijwKdbsn.jpg" onload="javascript:imgLoad(this);" border="0" /><br />图2。对象在EDEN被创建一直到这个空间变满。<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_111104_VKZkYqZRim.jpg" onload="javascript:imgLoad(this);" border="0" /><br />图3。处理的顺序十分重要：垃圾收集器首先扫描EDEN和生存者空间，这就保证了占据空间的对象有足够的机会证明自己是有效的。<br /><br /><br />图4展示了一个小幅度调整是如何运行的<br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_111258_gAtVGtmycy.jpg" onload="javascript:imgLoad(this);" border="0" /><br />图4：当垃圾收集器释放所有的无效的对象并把有效的对象移动到一个更紧凑整齐的新空间，它将EDEN和生存者空间清空。<br /><br />以上就是SUN实现的垃圾收集器机制，你可以看出在老一代中的对象会被大幅度调整器收集清除。长生命周期的对象的清除花费的代价很高，因此如果你希望生命周期短的对象在占据空间前及时的死亡，就需要一个主垃圾收集器去回收他们的内存。<br /><br />上面所讲解的东西是为了更好的帮助我们识别出内存泄漏。当JAVA中的一个对象包含了一个并不想要的一个指向其他对象的引用的时候，内存就会泄漏，这个引用阻止了垃圾收集器去回收它所占据的内存。采用这种机制的SUN 虚拟机，对象不会被丢弃，而是利用自己特有的方法把他们从乐园和幸存者空间移动到老一代地区。因此，在一个基于多用户的WEB环境，如果许多请求造成了泄漏，你就会发现老一代的增长。<br /><br />图5显示了那些潜在可能造成泄漏的对象：主收集器收集后遗留下来占据空间的对象会越来越多。不是所有的占据空间的对象都造成内存泄漏，但是造成内存泄漏的对象最终都占据者空间。如果内存泄漏的确存在，这些造成泄漏的对象就会不断的占据空间，直至造成内存溢出。<br /><br />因此，我们需要去跟踪垃圾收集器在处理老一代中的运行：每次垃圾收集器大幅度收集运行时，有多少内存被释放？老一代内容是不是按一定的原理来增长？<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_111202_aAIhawojNq.jpg" onload="javascript:imgLoad(this);" border="0" /><br />图5。阴影表示在经过大幅度的收集后幸存下来的对象，这些对象是潜在可能引发内存泄漏的对象<br /><br />一部分这些相关的信息是可以通过跟踪API得到，更详细的信息通过详细的垃圾收集器的日志也可以看到。和所有的跟踪技术一样，日值记录详细的程度影响着JVM的性能，你想得到的信息越详细，付出的代价也就越高。为了能够判断内存是否泄漏，我使用了能够显示辈分之间所有的不同的较权威的技术来显示他们的区别，并以此来得到结果。SUN 的日志报告提供的信息比这个详细的程度超过5％，我的很多客户都一直使用那些设置来保证他们管理和调整垃圾收集器。下面的这个设置能够给你提供足够的分析数据：<br />–verbose:gc –xloggc:gc.log –XX:+PrintGCDetails –XX:+PrintGCTimeStamps <br /><br />明确发现在整个堆中存在有潜在可能泄漏内存的情况，用老一代增长的速率才比较有说服力。切记调查不能决定些什么：为了能够最终确定你内存泄漏，你需要离线在内存模拟器中运行你的应用程序。<br /><br /><b>IBM JVM内存管理模式</b><br />IBM的JVM的机制有一点不同。它不是运行在一个巨大的继承HEAP中，它仅在一个单一的地区维护了所有的对象同时随着堆的增长来释放内存。这个堆是这样运行的：在一开始运行的时候，它会很小，随着对象实例不断的填充，在需要执行垃圾收集的地方清除掉无效的对象同时把所有有效的对象紧凑的放置到堆的底部。因此你可能猜测到了，如果想寻找可能发生的内存泄漏应该观察堆中所有的动作，堆的使用率是在提高？<br /><br /><b>如何分析内存泄漏</b><br />内存泄漏非常难确定，如果你能够确定是请求导致的，那你的工作就非常简单了。把你的程序放入到运行环境中，并在内存模拟器中运行，按下面的步骤来：<br />1．        在内存模拟器中运行你的应用程序<br />2．        执行使用方案（制造请求）以便让程序在内存中装载请求所需要的所有的对象，这可以为以后详细的分析排除不必要的干扰<br />3．        在执行使用方案前对堆进行拍照以便捕获其中所有运行的对象。<br />4．        再次运行使用方案。<br />5．        再次拍照，来捕获使用方案运行之后堆中所有对象的状态。<br />6．        比较这2个快照，找出执行使用方案后本不应该出现在堆中的对象。<br /><br />这个时候，你需要去和开发者交流，告诉他你所碰到的棘手的请求，他们可以判断究竟是对象泄漏还是为了某个目的特地让对象保留下来的。如果执行完后并没有发现内存泄漏的情况，我一般会转到步骤4再进行多次类似的跟踪。比如，我可能会将我的请求反复运行17次，希望我的泄漏分析能够得到17个情况（或更多）。这个方法不一定总有用，但如果是因为请求引起的对象泄漏的话，就会有很大的帮助。<br /><br />如果你无法明确的判断泄漏是因为请求引发的，你有2个选择：<br />1．        模拟每一个被怀疑的请求直至发现内存泄漏<br />2．        存配置一个内存性能跟踪工具<br /><br />第一个选项在小应用程序中是确实可用的或者你非常走运的解决了问题，但对大型应用程序不太有用。如果你有跟踪工具的话第二个选择是比较有用的。这些工具利用字节流工具跟踪对象的创建和销毁的数量，他们可以报告特定类中的对象的数量状态，例如把Collections类作为特定的请求。例如，一个跟踪工具可以跟踪/action/login.do请求，并在它完成后将其中的100个对象放入HASHMAP中。这个报告并不能告诉你造成泄漏的是代码还是某个对象，而是告诉你在内存模拟器中应该留意那些类型的请求。把程序服务器放到产品环境中并不会使他们变敏感，而是跟踪性能的工具可以使你的工作变的更简单化。<br /><br /><b>虚假内存泄漏</b><br />少数的一些问题看起来是内存泄漏实际上并非如此。<br />我将这些情况称为假泄漏，表现在下面几种情况：<br />1．        分析过早<br />2．        Session泄漏<br />3．        异常的持久区域<br /><br />这章节对这些假泄漏都进行了调查，描述了如何去判断这些情况以及如何处理.<br /><br /><b>不要过早分析</b><br />为了在寻找内存泄漏的时候尽量减少出现判断错误的可能性，你应当在适当的时候分析堆。危险是：一些生命周期长的对象需要装载到堆中，因此在堆达到稳定状态且包含了核心对象之前具有很大的欺骗性。在分析堆之前，应该让应用程序达到稳定状态。<br />为了判断是否过早的对堆进行分析，持续2个小时对跟踪到的分析快照进行分析，看堆的使用率是上升还是下降。如果是下降，保存这个时候的内存记录。如果是上升，这个时候就需要分析内存中的SESSION了。<br /><br /><b>发生泄漏的session</b><br />WEB请求经常导致内存泄漏，在一个WEB请求中，对象会被限制存储在有限的几个区域。这些区域就是：<br />1．        页面区域<br />2．        请求区域<br />3．        上下文区域<br />4．        应用程序区域<br />5．        静态变量<br />6．        长生命周期的变量，例如SERVLET<br /><br />当实现一些JSP（JAVASERVER页面）时，在页面上声明的变量在页面结束的时候就被释放，这些变量仅仅在这个单独的页面存在时存在。WEB服务器会向应用程序服务器传送一系列参数和属性，也就是在SERVLET和JSP之间传输HttpServletRequest中的对象。你的动态页面依靠HttpServletRequest在不同的组件之间传输信息，但当请求完成或者socket结束的时候，SERVLET控制器会释放所有在HttpServletRequest 中的对象。这些对象仅在他们的请求的生命周期内存在。<br /><br />HTTP是无状态的，这意味着客户向服务器发送一个请求，服务器回应这个请求，这个传递就完成了，就是会话结束了。我们应该感激WEB页面帮我们做的日志，这样我们就能向购物车放置东西，并去检查它，服务器能够定义一个跨越多请求的扩展对话。属性和参数被放在各自用户的HttpSession对象中，并通过它让程序的SERVLET和JSP交流。利用这种办法，页面存储你的信息并把他们添加到HttpSession中，因此你可以用购物车购买东西，并检查商品和使用信用卡付帐。作为一个无状态的协议，它总是客户端发起连接请求，服务器需要知道一个会话存在多长时间，到时候就应该释放这个用户的数据。超过这个会话的最长时间就是会话超时，他们在程序服务器中设置。除非明确的要求释放对象或者这个会话失效，否则在会话超时之前会话中的对象会一直存在。<br /><br />正如session是为每个用户管理对象一样，ServletContext为整个程序管理对象。ServletContext的有效范围是整个程序，因此你可以利用Servlet中的ServletContext或者JSP应用程序对象在所有的Servlet和JSP之间让在这个程序中的所有用户共享数据。ServletContext是最主要的存放程序配置信息和缓存程序数据的地方，例如JNDI的信息。<br /><br />如果数据不是存储这个四个地方（页面范围，请求范围，会话范围，程序范围）那就可能存储在下面的对象中：<br />1．        静态变量<br />2．        长生命周期的类变量<br /><br />每个类的静态变量被JVM（JAVA虚拟机）所控制，他们存在与否和类是否已经被初始化无关。一个类的所有实例共用一个存储静态变量的地方，因此在任何一个实例中修改静态变量会影响这个类的其他实例。因此，如果一个程序在静态变量中存放了一个对象，如果这个变量生命周期没有到，那么这个对象就不会被JVM释放。这些静态对象是造成内存泄漏的主要原因。<br /><br />最后，对象能够被放到内部数据类型或者长生命周期类中的成员变量中，例如SERVLET。当一个SERVLET被创建并且被装载到内存，它在内存中仅有一个实例，采用多线程去访问这个SERVLET实例。如果在INIT()方法中装载配置信息，将他存储于类变量中，那么当需要维护的时候就可以随时读出这些信息，这样所有的对象就用相同的配置。我常碰到的一个问题就是利用SERVLET类变量去存储象页面缓存这样的信息。在他们自己内部本身存贮这些缓存配置是个不错的选择，但存贮在SERVLET中是最糟糕的情况。如果你需要使用缓存，你最好使用第三方控制插件，例如 TANGOSOL的COHERENCE。<br /><br />当在页面或者请求范围中利用变量存放对象的时候，在他们结束的时候这些对象会自动释放。同样，在SESSION中存放对象的时候，当程序明确说明此SESSION失效的或者会话执行超时的时候，这些对象才会自动被释放。<br /><br />很多看起来象内存泄漏的情况都是上面的那些会话中的泄漏。一个造成泄漏的会话并不是泄漏了内存而是类似于泄漏，它消耗了内存，但最终这些内存都会被释放的。如果程序服务器发生内存溢出，判断是内存泄漏还是内存缺乏的最好的方法就是：停止所有向这个服务器所发的请求的对象，等待会话超时，看内存时候会被释放出来。这虽然不会一定能够达到你要的目的，但是这是最好的分段处理方法，当你装载测试器的时候，你应该先挂断你内容巨大的会话而不是先去寻找内存泄漏。<br /><br />通常来说，如果你执行了一个很大的会话，你应该尽量去减少它所占用的内存空间，如果可以的话最好能重构程序，以减少session所占据的内存空间。下面2种方法可以降低大会话和内存的冲突：<br />1．        增大堆的空间以支持你的大会话<br />2．        缩短会话的超时时间，让它能够快速的失效<br /><br />一个巨大的堆会导致垃圾回收花费更多的时间，因此这不是一个好解决方法，但总比发生OutofMemoryError强。增加足够的堆空间以使它能够存储所有应该保存的有效值，也意味着你必须有足够的内存去存储所有访问你站点的用户的有效会话。如果商业规则允许的话最好能缩短会话超时的时间，以减少堆占用空间的冲突。<br />总结下，你应该依据合理性和重要性按下面的步骤依次去执行：<br />1．        重构程序，尽量减少拥有session范围的变量所存储的信息量<br />2．        鼓励你的客户在他们使用完后，明确的释放会话<br />3．        缩短超时的时间，以便于让你内存尽快的得到回收<br />4．        增加你堆空间的大小<br /><br />无论如何，不要让程序范围级的变量，静态变量，长生命周期的类存储对象，事实上，你需要在内存模拟器中去分析泄漏。<br />异常的持久空间<br /><br />容易误解JVM为持久空间分配内存的目的。堆仅仅存储类的实例，但JVM在堆中创建类实例之前，它必须把字节流文件（.class文件）装载到程序内存中。它利用内存中的字节流在堆中创建类的实例。JVM利用程序的内存来装载字节流文件，这个内存空间称为持久空间。图6显示了持久空间和堆的关系：它存在于JVM程序中，并不是堆的一部分。<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_111525_tfkhkYSdOx.jpg" onload="javascript:imgLoad(this);" border="0" /><br />Figure 6. The relationship between the permanent space and the heap<br /><br />通常，你可能想让你的持久空间足够大以便于它能够装载你程序所有的类，因为很明显，从文件系统中读取类文件比从内存中装载代价高很多。JVM提供了一个参数让你不的程序不卸载已经装载到持久空间中的类文件：<br /><span style="COLOR: blue">–noclassgc </span><br /><br />这个参数选项告诉JVM不要跑到持久空间去执行垃圾收集释放其中已经装载的类文件。这个参数选项很聪明，但是会引起一个问题：当持久空间满了以后依然需要装载新文件的时候JVM会怎么处理呢？我观测到的资料说明：如果JVM检测到持久空间还需要内存，就会调用主垃圾收集程序。垃圾收集器清除堆，但它并不会对持久空间进行任何操作，因此它的努力是白费的。于是JVM就再重新检测持久空间，看它是否满，然后再次执行程序，一遍的一遍重复。<br /><br />我第一次碰到这种问题的时候，用户抱怨说程序性能很差劲，并且在运行了几次后就出现了问题，可能是内存溢出问题。在我调查了详细的关于堆和程序内存利用的收集器的记录后，我迅速发觉堆的状态非常正常，但程序确发生了内存溢出。这个用户维持了数千的JSP页面，在装载到内存前把他们都编译成了字节流文件放入持久空间。他的环境已经造成了持久空间溢出，但是在堆中由于用了 －noclassgc 选项，于是JVM并不去释放类文件来装载新的类文件。于是就导致了内存溢出错误，我把他的持久空间改为512M大小，并去掉了 －noclassgc 参数。<br /><br />正像图7显示的，当持久空间变满了的时候，就引发垃圾收集，清理了乐园和幸存者空间，但是并不释放持久空间中的一点内存。<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_111624_CegPhkFeRe.jpg" onload="javascript:imgLoad(this);" border="0" /><br />Figure 7. Garbage collection behavior when the permanent space becomes full. Click on thumbnail to view full-sized image.          <br />                         <br /><b>注意</b><br />当设置持久空间大小时候，一般考虑128M，除非你的程序有很多的类文件，这个时候，你就可以考虑使用256M大小。如果你想让他能够装载所有的类的时候，就会导致一个典型的结构错误。设置成512M就足够了，它仅仅是暂时的时间的花费。把持久空间设置成512M大小就象给一个脚痛的人吃止痛药，虽然暂时缓解了痛，但是脚还是没有好，依然需要医生把痛治疗好，否则只是把问题延迟了而已。<br /><br /><b><span style="FONT-SIZE: 16px">线程池</span></b><br /><br />外界同WEB或程序服务器连接的主要方法就是向他们发送请求，这些请求被放置到程序的执行次序队列中。和内存最大的冲突就是程序服务器所设置的线程池的大小。线程池的大小就是程序可以同时处理的请求的数量。如果池太小，请求就需要在队列中等待程序处理，如果太大，CPU就需要花费太多的时间在这些众多的线程之间来回的切换。<br />每个服务器都有一个SOCKET负责监听。程序把接受到的请求放到待执行队列中，然后将这个请求从队列移动到线程中被程序处理。<br /><br />图8显示了服务器的处理程序。<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_111825_sFWaoYHDTl.jpg" onload="javascript:imgLoad(this);" border="0" /><br />Figure 8. 服务器处理请求的次序结构<br /><br /><b>线程池太小</b><br />每当我碰到有人抱怨装载速度的性能随着装载的数量的增加变的越来越糟糕的时候，我会首先检查线程池。特别是，我在看到下面这些信息的时候：<br />1．线程池的使用<br />2．很多请求等待处理（在队列中等待处理）<br /><br />当一个线程池被待处理的请求装满的时候，响应的时间就变的极其糟糕，因为这些在队列中等待处理的请求会消耗很多的额外时间。这个时候，CPU的利用率会非常低，因为程序服务器没有时间去指挥CPU工作。这个时候，我会按一定幅度增加调节池的大小，并在未处理请求的数量减少前一直监视程序的吞吐量，你需要一个合理甚至更好的负载量者，一个精确的负载量测试工具可以准确的帮你测试出结果。当你观测吞吐量的时候，如果你发现吞吐量降低了，你就应该把池的大小下调一个幅度，一直到找到让它保持最大吞吐量的大小为止。<br /><br />图9显示了连接池太小的情况<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_111839_OBpJnguGBH.jpg" onload="javascript:imgLoad(this);" border="0" /><br />Figure 9. 所有的线程都被占用了，请求就只能在队列中等待<br /><br />每当我阅读性能调整手册的时候，最让我头疼的就是他们从来不告诉你特殊情况下线程池应该是多大。由于这些值非常依赖程序的行为，他们只告诉你大普通情况下正确的大小，但是他们给了你一个范围内的值，这对用户很有利的。例如考虑下面2种情况:：<br />1．        一个程序从内存中读出一个字符串，把它传给JSP页面，让JSP页面去显示<br />2．        另一个程序从数据库中读出1000个数值，为这些不规则的数值求平均。第一个程序对请求的回应会很块，大概仅需要不足0.25秒的时间，且不怎么占据CPU。第二个程序可能需要3秒去回应，同时会占据CPU。因此，为第一个程序配置的池大小是100就有点太小了，因为程序能够同时处理200个；但为第二个程序配置的池是100，就有点太大了，因为CPU可能就能应付50个线程。<br />但是，很多程序并没有在这种情况下动态的去调整的功能。多数情况下是做相同的事，但是应该为他们划分范围。因此，我建议你为一个CPU分配50到75个左右的线程。对一些程序来说，这个数量可能太少，对另一个些来说可能太多，我刚开始为每个CPU分配50到75个线程，然后根据吞吐量和CPU的性能，并做适当的调整。<br /><br /><b>线程池太大</b><br />除了线程池数量太小之外的情况外，环境也可能把线程数量配置的过大。当这些环境中的负载量不断增大的时候，CPU的使用率会持续无法降低，就没有什么响应请求的时间了，因为CPU只顾的在众多的线程之间来回的切换跳动，没时间让线程去做他们应该做的事了。<br /><br />连接池过大的最主要的迹象就是CPU的使用率一直很高。有些时候，垃圾收集也可能导致CPU使用率很高，但是垃圾收集导致的CPU使用率很高和池过大导致的使用率有一个主要的区别就是：垃圾收集引起的只是短时间的高使用率就象个钉子，而池过大导致的就是一直持续很高呈线性。<br /><br />这个情况发生的时候，请求会被放在队列中不被处理，但是不会始终如此，因为请求占用CPU的情况和程序占用的情况造成的后果不同。降低线程池的大小可能会让请求等待，但是让请求等待总比为了处理请求而让CPU忙不过来的好。让CPU保持持续的高使用率，同时性能不降低，新请求到来的时候放入到队列中，这是最理想的程序。考虑下面这个很类似的情况：很多高速公里有交通灯来保证车辆进入到拥挤的公里中。在我看来，这些交通灯根本没用，道理很充分。比如你来了，在交通灯后面的安全线上等待进入到高速公路上。如果所有的车辆都同时涌向公里，我们就动弹不得，但是只要减缓涌向高速公路车辆的速度，交通迟早会畅通。事实上，很多的大城市都有这样功能，但根本没用，他们真正需要的是一些更多的小路（CPU），涌向高速公路的速度真的降低了，那么交通会变的正常起来。<br /><br />设置一个饱和的池，然后逐步减少连接池大小，一直到CPU占用率为75％到85％之间，同时用户负载正常。如果等待队列大小实在无法控制，考虑下面2中建议：<br />1．把你的程序放入代码模拟器运行，调整程序代码<br />2．增加额外的硬件<br /><br />如果你的用户负载超过了环境能承受的范围，你应该考虑修正代码减少和CPU的冲突或者增加CPU。<br /><br /><b><span style="FONT-SIZE: 16px">JDBC连接池</span></b><br /><br />很多JAVA EE 程序连接到一个后台数据源，大多数是通过JDBC（JAVA DATABASE CONNECTIVITY）将程序和后台连接起来。由于创建数据库连接的代价很高，程序服务器让在同一个程序服务器实例下的所有程序共享特定数量的一些连接。如果一个请求需要连接到数据库，但是数据库的连接池无法为这个请求创建一个新连接，这个时候请求就会停下来等待连接池完成自己的操作再给她分配一个连接。反过来，如果数据库连接池太大程序服务器就会浪费资源，并且程序有可能强迫数据库承受过量的负荷。我们调试的目的就是尽量减少请求的等待时间和饱和的资源之间之间的冲突，让一个请求在数据库外等待要比强迫数据库好的多。<br /><br />一个程序服务器如果设置连接的数量不合理就会有下面这些特征：<br />1．程序运行速度缓慢<br />2．CPU使用率低<br />3．数据库连接池使用率非常高<br />4．线程等待数据库的连接<br />5．线程使用率很高<br />6．请求队列中有待处理的请求（潜在的）<br />7．数据库CPU使用率很低（因为没有足够的请求能够让他繁忙起来）<br /><br /><b>JDBC prepared statements</b><br />和JDBC相关的另一个重要的设置就是：为JDBC使用的statement 所预设的缓存的大小。当你的程序在数据库中运行SQL statement 的时候三下面3个步骤进行：<br />1．准备<br />2．执行<br />3．返回数值<br /><br />在准备阶段，数据库驱动器让数据库完成队列中的执行计划。执行的时候，数据库执行语句并返回指向结果的引用。在返回的时候，程序重新描述这些结果并描述出这些被请求的信息。<br /><br />数据库驱动会这样优化程序：首先，你需要去准备一个statement ，这个statement 它会让数据库做好执行和缓存结果的准备。在此同时，数据库驱动会从缓存中装载已经准备好的statement ，而不用直接连接到数据库。<br /><br />如果prepared statement 设置太小，数据库驱动器会被迫去查询没有装载进缓存区的statement ，这就会增加额外的连接到数据库的时间。prepared statement 缓存区设置不恰当最主要的症状就是花费大量的时间去连接相同的statement。这段被浪费的时间本来是为了让它去装载后面的调用的。<br /><br />事情变的稍微复杂了点，缓存prepared statement 是每个statement的基础，就是说在一个statement连接之前都应当缓存起来。这个增加的复杂性就产生了一个冲突：如果你有100个prepared statement需要去缓存，但你的连接池中有50个数据库连接，这个时候你就需要有存放5000条预备语句的内存。<br /><br />通过跟踪性能，确定出你程序所执行的不重复的statement 的数量，并从这些statement 中找出哪些条是频繁执行的。<br /><br /><b><span style="FONT-SIZE: 16px">Entity bean（实体BEAN）和stateful session bean的缓冲</span></b><br /><br />无状态（stateless）对象可以被放入到池中共享，但象Entity beans和 stateful session bean这样的有状态的对象就需要被缓存，因为这些bean的每个实例都是不相同的。当你需要一个有状态对象时，你需要明确创建这个对象的特定实例，普通的实例是不能满足的。类似的，你考虑一个超市类似的情况，你需要个售货员但他叫什么并不重要，任何售货员都可以满足你。也就是，售货员被放入池中共享，因为你只需要是售货员就可以，而不是一个叫做史缔夫的这个售货员。当你离开超市的时候，你需要带上你的孩子，不是其他人的孩子，而是你自己的。这个时候，孩子就需要被缓存。<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_112603_rhMHxXnPGC.jpg" onload="javascript:imgLoad(this);" border="0" /><br />Figure 10. The application requests an object from the cache that is in the cache, so a reference to that object is returned without making a network trip to the database<br /><br />当你的缓存区太小的时候，缓存的性能就会明显的受到影响。特别是，当一个请求去一个已经满了的缓存区域去请求一个对象的时候，下面的步骤就会执行，这些步骤会在图11中显示：<br />1．        程序请求一个对象<br />2．        缓存检测这个对象是否已经存在于缓存中<br />3．        缓存决定把一个对象开除出缓存（一般采用的算法是遗弃最近使用次数最少的对象）<br />4．        把这个对象扔出缓存（称为passivated）<br />5．        把从数据库中装载这个新对象并放入到缓存（称为activated）<br />6．        把指向这个对象的引用返回给程序<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_08_03_112626_NElePQVlUN.jpg" onload="javascript:imgLoad(this);" border="0" /><br />Figure 11. Because the requested object is not in the cache, an object must be selected for removal from the cache and removed from it.<br /><br />如果多数的请求都需要执行这些步骤的话，那你采用缓存技术就不是好的选择了！如果这些处理步骤频繁发生的话，你就需要重新推敲下你的缓存了。回忆一下：从缓存中去除一个对象称为passivation，从持久存储区取出一个对象放入缓存称为activation。能在缓存中找到的请求（缓存中有此请求的对象）的百分率称为hit ratio，相反找不到的请求的百分率称为miss ratio。<br /><br />缓存刚被初始化的时候，hit ratio是0，它的activation数量非常高，因此在初始化后你需要去观察缓存的性能。初始化以后，你应该跟踪passivation的数量并把它和与向缓存请求对象的请求的总量相比较，因为passivations只会发生在缓存被初始化以后。但一般来说，我们更需要关心缓存的miss ratio。如果miss ratio超过25％，那么缓存可能是太小了。因此，如果missratio的数量超过75％，那么不是你的缓存设置的太小就是你不需要缓存这个技术。<br /><br />一旦你觉得你的缓存太小，就去尝试着增大大小，并测试增加的性能。如果miss ration下降到20％以下，那你的缓存的大小就非常棒了，如果没有什么效果，那么你就需要和这个程序的技术员联系，看是这个对象是不是需要缓存或者是否应该修正程序中这个对象的代码。<br /><br /><b><span style="FONT-SIZE: 16px">Staless session bean和message-driven bean池</span></b><br /><br />Stateless session bean 和message-driven bean 在商业应用方面很重要，不要期望它们会保持自己特有的状态信息。当你的程序需要使用这些BEAN的商业功能的时候，它就从一个池中取出一个BEAN实例，用这个实例来调用一个个方法，用完后再将BEAN的实例再放回到池中。如果你的程序过了一会又需要这个一摸一样的BEAN，就从池中再得到一个实例，但不能保证你得到的就是上一个实例。池能够让程序共享资源，但是会让你的程序付出潜在的等待时间。如果你无法从池中得到想要的BEAN，请求就会等待，一直到这个BEAN被放入到池中。很多程序服务器都会把这些池调整的很好，但是我碰到过因为在环境中把他们设置的太小而引发的不少麻烦。Stateless bean池的大小应该和可执行线程池的大小一般大，因为一个线程同时只能使用一个对象，再多了就造成浪费的。因此，一些程序服务器把池的大小和线程的数量设置成同样的数量。为了保险起见，你应该亲自把它设置成这个数。<br /><br /><b><span style="FONT-SIZE: 16px">事务</span></b><br /><br />使用Enterprise Java的一个好处就是它天生就支持事务。通过JAVAEE 5 EJB（Enterprise javaBeans）的注释，你可以控制事务中方法的使用。事务会以下面2中方式结束：<br />1．        事务提交<br />2．        事务回滚<br /><br />当一个事务被提交的时候，说明它已经完全成功了，但是当它回滚的时候，就说明发生了一些错误。回滚会是下面2种情况：<br />1．        程序造成的回滚（程序回滚）<br />2．        非程序造成的回滚（非程序回滚）<br /><br />通常，程序回滚是因为商业的规定。比如一个WEB程序做一个素描画的价格的调查，程序可能让用户输入年龄，并且商业规定18岁以上才可以进入。如果一个16岁的提交了信息，那么程序就会抛出一个错误，打开一个网页告诉他，他年龄还不能参与到这个信息的调查。因为程序抛出了异常，因此包含在程序中的事务的就会发生回滚。这只是普通的程序回滚，只有当发生大量的程序回滚才值得我们注意。<br /><br />另一方面，非程序回滚是非常糟糕的。有三种情形的非程序回滚：<br />1．        系统回滚<br />2．        超时回滚<br />3．        资源回滚<br /><br />系统回滚意味着程序服务器中的一些东西非常的糟糕，恢复的几率很渺茫。超时回滚就是当程序服务器中的程序处理请求时超时；除非你把超时设置的很短才会出现这种错误。资源回滚就是当一个程序服务器管理内部的资源的时候发生错误。例如，如果你设置你的程序服务器通过一个简单的SQL语句去测试数据库的连接，但数据库对于程序服务器来说是无法连接的，这个时候任何和这个资源相关的事情都会发生资源回滚。<br /><br />如果发生非程序回滚，我们应该立刻注意，这个是不小的问题，但是你也需要留意程序回滚发生的频率。很多时候人们对发生的异常很敏感，因此你需要哪些异常对你程序来说才是重要的。<br /><br /><b><span style="FONT-SIZE: 16px">总结</span></b><br /><br />尽管各个程序和他们的环境都各不相同，但是有一些共同的问题困扰着他们。这篇文章的注意力并不是放在程序代码的问题上，因为把注意力放在因为环境的问题而导致的低性能的问题上：<br />1．内存溢出<br />2．线程池大小<br />3．JDBC连接池大小<br />4．JDBC预先声明语句缓存大小<br />5．缓存大小<br />6．池大小<br />7．执行事务时候的回滚<br /><br />为了有效的诊断性能的问题，你应该了解什么问题会导致什么样的症状。如果主要是程序的代码导致的恶果那你应该带着问题去寻求负责代码的人寻求帮助，但是如果问题是由环境引起的，那么就要依靠你的操作来解决了。<br />问题的根源依赖于很多要素，但是一些指示器可以增加一些你处理问题时候的一些信心，依靠他们可以完全排除一些其他的原因。我希望这个文章能对你排解JAVAEE环境问题起到帮助。<br /><br /><b>作者简介</b><br />Steven Haines是3本JAVA书籍的作者：The Java Reference Guide (InformIT/Pearson, 2005), Java 2 Primer Plus (SAMS, 2002), and Java 2 From Scratch (QUE, 1999)。另外多次为别人的文章提供了帮助，同时和其他人合著了一些其他的书，同时也是不少软件杂志的编辑，也是InformIT.com的主持人。作为一个教育家，他在Learning Tree University 合 the University of California 学校教授了不少JAVA方面的知识。白天他在Quest Software做JAVA EE 5性能架构师，同时定义性能跟踪调节软件以及控制管理大规模的JAVA EE 5部署的性能，包括那些世界500强企业。<img src ="http://www.blogjava.net/faithwind/aggbug/61766.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-08-04 15:24 <a href="http://www.blogjava.net/faithwind/articles/61766.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>iframe高度</title><link>http://www.blogjava.net/faithwind/articles/42773.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Mon, 24 Apr 2006 01:37:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/42773.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/42773.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/42773.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/42773.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/42773.html</trackback:ping><description><![CDATA[
		<div id="entryCopyright">
				<strong>
						<font color="black">Made In Zeal 转载请保留原始链接：</font>
				</strong>
				<a href="http://www.zeali.net/blog/entry.php?id=81">http://www.zeali.net/blog/entry.php?id=81</a>
		</div>
		<div style="FONT-WEIGHT: bold; FONT-SIZE: 12px; COLOR: #336666">关键字: <a href="http://technorati.com/tag/javascript" rel="tag">javascript</a>,<a href="http://technorati.com/tag/iframe" rel="tag">iframe</a>,<a href="http://technorati.com/tag/%E9%AB%98%E5%BA%A6" rel="tag">高度</a>,<a href="http://technorati.com/tag/firefox" rel="tag">firefox</a></div>
		<div id="entry_introduce">
				<div class="entry_introduce">当你在页面上使用了<font color="#0000ff">iframe</font>之后，一般来说会不希望iframe显示难看的滚动条，以使iframe里面的内容和主页面的内容浑然一体。这时候你会设置 <font color="#ff0000">scrolling</font>=<font color="#ff1493">"no"</font> 属性。但是这样一来如果iframe里面的内容是变化的，高度会随之内容的变化而变化的时候，你的iframe就会显得太长导致底下一大片空白，或者正好相反，由于iframe的高度太小导致一部分内容会被挡住。这里我提供一个兼容IE/NS/Firefox的<font color="#0000ff">javascript</font>脚本实现动态调整iframe的高度。如果需要调整宽度的话，原理是一样的，本文不加详述。</div>
		</div>
		<div id="entryBody">
				<p>首先，在你的主页面上必须包含以下这段javascript代码：</p>
				<p>
						<span style="COLOR: #0000ff">&lt;</span>
						<span style="COLOR: #800000">script </span>
						<span style="COLOR: #ff0000">language</span>
						<span style="COLOR: #0000ff">="Javascript"</span>
						<span style="COLOR: #0000ff">&gt;</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">
						</span>
						<br />
						<span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">var</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">getFFVersion</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">=</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">navigator.userAgent.substring(navigator.userAgent.indexOf(</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">"</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">Firefox</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">"</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">)).split(</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">"</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">/</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">"</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">)[</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">1</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">]</span>
						<br />
						<span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5">//</span>
						<span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5">extra height in px to add to iframe in FireFox 1.0+ browsers</span>
						<span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5">
						</span>
						<br />
						<span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">var</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">FFextraHeight</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">=</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">getFFVersion</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">&gt;=</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">0.1</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">?</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">
						</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">16</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">: </span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">0</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">
						</span>
						<br />
						<br />
						<span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">function</span>
						<span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">dyniframesize(iframename) {</span>
						<br />  <span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">var</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> pTar </span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">=</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"></span><span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">null</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">;</span><br />  <span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">if</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> (document.getElementById){<br />    pTar </span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">=</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> document.getElementById(iframename);<br />  }</span><br />  <span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">else</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">{<br />    eval('pTar </span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">=</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> ' </span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">+</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> iframename </span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">+</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> ';');<br />  }</span><br />  <span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">if</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> (pTar </span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">&amp;&amp;</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"></span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">!</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">window.opera){</span><br />    <span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5">//</span><span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5">begin resizing iframe</span><span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5"></span><br /><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">    pTar.style.display</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">=</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">"</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">block</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">"</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"></span><br />    <br />    <span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">if</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> (pTar.contentDocument </span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">&amp;&amp;</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> pTar.contentDocument.body.offsetHeight){</span><br />      <span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5">//</span><span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5">ns6 syntax</span><span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5"></span><br /><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">      pTar.height </span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">=</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> pTar.contentDocument.body.offsetHeight</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">+</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">FFextraHeight; <br />    }</span><br />    <span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">else</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"></span><span style="COLOR: #0000ff; BACKGROUND-COLOR: #f5f5f5">if</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> (pTar.Document </span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">&amp;&amp;</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> pTar.Document.body.scrollHeight){</span><br />      <span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5">//</span><span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5">ie5+ syntax</span><span style="COLOR: #008000; BACKGROUND-COLOR: #f5f5f5"></span><br /><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">      pTar.height </span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5">=</span><span style="COLOR: #000000; BACKGROUND-COLOR: #f5f5f5"> pTar.Document.body.scrollHeight;<br />    }<br />  }<br />}</span><br /><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">script</span><span style="COLOR: #0000ff">&gt;</span></p>
				<p>然后对于主页面用到iframe的地方添加代码：</p>
				<p>
						<span style="COLOR: #0000ff">&lt;</span>
						<span style="COLOR: #800000">iframe </span>
						<span style="COLOR: #ff0000">id</span>
						<span style="COLOR: #0000ff">="myTestFrameID"</span>
						<span style="COLOR: #ff0000">
								<br />onload</span>
						<span style="COLOR: #0000ff">="javascript:{dyniframesize('myTestFrameID');}"</span>
						<span style="COLOR: #ff0000">
								<br />marginwidth</span>
						<span style="COLOR: #0000ff">=0 </span>
						<span style="COLOR: #ff0000">marginheight</span>
						<span style="COLOR: #0000ff">=0 </span>
						<span style="COLOR: #ff0000">frameborder</span>
						<span style="COLOR: #0000ff">=0 </span>
						<br />
						<span style="COLOR: #ff0000">scrolling</span>
						<span style="COLOR: #0000ff">=no </span>
						<span style="COLOR: #ff0000">src</span>
						<span style="COLOR: #0000ff">="/myiframesrc.php"</span>
						<span style="COLOR: #ff0000">
								<br />width</span>
						<span style="COLOR: #0000ff">=200 </span>
						<span style="COLOR: #ff0000">height</span>
						<span style="COLOR: #0000ff">=100</span>
						<span style="COLOR: #0000ff">&gt;&lt;/</span>
						<span style="COLOR: #800000">iframe&gt;<br /><br /><font color="#000000">有时为了方便使用Iframe，但被潜入的页面长度不是固定的，显示滚动条不仅影响美观还对用户操作带来不便，自动调整高度可以解决这个问题。<br />&lt;SCRIPT LANGUAGE="JavaScript"&gt;<br /><br />function f_frameStyleResize(targObj){<br /><br />var targWin = targObj.parent.document.all[targObj.name];<br /><br />if(targWin != null) {<br /><br />var HeightValue = targObj.document.body.scrollHeight<br /><br />if(HeightValue &lt; 600){HeightValue = 600} //不小于600<br /><br />targWin.style.pixelHeight = HeightValue;<br /><br />}<br /><br />}<br /><br />function f_iframeResize(){<br /><br /><br />bLoadComplete = true; f_frameStyleResize(self);<br /><br />}<br /><br />var bLoadComplete = false;<br /><br />window.onload = f_iframeResize;<br /><br />&lt;/SCRIPT&gt;</font><br /><br /><br /><br /><font color="#000000">源代码如下<br /><br />&lt;script type="text/javascript"&gt;<br />//** iframe自动适应页面 **//<br /><br />//输入你希望根据页面高度自动调整高度的iframe的名称的列表<br />//用逗号把每个iframe的ID分隔. 例如: ["myframe1", "myframe2"]，可以只有一个窗体，则不用逗号。<br /><br />//定义iframe的ID<br />var iframeids=["test"]<br /><br />//如果用户的浏览器不支持iframe是否将iframe隐藏 yes 表示隐藏，no表示不隐藏<br />var iframehide="yes"<br /><br />function dyniframesize() <br />{<br />  var dyniframe=new Array()<br />  for (i=0; i&lt;iframeids.length; i++)<br />  {<br />   if (document.getElementById)<br />   {<br />    //自动调整iframe高度<br />    dyniframe[dyniframe.length] = document.getElementById(iframeids[i]);<br />    if (dyniframe[i] &amp;&amp; !window.opera)<br />    {<br />     dyniframe[i].style.display="block"<br />     if (dyniframe[i].contentDocument &amp;&amp; dyniframe[i].contentDocument.body.offsetHeight) //如果用户的浏览器是NetScape<br />      dyniframe[i].height = dyniframe[i].contentDocument.body.offsetHeight; <br />     else if (dyniframe[i].Document &amp;&amp; dyniframe[i].Document.body.scrollHeight) //如果用户的浏览器是IE<br />      dyniframe[i].height = dyniframe[i].Document.body.scrollHeight;<br />    }<br />   }<br />   //根据设定的参数来处理不支持iframe的浏览器的显示问题<br />   if ((document.all || document.getElementById) &amp;&amp; iframehide=="no")<br />   {<br />    var tempobj=document.all? document.all[iframeids[i]] : document.getElementById(iframeids[i])<br />    tempobj.style.display="block"<br />   }<br />  }<br />}<br /><br />if (window.addEventListener)<br />window.addEventListener("load", dyniframesize, false)<br />else if (window.attachEvent)<br />window.attachEvent("onload", dyniframesize)<br />else<br />window.onload=dyniframesize<br />&lt;/script&gt;<br /><br /><br />使用的时候只要贴在&lt;head&gt;&lt;/head&gt;里面就可以了</font></span>
				</p>
		</div>
<img src ="http://www.blogjava.net/faithwind/aggbug/42773.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-04-24 09:37 <a href="http://www.blogjava.net/faithwind/articles/42773.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JFreeChart在webwork中的应用</title><link>http://www.blogjava.net/faithwind/articles/34302.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Wed, 08 Mar 2006 09:13:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/34302.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/34302.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/34302.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/34302.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/34302.html</trackback:ping><description><![CDATA[前言：<BR>webwork是当今流行的J2EE几大架构之一，在实际的项目中，我们往往要在webwork的输出流中调用一个图表chart，代替直接用response.out输出。那么我们应该怎么样去实现呢？本文将详细阐述JFreeChart在webwork中的应用。本文调试环境为windows2000+tomcat5.0。共分三个部分：<BR>1，JFreeChart 简介 <BR>2，webwork简介及webwork，JFreeChart应用环境的配置<BR>3，JFreeChart在webwork中的应用的具体例子
<P>一： JFreeChart 简介：<BR>JFreeChart是开放源代码项目，它主要用来各种各样的图表，这些图表包括：饼图、柱状图(普通柱状图以及堆栈柱状图)、线图、区域图、分布图、混合图、甘特图以及一些仪表盘等等。<BR>一.JFreeChart创建WEB图表的基本知识<BR>JFreeChart主要是由三个类构成：<BR>org.jfree.chart.servlet.ChartDeleter<BR>org.jfree.chart.servlet.DisplayChart<BR>org.jfree.chart.servlet.ServletUtilities<BR>在下面做一下简单的介绍：<BR>ChartDeleter 继承自HttpSessionBindingListener，用于实现当Session 关闭时，删除<BR>临时目中的图象文件。<BR>ServletUtilities有一系列方法：<BR>saveChartAs*;saveChartAs*是把图表按照不同的形式存储为图象；<BR>sendTempFile方法被重载了很多次，用于把文件流发送response;<BR>DisplayChart继承自Httpservlet 用于处理显示图象；</P>
<P>二：webwork简介及webwork，JFreeChart应用环境的配置<BR>WebWork是一个源代码开放的Web应用框架，用于简化基于Web的应用开发。<BR>WebWork的最大优点是它的简单性和灵活性。WebWork有一个很小的API，它使开发者可以迅速进行开发工作。<BR>WebWork是许多特性和适用性的组合，包括使用variour view技术，<BR>例如JavaServer Pages(JSP),Velocity,Extensible Stylesheet Language Transformations <BR>Specification(XSLT)和JasperReporters。还有，webwork对JFreechart有非常好的支持。<BR>从这里下载webwork-2.0-beta2.zip。<BR>(<A href="https://webwork.dev.java.net/servlets/ProjectDocumentList">https://webwork.dev.java.net/servlets/ProjectDocumentList</A>)<BR>解压缩文件，你可以在webwork-2.0\lib\optional目录下找到jfreechart-0.9.13.jar。<BR>你也可以从这里下载最新的jfreechart-0.9.15.zip。<BR>（<A href="http://www.jfree.org/jfreechart/index.html">http://www.jfree.org/jfreechart/index.html</A>）<BR>本文使用的是jfreechart-0.9.15.zip。<BR>web应用的目录结构图如下所示：<BR>/webapp/WEB-INF <BR>/webapp/WEB-INF/web.xml <BR>/webapp/WEB-INF/webwork.tld <BR>/webapp/WEB-INF/classes <BR>/webapp/WEB-INF/classes/xwork.xml <BR>/webapp/WEB-INF/lib <BR>接下来请务必将下面这些包放在/webapp/WEB-INF/lib 目录下。<BR>1。commons-logging.jar <BR>2。ognl-2.5.1.jar <BR>3。oscore-2.2.1.jar <BR>4。xwork-1.0-beta.jar <BR>5。webwork-2.0-beta.jar <BR>下面是可以选择的包，具体根据项目需要来决定是否添加： <BR>cos-multipart.jar <BR>pell-multipart.jar <BR>velocity-dep-1.3.jar<BR>2.1 配置你的xwork.xml <BR>文件像下面例子一样，注意一定要包含webwork-default.xml。<BR>&lt;!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN" </P>
<P>"<A href="http://www.opensymphony.com/xwork/xwork-1.0.dtd">http://www.opensymphony.com/xwork/xwork-1.0.dtd</A>"&gt;<BR>&lt;xwork&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;include file="webwork-default.xml"/&gt;&nbsp;&nbsp; <BR>&nbsp;&nbsp; &lt;package name="default" extends="webwork-default"&gt;<BR>&nbsp;&nbsp;&nbsp; ...<BR>&nbsp;&nbsp;&nbsp; &lt;/package&gt;<BR>&lt;/xwork&gt;</P>
<P>webwork-default.xml这个文件在webwork-2.0-beta.jar里面。<BR>2.2 修改web.xml文件<BR>修改web.xml文件以包含webwork的设置，如何设置的内容，请察看<BR>$WEBWORK/src/resource/web/WEB-INF/web.xml文件。<BR>2.3 Taglib部分的选择<BR>如果你不打算使用taglib、Velocity、或者XSLT，仅仅只需要在web.xml中不要包含这些内容就可以了。<BR>2.4 Log的设置<BR>Webwork使用log4j，如果你的app server没有安装log4j，你需要增加log4j，复制log4j.jar文件到合适的lib目录。如果你使用tomcat，那么安装目录是$TOMCAT_HOME\lib，当然也可以安装到你的web应用的路径。但需要注意的是如果appserver也适用log4j，你需要小心版本冲突。<BR>2.5 解压缩jfreechart-0.9.15.zip<BR>jfreechart-0.9.15目录下的jfreechart-0.9.15.jar和lib/jcommon-0.9.0.jar,lib/gnujaxp.jar都是开发运行需要的文件，把这三个文件放置到/webapp/WEB-INF/lib目录下。</P>
<P>三 JFreeChart在webwork中的应用的具体例子<BR>在进行上面的步骤后，我们将要进行具体的开发过程。 <BR>步骤：首先在xwork.xml -定义result-types <BR>&nbsp;&nbsp; &lt;result-types&gt;<BR>&nbsp;&nbsp; &lt;result-type name="chart" class="pawpaw.test.ChartResult"/&gt;<BR>&nbsp;&nbsp; &lt;/result-types&gt;</P>
<P>&nbsp;&nbsp; 然后在xwork.xml - 定义action <BR>&lt;action name="viewModerationChart" class="pawpaw.test.ViewModerationChartAction"&gt; <BR>&nbsp; &lt;result name="success" type="chart"&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;param name="width"&gt;400&lt;/param&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;param name="height"&gt;300&lt;/param&gt; &lt;/result&gt;<BR>&lt;/action&gt;</P>
<P>其中param里面定义的是chart图表的长宽。<BR>然后在web.xml文件中增加以下内容：<BR>&nbsp;&nbsp;&nbsp; &lt;servlet&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;servlet-name&gt;DisplayChart&lt;/servlet-name&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;servlet-class&gt;org.jfree.chart.servlet.DisplayChart&lt;/servlet-class&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/servlet&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;servlet-mapping&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;servlet-name&gt;DisplayChart&lt;/servlet-name&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;url-pattern&gt;/servlet/DisplayChart&lt;/url-pattern&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/servlet-mapping&gt;</P>
<P>部分源代码如下：<BR>package pawpaw.test;</P>
<P>import com.opensymphony.webwork.ServletActionContext;<BR>import com.opensymphony.xwork.ActionInvocation;<BR>import com.opensymphony.xwork.Result;<BR>import org.jfree.chart.ChartUtilities;<BR>import org.jfree.chart.JFreeChart;<BR>import java.io.OutputStream;<BR>import javax.servlet.http.HttpServletResponse;<BR>/*<BR>&nbsp;* &lt;p&gt;Description: 把chart文件流换成是通过HttpServletResponse<BR>&nbsp;*&nbsp;&nbsp;&nbsp; 对象获取到的输出流在浏览器中输出&lt;/p&gt;<BR>&nbsp;* author: pawpaw<BR>&nbsp;* @version 1.0&nbsp; 12/15/2003<BR>&nbsp;*/<BR>public class ChartResult implements Result {<BR>&nbsp;&nbsp;&nbsp; JFreeChart chart;<BR>&nbsp;&nbsp;&nbsp; boolean chartSet = false;<BR>&nbsp;&nbsp;&nbsp; private int height;<BR>&nbsp;&nbsp;&nbsp; private int width;</P>
<P>&nbsp;&nbsp;&nbsp; public void setChart(JFreeChart chart) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.chart = chart;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; chartSet = true;<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; //设置图片的长度<BR>&nbsp;&nbsp;&nbsp; public void setHeight(int height) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.height = height;<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; //设置图片的宽度<BR>&nbsp;&nbsp;&nbsp; public void setWidth(int width) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.width = width;<BR>&nbsp;&nbsp;&nbsp; }</P>
<P>&nbsp;&nbsp;&nbsp; public void execute(ActionInvocation invocation) throws Exception {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JFreeChart chart = null;</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (chartSet) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; chart = this.chart;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; chart = (JFreeChart) invocation.getStack().findValue("chart");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (chart == null) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new NullPointerException("No chart found");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //把文件流换成是通过HttpServletResponse对象获取到的输出流<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HttpServletResponse response = ServletActionContext.getResponse();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OutputStream os = response.getOutputStream();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ChartUtilities.writeChartAsPNG(os, chart, width, height);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.flush();<BR>&nbsp;&nbsp;&nbsp; }<BR>}</P>
<P><BR>创建JFreeChart的action类。<BR>package pawpaw.test;</P>
<P>import java.awt.Insets;<BR>import java.awt.Font;<BR>import java.io.PrintWriter;<BR>import javax.servlet.http.HttpSession;<BR>import org.jfree.data.*;<BR>import org.jfree.chart.*;<BR>import org.jfree.chart.plot.*;<BR>import org.jfree.chart.entity.*;<BR>import org.jfree.chart.urls.*;<BR>import org.jfree.chart.servlet.*;<BR>import org.jfree.chart.labels.StandardPieToolTipGenerator;<BR>import org.jfree.util.Rotation;<BR>import com.opensymphony.xwork.ActionSupport;<BR>/*<BR>&nbsp;*&lt;p&gt;Description: 输出一条斜线chart&lt;/p&gt;<BR>&nbsp;* author: pawpaw<BR>&nbsp;* @version 1.0&nbsp; 12/15/2003<BR>&nbsp;*/<BR>public class ViewModerationChartAction extends ActionSupport {&nbsp;&nbsp; <BR>&nbsp;&nbsp; private JFreeChart chart;<BR>&nbsp;&nbsp; public String execute() throws Exception {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 创建chart文件数据集<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; XYSeries dataSeries = new XYSeries(null);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt;= 100; i++) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dataSeries.add(i, RandomUtils.nextInt());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; XYSeriesCollection xyDataset = new XYSeriesCollection(dataSeries);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ValueAxis xAxis = new NumberAxis("Raw Marks");//x轴坐标<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ValueAxis yAxis = new NumberAxis("Moderated Marks");//y轴坐标&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 设置chart的样式<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; chart =new JFreeChart(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Moderation Function",// 图表标题<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JFreeChart.DEFAULT_TITLE_FONT,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new XYPlot(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; xyDataset,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; xAxis,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; yAxis,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new&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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; StandardXYItemRenderer(StandardXYItemRenderer.LINES)),// 数据集<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; false&nbsp;&nbsp;&nbsp; //是否生成URL链接<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; chart.setBackgroundPaint(java.awt.Color.white);&nbsp; //设置图片的背景色<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Font font = new Font("黑体",Font.CENTER_BASELINE,20);//设置图片标题的字体和大小<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TextTitle _title = new TextTitle(title);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _title.setFont(font);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; chart.setTitle(_title);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return super.SUCCESS;<BR>&nbsp;&nbsp; }&nbsp;&nbsp; <BR>&nbsp;&nbsp; public JFreeChart getChart() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return chart;<BR>&nbsp;&nbsp; }<BR>}</P>
<P>小结：这只是一个简单的例子，如果你想深入了解的话，可以参考webwork文档和jfreechart网站。<BR>参考资料：<BR><A href="http://www.jfree.org/jfreechart/index.html">http://www.jfree.org/jfreechart/index.html</A><BR>webwork英文文档 </P>
<P>&nbsp;</P><img src ="http://www.blogjava.net/faithwind/aggbug/34302.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-03-08 17:13 <a href="http://www.blogjava.net/faithwind/articles/34302.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java Web Framework综述</title><link>http://www.blogjava.net/faithwind/articles/33235.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Thu, 02 Mar 2006 08:09:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/33235.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/33235.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/33235.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/33235.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/33235.html</trackback:ping><description><![CDATA[<STRONG>0.简介<BR></STRONG>本文介绍Java Web Framework的基本工作原理，和一些常用的开源Web MVC Framework(Struts, Web Work, Tapestry, Echo, JSF, Maverick, Spring MVC, Turbine, Cocoon, Barracuda)。
<DIV>Web开发的最重要的基本功是HTTP；Java Web开发的最重要的基本功是Servlet Specification。HTTP和Servlet Specification对于Web Server和Web Framework的开发实现来说，是至关重要的协议规范。</DIV>
<DIV>应用和剖析开源Web Framework，既有助于深入掌握HTTP &amp; Servlet Specification, 也有助于了解一些现代的B/S Web框架设计思想，如MVC，事件处理机制，页面组件，IoC，AOP等。在这个现代化的大潮中，即使Servlet规范本身也不能免俗，不断引入Filter、Listener等现代框架设计模式。同是Sun公司出品的JSF更是如此。</DIV>
<DIV>关于MVC模型、项目简介、配置文件、入门示例等基础知识，网上已经有大量的重复资料信息，本文不再赘述。</DIV>
<DIV>文中会提到一些相关的开源项目，和一些编程思想，如有需要，可以用相关的关键字在网上搜索，获取基本的背景知识。</DIV>
<DIV>本文力图言简意赅，突出重点。着重描述其他资料没有提到、或很少提到的较重要内容，如运行原理、主流用法，相关知识，关键特性等。</DIV>
<DIV><STRONG>1. Java Web程序工作原理</STRONG><BR>Tomcat的Server.xml文件中定义了网络请求路径到主机本地文件路径的映射。比如，&lt;context path="/yourapp" docBase="yourapp_dir/webapp"/&gt;</DIV>
<DIV>&nbsp;</DIV>
<DIV>我们来看一下，一个HTTP Request-Response Cycle的处理过程。</DIV>
<DIV>HTTP Request URL一般分为三段：host, context, path。</DIV>
<DIV>如<A href="http://yourhost/yourapp/en/index.html">http://yourhost/yourapp/en/index.html</A>这个URL，分为host=yourhost, context=yourapp, path=en/index.html三段。其中，Context部分由request.getContext()获得，path部分由request.getServletPath()获得（返回结果是“/en/index.html”）。</DIV>
<DIV>yourhost主机上运行的Tomcat Web Server接收到这个URL，根据Context定义，把yourapp这个网络路径映射为yourapp_dir/webapp，并在此目录下定位en/index.html这个文件，返回到客户端。</DIV>
<DIV>&nbsp;</DIV>
<DIV>如果我们这个URL更换为<A href="http://yourhost/yourapp/en/index.jsp">http://yourhost/yourapp/en/index.jsp</A>，这个时候Tomcat会试图把yourapp_dir/webapp/en/index.jsp文件编译成Servlet，并调用运行这个Servlet。</DIV>
<DIV>我们再把这个URL更换为<A href="http://yourhost/yourapp/en/index.do">http://yourhost/yourapp/en/index.do</A>。</DIV>
<DIV>注意，戏剧化的事情就发生在这个时候，Servlet规范中最重要的类RequestDispatcher登场了。RequestDispatcher根据WEB-INF/web.xml配置文件的定义，调用对应的Servlet来处理en/index.do这个路径。</DIV>
<DIV>假设web.xml里面有这样的定义。</DIV>
<DIV>&nbsp; &lt;servlet&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;servlet-name&gt;DispatchServlet&lt;/servlet-name&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;servlet-class&gt;yourapp.DispatchServlet&lt;/servlet-class&gt;</DIV>
<DIV>&nbsp; &lt;/servlet&gt;</DIV>
<DIV>&nbsp; &lt;servlet-mapping&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;servlet-name&gt;DispatchServlet&lt;/servlet-name&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;url-pattern&gt;*.do&lt;/url-pattern&gt;</DIV>
<DIV>&nbsp; &lt;/servlet-mapping&gt;</DIV>
<DIV>那么，RequestDispatcher会调用yourapp.DispatchServlet类处理这个路径。</DIV>
<DIV>如果web.xml没有定义对应en/index.do这个路径的Servlet，那么Tomcat返回“您请求的资源不存在”。</DIV>
<DIV>RequestDispatcher用于Web Server中，也可以用于应用程序中进行处理转向，资源定位。比如，我们在处理en/index.do的代码中调用，</DIV>
<DIV>request.getRequestDispatcher(“cn/index.jsp”).forward(request, response), 就可以转交另外的资源cn/index.jsp来处理。</DIV>
<DIV>&nbsp;</DIV>
<DIV>几乎所有的Web Framework都需要定义自己的Dispatch作用的Servlet，并调用RequestDispatcher进行转向处理。</DIV>
<DIV>阅读Web Framework源代码，有两条主要线索，(1)根据web.xml找到对应的Servlet类；(2)搜索包含“RequestDispatcher”词的代码文件。</DIV>
<DIV>&nbsp;</DIV>
<DIV>我们看到，request, response&nbsp; 这两个参数，被RequestDispatcher在各种Servlet之间传来传去（JSP也是Servlet）。所以，request的setAttribute()和getAttribute()方法是Servlet之间传送数据的主要方式。</DIV>
<DIV>在MVC结构中，一般的处理流程如下：</DIV>
<DIV>处理HTTP Request的基本单位一般称为Action，是一个比Servlet轻量得多的接口定义，通常只有一两个方法，如execute(perform), validate等。</DIV>
<DIV>我们知道，URL-&gt;Servlet映射，定义在Web.xml配置文件里，但MVC框架通常会有另外一个定义URL-&gt; Action映射的配置文件。</DIV>
<DIV>入口Dispatcher Servlet根据URL -&gt; Action的映射关系，把请求转发给Action。</DIV>
<DIV>Action获得输入参数，调用商业逻辑，并把结果数据和View标识给（Model &amp; View）返回给Dispatcher Servlet。</DIV>
<DIV>Dispatcher Servlet根据这个View 标识，定位相应的View Template Path，把处理转交给View（JSP +TagLib, Velocity, Free Marker, XSL等）。</DIV>
<DIV>View一般通过request.getAttribute()获得结果数据，并显示到客户端。至于是谁把结果数据设置到request.attribute里面，有两种可能：Action或Dispatcher Servlet。</DIV>
<DIV><STRONG>2. Struts<BR></STRONG><A href="http://struts.apache.org/">http://struts.apache.org/</A></DIV>
<DIV>Struts是目前用户群最大、开发厂商支持最多的开源Web Framework。</DIV>
<DIV>Struts劳苦功高，为普及MVC框架作出了不可磨灭的贡献。显赫的声望，趋于老化的厚重结构，令Struts成为很多现代Web Framework参照、挑战的目标。</DIV>
<DIV>&nbsp;</DIV>
<DIV>Struts应用主要包括3件事情: 配置struts-config.xml文件,实现Action类，实现View；还有一些高级扩展用法。下面分别讲述。</DIV>
<DIV>&nbsp;</DIV>
<DIV>1. 配置struts-config.xml文件：</DIV>
<DIV>Struts支持多级配置文件，具体用法和限制，详见Struts文档。这里只讨论struts-config.xml主流配置的内容。:-)</DIV>
<DIV>&nbsp;</DIV>
<DIV>(1) URL Path到Action的映射。</DIV>
<DIV>如&lt;action path="/LogonSubmit" type="app.LogonAction" ... /&gt;</DIV>
<DIV>&nbsp;</DIV>
<DIV>Struts的入口Servlet是ActionServlet。</DIV>
<DIV>ActionServlet需要此信息把URL Path调用对应的Action类处理。</DIV>
<DIV>在Struts运行期间，一个URL Path，只存在一个对应的Struts Action实例。所有的该URL Path的请求，都经过这同一个Struts Action实例处理。所以Struts Action必须线程安全。</DIV>
<DIV>想想看，其实这个要求并不过分，Action只是一个处理程序，不应该保存跨HTTP请求的状态数据，按理来说，也应该做成线程安全的。</DIV>
<DIV>&nbsp;</DIV>
<DIV>(2) Template Name到View Template Path的映射。</DIV>
<DIV>&lt;forward name="success" path="/pages/Welcome.jsp"/&gt;</DIV>
<DIV>&nbsp;</DIV>
<DIV>Action类返回一个Template Name，ActionServlet根据这个Template Name获得对应的View Template Path，然后调用</DIV>
<DIV>request.getRequestDispatcher(“View Template Path”)，把处理转向路径对应的Servlet。在这个例子中，是转向/pages/Welcome.jsp编译后的Servlet。</DIV>
<DIV>&nbsp;</DIV>
<DIV>我们来看一个一个Velocity的例子。</DIV>
<DIV>&lt;include name="success" path="/pages/Welcome.vm"/&gt;</DIV>
<DIV>web.xml的定义如下</DIV>
<DIV>&lt;servlet&gt;</DIV>
<DIV>&nbsp; &lt;servlet-name&gt;velocity&lt;/servlet-name&gt;</DIV>
<DIV>&lt;servlet-class&gt;org.apache.velocity.tools.view.servlet.VelocityViewServlet&lt;/servlet-class&gt;</DIV>
<DIV>&lt;/servlet&gt;</DIV>
<DIV>&lt;servlet-mapping&gt;</DIV>
<DIV>&nbsp; &lt;servlet-name&gt;velocity&lt;/servlet-name&gt;</DIV>
<DIV>&nbsp; &lt;url-pattern&gt;*.vm&lt;/url-pattern&gt;</DIV>
<DIV>&lt;/servlet-mapping&gt;</DIV>
<DIV>&nbsp;</DIV>
<DIV>这时，request.getRequestDispatcher(“/pages/Welcome.vm”)会调用VelocityViewServlet，由VelocityViewServlet负责装并驱动运行/pages/Welcome.vm这个模板文件。</DIV>
<DIV>这里面有一个问题，如果调用的是DispatchRequester.include()方法，那么如何才能把pages/Welcome.vm传给VelocityViewServlet呢？</DIV>
<DIV>如前所说，RequestDispatcher传递的参数只有两个，request和response。那么只能通过request attribute。正是为了解决这个问题，Servlet2.3规范之后，加入了javax.servlet.include.servlet_path这个属性。</DIV>
<DIV>参见VelocityViewServlet的代码（velocity-tool开源项目）</DIV>
<DIV>// If we get here from RequestDispatcher.include(), getServletPath()</DIV>
<DIV>// will return the original (wrong) URI requested.&nbsp; The following special</DIV>
<DIV>// attribute holds the correct path.&nbsp; See section 8.3 of the Servlet</DIV>
<DIV>// 2.3 specification.</DIV>
<DIV>String path = (String)request.getAttribute("javax.servlet.include.servlet_path");</DIV>
<DIV>&nbsp;</DIV>
<DIV>从这里我们可以看出，为什么通晓Servlet Specification对于通晓Web Framework至关重要。</DIV>
<DIV>&nbsp;</DIV>
<DIV>(3) Form Bean的定义</DIV>
<DIV>如&lt;form-bean name="logonForm" type="app.LogonForm"/&gt;<BR>Struts Form Bean需要继承ActionForm类。<BR>Form Bean类，主要有三个作用：</DIV>
<DIV>[1]根据bean的定义，利用reflection机制，自动把request参数转化为需要的数据类型，填入到bean的属性当中。ActionForm类名中虽然有Form这个词，但不仅能够获取Form提交后的HTTP Post参数，也可以获取URL后缀的HTTP Get参数。</DIV>
<DIV>[2]输入验证。用户可以配置validation.xml，定义各属性的验证规则。</DIV>
<DIV>[3]当作View Object来用。用户需要熟练掌握Struts HTML TagLib的用法，才能把Form Bean的属性正确显示出来。</DIV>
<DIV>&nbsp;</DIV>
<DIV>(4)其他定义。详见Struts文档。不再赘述。</DIV>
<DIV>&nbsp;</DIV>
<DIV>2.实现Action。</DIV>
<DIV>Action类从Form Bean或直接从request中获得输入参数，调用商业逻辑，把结果数据（也许会包装成View Object），用request.setAttribute()放到request中，最后返回一个用ForwardMapping类包装的Template Name。</DIV>
<DIV>&nbsp;</DIV>
<DIV>3.实现View。</DIV>
<DIV>Struts View的标准实现方法是JSP + Struts TagLib，其中最重要的就是Struts HTML TagLib。</DIV>
<DIV>html:form tag则是整个HTML Tag的核心，其它的如html:input, html:select等tag，都包含在html:form tag里面。</DIV>
<DIV>html:form tag用来映射Form Bean（也可以通过适当定义，映射其他的bean，但使用上会有很多麻烦）。html:form tag包含的其他Struts html tag用来映射Form Bean的属性。</DIV>
<DIV>&nbsp;</DIV>
<DIV>Struts Bean TagLib的用法比较臃肿，一般情况下可以用JSTL代替。当然，如果需要用到bean:message tag实现国际化，那又另当别论。</DIV>
<DIV>Struts Tile TagLib用于页面布局。开源Portal项目Liferay使用了Struts Tile TagLib做为布局控制。</DIV>
<DIV>&nbsp;</DIV>
<DIV>4.高级扩展用法</DIV>
<DIV>用户可以重载Struts的一些控制类，引入自己的一些定制类。详见Struts文档。</DIV>
<DIV>本文不是Struts专题，只讲述最重要的主流用法，其它边边角角的，不再赘述。</DIV>
<DIV><STRONG>3. WebWork<BR></STRONG><A href="http://www.opensymphony.com/webwork/">http://www.opensymphony.com/webwork/</A></DIV>
<DIV>WebWork由于灵活的可插拔特性，受到很多资深程序员的欢迎。似乎很有可能大肆流行起来。</DIV>
<DIV>WebWork项目建立在XWork项目上。入口Servlet是WebWork项目中定义的ServletDispatcher，而Action在XWork项目中定义。</DIV>
<DIV>XWork Action接口的execute()方法没有参数，不像Struts Action那样接受request, response参数，所以XWork Action能够脱离Web环境被直接调用，便于单元测试。</DIV>
<DIV>这里引入了一个问题。没有了request参数，那么XWork Action如何获得request parameters作为输入数据？又通过什么桥梁（Struts用request.setAttribute）把结果数据传送到View层？</DIV>
<DIV>在Web Work中，只能通过Action本身的getter, setter属性来传送输入参数和输出结果。</DIV>
<DIV>比如，我们有这样一个实现了XWork Action接口的类，</DIV>
<DIV>YourAction implements Action{</DIV>
<DIV>&nbsp; int productId = null;</DIV>
<DIV>&nbsp; String productName = null;</DIV>
<DIV>&nbsp;</DIV>
<DIV>&nbsp; public void setProductId(int productId){this.productId = productId;}</DIV>
<DIV>&nbsp; public String getProductName(){return productName;}</DIV>
<DIV>&nbsp;</DIV>
<DIV>&nbsp; public String execute(){</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; productName = findNameById(productId);</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return “success”;</DIV>
<DIV>&nbsp; }</DIV>
<DIV>}</DIV>
<DIV>这个类里面的productId将接受request输入参数，productName是输出到页面显示的结果。</DIV>
<DIV>比如，这样的请求，<A href="http://yourhost/yourapp/MyAction.action?productId=1">http://yourhost/yourapp/MyAction.action?productId=1</A> </DIV>
<DIV>Web Work会把1填到YourAction的productId里面，然后执行execute()方法，JSP里的语句&lt;ww:property value=“productName”&gt;会把YourAction的productName显示在页面上。</DIV>
<DIV>&nbsp;</DIV>
<DIV>如果一个Web Framework采用了这种屏蔽Action的request, response参数的设计方式，一般也同时会采用这种Action和输入输出数据结合成一体的解决方式。类似的情形也存在于Tapestry和Maverick中，后面会讲到。</DIV>
<DIV>当WebWork ServletDispatcher接收到HTTP Request的时候，首先把所有相关的信息（包括request, response, session, servlet config, servelt context, 所有request参数）等存放到AcationContext中，然后根据Interceptor配置信息，生成一个YourAction的动态代理类对象。实际上运行的正是这个代理对象，如同Servlet Filter的工作机制一般，所有注入的Interceptor方法会先于Actio方法运行。</DIV>
<DIV>我们来看一下Action和Interceptor的地位：Action没有参数，无法获得ActionContext；而Interceptor接受的ActionInvoication参数拥有包括ActionContext在内的所有重要信息。</DIV>
<DIV>这种权力分配的不平等，注定了Action的作用非常有限，只限于调用商业逻辑，然后返回一个成功与否标志。所有与外部Web世界打交道、协调内部工作流程的重担，都责无旁贷地落在Interceptor的肩上。</DIV>
<DIV>我们可以设想一个极端的例子。我们声明一批不做任何事情的空Action，我们只是需要它们的空壳类名；我们制作一批对应的Interceptor，所有的转发控制、商业逻辑都在Interceptor上实现，然后把Interceptor都注入到对应的空Action。这在理论上是完全可行的。</DIV>
<DIV>在Web海洋的包围中，Action可少，Interceptor不可少。Action是一个孤岛,如果没有外来盟友Interceptor的协助，只能在自己的小范围内独立作战（比如Unit Test），而对整体大局的作战目标无法产生影响。</DIV>
<DIV>下面我们来看一下Action是如何在Interceptor的全程监管下工作的。</DIV>
<DIV>&nbsp;</DIV>
<DIV>在WebWork中，我们需要如下配置XWork.xml。</DIV>
<DIV>&lt;xwork&gt;</DIV>
<DIV>&lt;!-- Include webwork defaults (from WebWork-2.1 JAR). --&gt;</DIV>
<DIV>&lt;include file="webwork-default.xml" /&gt;</DIV>
<DIV>&nbsp;</DIV>
<DIV>&lt;!-- Configuration for the default package. --&gt;</DIV>
<DIV>&lt;package name="default" extends="webwork-default"&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;!-- Default interceptor stack. --&gt; </DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;default-interceptor-ref name=" defaultStack" /&gt; </DIV>
<DIV>&nbsp;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;!-- Action: YourAction. --&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;action name="youraction" class="yourapp.YourAction"&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;result name="success" type="dispatcher"&gt;</DIV>
<DIV>YourAction.jsp</DIV>
<DIV>&lt;/result&gt;</DIV>
<DIV>&lt;/action&gt;</DIV>
<DIV>&lt;/package&gt;</DIV>
<DIV>&lt;/xwork&gt;</DIV>
<DIV>&nbsp;</DIV>
<DIV>webwork-default.xml里面的相关定义如下：</DIV>
<DIV>&lt;interceptors&gt;</DIV>
<DIV>&lt;interceptor name="validation" class="com.opensymphony.xwork.validator.ValidationInterceptor"/&gt;</DIV>
<DIV>&nbsp;</DIV>
<DIV>&lt;interceptor name="static-params" class="com.opensymphony.xwork.interceptor.</DIV>
<DIV><BR>StaticParametersInterceptor"/&gt;</DIV>
<DIV>&lt;interceptor name="params" class="com.opensymphony.xwork.interceptor.ParametersInterceptor</DIV>
<DIV>"/&gt;</DIV>
<DIV>&lt;interceptor name="conversionError" class="com.opensymphony.webwork.interceptor.</DIV>
<DIV><BR>WebWorkConversionErrorInterceptor"/&gt;</DIV>
<DIV>&lt;interceptor-stack name="defaultStack"&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;interceptor-ref name="static-params"/&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;interceptor-ref name="params"/&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;interceptor-ref name="conversionError"/&gt;</DIV>
<DIV>&lt;/interceptor-stack&gt;</DIV>
<DIV>&lt;/interceptors&gt;</DIV>
<DIV>&nbsp;</DIV>
<DIV>从上述的配置信息中可以看出，YourAction执行execute()方法的前后，会被</DIV>
<DIV>defaultStack所定义的三个Intercepter截获。这些Interceptor的任务之一就是把输入参数设置到Action的对应属性当中。</DIV>
<DIV>如果我们需要加入对YourAction的属性的验证功能，只要把上述定义中的validation Interceptor加入到defaultStack中就可以了。当然，实际工作还没有这么简单，一般来说，还要为每个进行属性验证的Action的都配置一份validation.xml。</DIV>
<DIV>XWork Interceptor能够在Package和Action级别上，进行截获处理。</DIV>
<DIV>Servlet Filter能够在URL Patten级别上，进行截获处理。虽然实际上，Servlet Filter截获的是Servlet，但某些情况下，可以达到和截获一批Action的同样效果。</DIV>
<DIV>比如，在Web Work中，我们可以为所有admin package的Action，加入一个Interceptor，当检查到当前Session的用户没有admin权限时，统一返回一个警告页面：您没有足够的权限执行这个操作。</DIV>
<DIV>我们看到也可以为所有URL Pattern为“admin/*.action”的URL定义一个Servlet Filter，当检查到当前Session的用户没有admin权限时，统一返回一个警告页面：您没有足够的权限执行这个操作。</DIV>
<DIV>&nbsp;</DIV>
<DIV>WebWork的Interceptor配置是相当灵活的，相当于对Action实现了AOP。Interceptor相当于Aspect，基类AroundInterceptor的before(), after()方法相当于Advice。</DIV>
<DIV>另外，XWork也提供了从XML配置文件装配Component的机制，相当于实现了对于Component的IoC。</DIV>
<DIV>提到AOP和IoC，顺便多讲两句。Spring AOP能够截获所有Interface，不限于某个特定接口；Spring框架支持所有类型的IoC，不限于某种特定类型。</DIV>
<DIV>&nbsp;</DIV>
<DIV>要知道，AOP, IoC可是现在最时髦的东西，一定不要错过啊。:D</DIV>
<DIV>相关概念导读（如果需要，请用如下关键字搜索网络）：</DIV>
<DIV>AOP -- Aspect Oriented Programming -- 面向方面编程。</DIV>
<DIV>IoC – Inversion of Control --控制反转</DIV>
<DIV>Dynamic Proxy -- 动态代理，JDK1.4引入的特性。还可以进一步参考CGLib, ASM等开源项目。</DIV>
<DIV>&nbsp;</DIV>
<DIV>WebWork直接支持所有主流View -- XSL,Velocity, FreeMarker,JSP。WebWork还提供了自己的TagLib。“直接支持”的意思是说，不用像Struts那样，使用Velocity的时候，还需要引入辅助桥梁Velocity-tool。</DIV>
<DIV>WebWork中用到一种功能和XPath类似的对象寻径语言ONGL，是一个开源项目。ONGL同样用在下面要介绍的Tapestry项目中。</DIV>
<DIV>Opensymphony下还有一个SiteMesh项目，通过Servlet Filter机制控制布局。可以和WebWork组合使用。</DIV>
<DIV>&nbsp;</DIV>
<DIV><STRONG>4. Tapestry<BR></STRONG><A href="http://jakarta.apache.org/tapestry/">http://jakarta.apache.org/tapestry/</A></DIV>
<DIV>Tapestry近来突然火了起来，令我感到吃惊。也许是JSF带来的Page Component风潮令人们开始关注和追逐Tapestry。</DIV>
<DIV>Tapestry的重要思想之一就是Page Component。</DIV>
<DIV>前面讲到，XWork能够自动把request参数映射到Action的属性当中。Tapestry走得更远，甚至能够根据request参数，映射到Action（Tapestry里面称为Page）的方法，并把request参数映射为Page方法需要的参数，进行正确的调用。就这样，Tapestry不仅把输入输出数据，而且把事件方法也绑定到了Page上面。</DIV>
<DIV>在Tapestry框架中，Action的概念已经非常模糊，而换成了Page的概念。而Tapestry Page是拥有属性和事件的页面组件，其中的事件处理部相当于Action的职责，而属性部分起着Model的作用。</DIV>
<DIV>除了使用Page和其它的Tapestry页面组件，用户也可以自定义页面组件。</DIV>
<DIV>&nbsp;</DIV>
<DIV>这种页面组件/属性事件的编程模型，受到一些程序员的欢迎。当然，这种编程模型并不是没有代价的，每个Tapestry模板文件都需要一个对应的.page文件。这些.page文件定义了页面组件的属性、事件、Validator等信息。</DIV>
<DIV>&nbsp;</DIV>
<DIV>我们来看一下B/S结构中，组件的属性、事件和HTTP Request绑定的基本原理。一个能够发出请求的页面组件（比如Link和Button），在输出自己的HTML的时候，需要输出一些特殊的信息来标志本组件的属性/事件，这样下次HTTP Request来的时候，会把这些信息带回来，以便Web Framework加以辨认识别，发给正确的Page Component处理。</DIV>
<DIV>这些特殊信息通常包含在URL参数或Hidden Input里面，必要的时候，还需要生成一些Java Script。Tapestry，Echo，JSF都是这种原理。</DIV>
<DIV>Tapestry的例子如下：</DIV>
<DIV>&lt;a href="#" jwcid="@DirectLink" parameters="ognl:currentItem.itemId" listener="ognl:listeners.showItem"&gt;</DIV>
<DIV>JSF用TagLib实现页面组件，也提供了类似的CommandLink和CommandButton Tag。其中对应Tapestry listener的Tag属性是action。后面会讲解。</DIV>
<DIV>&nbsp;</DIV>
<DIV>Tapestry的模板标签是HTML标签的扩展，具有良好的“所见即所得”特性，能够直接在浏览器中正确显示，这也是Tapestry的一个亮点。</DIV>
<DIV><STRONG>5. Echo<BR></STRONG><A href="http://sourceforge.net/projects/echo">http://sourceforge.net/projects/echo</A></DIV>
<DIV>Echo提供了一套类似Swing的页面组件，直接生成HTML。</DIV>
<DIV>从程序员的角度看来，用Echo编写Web程序，和用Swing编写Applet一样，属于纯面向组件事件编程，编程模型也以Event/Listener结构为主体。</DIV>
<DIV>Echo没有Dispatcher Servlet，也没有定义URL-&gt;Action映射的配置文件。</DIV>
<DIV>Echo的Action就是实现了ActionListener接口（参数为ActionEvent）的Servlet（继承EchoServer类）。</DIV>
<DIV>所以，Echo直接由Web Server根据web.xml配置的URL -&gt; Servlet的映射，进行转发控制。</DIV>
<DIV>&nbsp;</DIV>
<DIV>Echo也没有明显的View层，Echo在页面组件方面走得更远，所有的HTML和JavaScript都由框架生成。你不必（也没有办法）写HTML，只需要（也只能）在Java代码中按照类似Swing编程方式，生成或操作用户界面。用户也可以定制自己的Echo组件。</DIV>
<DIV>Echo的UI Component的实现，采用了两个重要的模式。一个是Peer（Component -&gt; ComponentPeer）模式，一个是UI Component -&gt; Renderer模式。</DIV>
<DIV>虽然Echo的API更类似于Swing，但实现上却采用更接近于AWT的Peer模式。每个Component类（代表抽象的组件，比如Button），都有一个对应的ComponentPeer类（代表实际的组件，比如windows桌面的Button，Linux桌面的Button，HTML Button等）。</DIV>
<DIV>先别急，这个事情还没有完。虽然ComponentPeer落实到了具体的界面控件，但是它还是舍不得显示自己，进一步把显示工作交给一个Renderer来执行。</DIV>
<DIV>比如，在Echo里面，Button类对应一个ButtonUI（继承了ComponentPeer）类，而这个ButtonUI类会把最终显示交给ButtonRender来处理。</DIV>
<DIV>据说多了这么一步，能够让显示控制更加灵活丰富。比如，同一个Renderer可以处理不同的UI Component，同一个UI Component也可以交给不同的Renderer处理。</DIV>
<DIV>JSF的页面组件也采用了UI Component -&gt; Renderer模式，后面会讲到。</DIV>
<DIV><STRONG>6. JSF<BR></STRONG><A href="http://java.sun.com/j2ee/javaserverfaces/index.jsp">http://java.sun.com/j2ee/javaserverfaces/index.jsp</A></DIV>
<DIV><A href="http://wwws.sun.com/software/communitysource/jsf/download.html">http://wwws.sun.com/software/communitysource/jsf/download.html</A> download source</DIV>
<DIV>&nbsp;</DIV>
<DIV>JSF的中心思想也是页面组件/属性事件。一般来说，JSF的页面组件是一个三件套{ UI Component, Tag, Renderer}。</DIV>
<DIV>UI Component有可能对应Model，Event，Listener。Tag包含componentType和rendererType两个属性，用来选择对应的的UI Component和Renderer。</DIV>
<DIV>JSF的应用核心无疑是JSF TagLib。JSF TagLib包含了对应所有重要HTML元素的Tag，而且Input Tag可以直接包含Validator Tag或者Validator属性，来定义验证手段。</DIV>
<DIV>&nbsp;</DIV>
<DIV>我们通过JSF携带的cardemo例子，来看JSF的处理流程。</DIV>
<DIV>(1) carDetail.jsp有如下内容：</DIV>
<DIV>&lt;h:commandButton action="#{carstore.buyCurrentCar}" value="#{bundle.buy}" /&gt;</DIV>
<DIV>可以看到，这个button的submit action和carstore.buyCurrentCar方法绑定在一起。我们在Tapestry里面曾经看到过类似的情景。</DIV>
<DIV>&nbsp;</DIV>
<DIV>(2) carstore在faces-config.cml中定义：</DIV>
<DIV>&nbsp; &lt;managed-bean&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;managed-bean-name&gt; carstore &lt;/managed-bean-name&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;managed-bean-class&gt; carstore.CarStore &lt;/managed-bean-class&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;managed-bean-scope&gt; session &lt;/managed-bean-scope&gt;</DIV>
<DIV>&nbsp; &lt;/managed-bean&gt;</DIV>
<DIV>&nbsp;</DIV>
<DIV>(3) carstore.CarStore类中的buyCurrentCar方法如下：</DIV>
<DIV>&nbsp;&nbsp;&nbsp; public String buyCurrentCar() {</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getCurrentModel().getCurrentPrice();</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return "confirmChoices";</DIV>
<DIV>&nbsp;&nbsp;&nbsp; }</DIV>
<DIV>&nbsp;</DIV>
<DIV>(4) confirmChoices转向在faces-config.cml中定义：</DIV>
<DIV>&nbsp; &lt;navigation-rule&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;from-view-id&gt;/carDetail.jsp&lt;/from-view-id&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;navigation-case&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;description&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Any action that returns "confirmChoices" on carDetail.jsp should</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cause navigation to confirmChoices.jsp</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/description&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;from-outcome&gt;confirmChoices&lt;/from-outcome&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;to-view-id&gt;/confirmChoices.jsp&lt;/to-view-id&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;/navigation-case&gt;</DIV>
<DIV>&nbsp; &lt;/navigation-rule&gt;</DIV>
<DIV>&nbsp;</DIV>
<DIV>(5)于是转到页面confirmChoices.jsp。</DIV>
<DIV>&nbsp;</DIV>
<DIV>除了Interceptor之外，JSF几乎包含了现代Web Framework应该具备的所有特性：页面组件，属性事件，IoC (ManagedBean)，Component -&gt; Renderer，类似于Swing Component的Model-Event-Listener。</DIV>
<DIV>也许设计者认为，众多庞杂的模式能够保证JSF成为一个成功的框架。Portal开源项目eXo就是建立在JSF框架上。</DIV>
<DIV>&nbsp;</DIV>
<DIV>可以看出这样一个趋势，现代Web Framework认为B/S结构的无状态特性和HTML界面是对编程来说是需要极力掩盖的一个缺陷，所以尽量模拟C/S结构的组件和事件机制，以吸引更多的程序员。</DIV>
<DIV><STRONG>7. Maverick<BR></STRONG><A href="http://mav.sourceforge.net/">http://mav.sourceforge.net/</A></DIV>
<DIV>Maverick是一个轻量而完备的MVC Model 2框架。Maverick的Action不叫Action，直截了当的称作Controller。</DIV>
<DIV>Controller只接受一个ControllerContext参数。request，response, servlet config, servelt context等输入信息都包装在ControllerContext里面，而且Model也通过ControllerContext的model属性返回。整个编程结构清晰而明快，令人赞赏。</DIV>
<DIV>但这个世界上难有十全十美的事情，由于ControllerContext只有一个model属性可以传递数据，程序员必须把所有需要的数据都打包在一个对象里面设置到model属性里。这种麻烦自然而然会导致这样的可能用法，直接把Controller本身设置为model，这又回到了Controller(Action)和Model一体的老路。</DIV>
<DIV>&nbsp;</DIV>
<DIV>前面讲到，WebWork也把所有的输入信息都包装在ActionContext里面，但Action并没有权力获取。而在Maverick中，Controller对于ControllerContext拥有全权的控制，两者地位不可同日而语。当然，由于参数ControllerContext包含request，reponse之类信息，这也意味着，Maverick Controller不能像WebWork Action那样脱离Web环境独立运行。</DIV>
<DIV>当然，这也并不意味着任何结构性缺陷。程序的结构由你自己控制，你完全可以把需要Unit Test的那部分从Web环境脱离开来，放到Business层。</DIV>
<DIV>如同WebWork，Maverick直接支持所有的主流View。Maverick的配置文件采Struts, Cocoon两家之长，URL -&gt; Action -&gt; View映射的主体结构类似于Struts，而View定义部分对Transform的支持则类似于Cocoon。如：</DIV>
<DIV>&lt;command name="friends"&gt;</DIV>
<DIV>&lt;controller class="org.infohazard.friendbook.ctl.Friends"/&gt;</DIV>
<DIV>&lt;view name="success" path="friends.jsp"&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;transform path="trimInside.jsp"/&gt;</DIV>
<DIV>&lt;/view&gt;</DIV>
<DIV>&lt;/command&gt;</DIV>
<DIV><STRONG>8. Spring MVC</STRONG><BR><A href="http://www.springframework.com/">http://www.springframework.com/</A></DIV>
<DIV>Spring MVC是我见过的结构最清晰的MVC Model 2实现。</DIV>
<DIV>Action不叫Action，准确地称做Controller；Controller接收request, response参数，干脆利落地返回ModelAndView（其中的Model不是Object类型，而是Map类型）。</DIV>
<DIV>其它的Web Framework中， Action返回值一般都只是一个View Name；Model则需要通过其它的途径（如request.attribute，Context参数，或Action本身的属性数据）传递上去。</DIV>
<DIV>&nbsp;</DIV>
<DIV>Spring以一招IoC名满天下，其AOP也方兴未艾。“Spring出品，必属精品”的观念已经深入人心。我这里多说也无益，强烈建议读者去阅读Spring Doc &amp; Sample &amp; Code本身。</DIV>
<DIV><STRONG>9. Turbine<BR></STRONG><A href="http://jakarta.apache.org/turbine/">http://jakarta.apache.org/turbine/</A></DIV>
<DIV>Turbine是一个提供了完善权限控制的坚实框架（Fulcrum子项目是其基石）。Turbine的个人用户不多，但不少公司用户选择Turbine作为框架，开发一些严肃的应用（我并没有说，用其它框架开发的应用就不严肃^_^）。Portal开源项目JetSpeed建立在Turbine上。</DIV>
<DIV>Turbine用RunData来传递输入输出数据。如同Maverick的ControllerContext，RunData是整个Turbine框架的数据交换中心。除了request, response等基本信息，RunData直接包括了User/ACL等权限控制相关的属性和方法，另外还包括Action Name和Target Template Name等定位属性。</DIV>
<DIV>Module是Turbine里面除了RunData之外的又一个核心类，是Turbine框架的基本构件，Action是Module，Screen也是Module。Turbine提供了LoginUser和LogoutUser两个Action作为整个系统的出入口。而其余流量的权限控制则由类似于Servlet Filter机制的Pipeline控制。</DIV>
<DIV>Turbine Pipeline的编程模型和Servlet Filter一模一样：Turbine Pipeline的Valve就相当于Servlet Filter，而ValveContext则相当于Filter Chain。还有更相近的例子，Tomcat源代码里面也有Valve和ValueContext两个类，不仅编程模型一样，而且名字也一样。</DIV>
<DIV>&nbsp;</DIV>
<DIV>权限控制贯穿于Turbine框架的始终。要用好Turbine，首先要通晓子项目Fulcrum 的Security部分的权限实现模型。</DIV>
<DIV>Fulcrum Security的权限实体包括四个-- User, Group, Role, Permission。</DIV>
<DIV>实体之间包含{Role，Permission}和{ Group, User, Role}两组关系。</DIV>
<DIV>{Role，Permission}是多对多的关系，一个Role可以具有各种Permission；{ Group, User, Role}之间是多对多的关系，一个Group可包含多个User，并可以给User分配不同的Role。</DIV>
<DIV>权限模型的实现同样采用Peer模式，Entity -&gt; EntityPeer, Entity -&gt; ManagerPeer。</DIV>
<DIV>Entity和EntityManger代表抽象的模型概念，而EntityPeer和ManagerPeer代表具体的实现。</DIV>
<DIV>用户可以根据模型，提供不同的实现，比如，用内存结构中实现，用数据表结构实现，与Windows NT权限验证机制结合，与OSWorkflow的权限控制模型结合，等等。其中，用数据表结构实现，又可以选择用Torque实现，或者用Hibernate实现。（Torque是Turbine的O/R Mapping子项目）</DIV>
<DIV>&nbsp;</DIV>
<DIV>例如，Falcrum.property配置文件包含如下Security相关选项：</DIV>
<DIV># -------------------------------------------------------------------</DIV>
<DIV>#&nbsp; S E C U R I T Y&nbsp; S E R V I C E</DIV>
<DIV># -------------------------------------------------------------------</DIV>
<DIV>services.SecurityService.user.class=org.apache.fulcrum.security.impl.db.entity.TurbineUser</DIV>
<DIV>services.SecurityService.user.manager=org.apache.fulcrum.security.impl.db.DBUserManager</DIV>
<DIV>services.SecurityService.secure.passwords.algorithm=SHA</DIV>
<DIV># -------------------------------------------------------------------</DIV>
<DIV>#&nbsp; D A T A B A S E&nbsp; S E R V I C E</DIV>
<DIV># -------------------------------------------------------------------</DIV>
<DIV>services.DatabaseService.database.newapp.driver=org.gjt.mm.mysql.Driver</DIV>
<DIV>services.DatabaseService.database.newapp.url=jdbc:mysql://127.0.0.1/newapp</DIV>
<DIV>services.DatabaseService.database.newapp.username=turbine</DIV>
<DIV>services.DatabaseService.database.newapp.password=turbine</DIV>
<DIV>&nbsp;</DIV>
<DIV>这说明，权限控制实现由数据库提供，需要根据权限模型创建如下数据表：</DIV>
<DIV>TURBINE_USER，TURBINE_ROLE，TURBINE_GROUP，</DIV>
<DIV>TURBINE_PERMISSION，TURBINE_ROLE_PERMISSION，</DIV>
<DIV>TURBINE_USER_GROUP_ROLE。</DIV>
<DIV>&nbsp;</DIV>
<DIV><STRONG>10. Cocoon<BR></STRONG><A href="http://cocoon.apache.org/">http://cocoon.apache.org</A></DIV>
<DIV>Cocoon项目是一个叫好不叫做的框架。采用XML + XSLT Pipeline机制，Java程序只需要输出XML数据，Cocoon框架调用XSL文件把XML数据转换成HTML、WML等文件。</DIV>
<DIV>Cocoon强大灵活的XSL Pipeline配置功能，XSLT的内容/显示分离的承诺，一直吸引了不少程序员fans。怎奈天不从人愿，由于复杂度、速度瓶颈、XSL学习难度等问题的限制，Cocoon一直主要限于网站发布出版领域，向CMS和Portal方向不断发展。另外，Cocoon开发了XSP脚本和Cocoon Form技术。</DIV>
<DIV>Cocoon的sitemap.xmap配置文件比较复杂，与其它的Web Framework差别很大。</DIV>
<DIV>主体Pipelines配置部分采用Pattern Match的方式，很像XSL语法，也可以类比于Web.xml里面Servlet Mapping的定义。比如，一个典型的URL-&gt;Action的映射定义看起来是这个样子：</DIV>
<DIV>&lt;map:pipelines&gt;</DIV>
<DIV>&lt;map:pipeline&gt;</DIV>
<DIV>&lt;map:match pattern="*-dept.html"&gt;</DIV>
<DIV>&nbsp; &lt;map:act set="process"&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;map:parameter name="descriptor"</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="context://docs/department-form.xml"/&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;map:parameter name="form-descriptor"</DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="context://docs/department-form.xml"/&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;map:generate type="serverpages" src="docs/confirm-dept.xsp"/&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;map:transform src="stylesheets/apache.xsl"/&gt;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; &lt;map:serialize/&gt;</DIV>
<DIV>&nbsp; &lt;/map:act&gt;</DIV>
<DIV>&nbsp; &lt;map:generate type="serverpages" src="docs/{1}-dept.xsp"/&gt;</DIV>
<DIV>&nbsp; &lt;map:transform src="stylesheets/apache.xsl"/&gt;</DIV>
<DIV>&nbsp; &lt;map:serialize/&gt;</DIV>
<DIV>&lt;/map:match&gt;</DIV>
<DIV>&lt;/map:pipeline&gt;</DIV>
<DIV>&lt;/map:pipelines&gt;</DIV>
<DIV><STRONG>11. Barracuda</STRONG><BR><A href="http://barracudamvc.org/Barracuda/index.html">http://barracudamvc.org/Barracuda/index.html</A></DIV>
<DIV>Barracuda是一个HTML DOM Component + Event/Listener结构的框架。</DIV>
<DIV>根据模板文件或配置文件生成静态Java类，并在代码中使用这些生成类，是Barracuda的一大特色。</DIV>
<DIV>Barracuda需要用XMLC项目把所有的HTML或WML模板文件，静态编译成DOM结构的Java类，作为页面组件。XMLC会根据HTML元素的id定义，生成相应DOM结点的简便操作方法。</DIV>
<DIV>&nbsp;</DIV>
<DIV>Barracuda的事件类也需要用Barracuda Event Builder工具把event.xml编译成Java类，引入到工程中。Barracuda直接用Java类的继承关系映射事件之间的父子层次关系。比如，ChildEvent是ParentEvent的子类。</DIV>
<DIV>Barracuda的事件分为两类：Request Events（Control Events）和Response Events（View Events）。</DIV>
<DIV>&nbsp;</DIV>
<DIV>Barracuda事件处理过程很像Windows系统消息队列的处理机制。</DIV>
<DIV>(1) Barracuda根据HTTP Request生成Request Event，放入到事件队列中。</DIV>
<DIV>(2) EventDispatcher检查事件队列是否为空，如果为空，结束。如果非空，按照先进先出的方式，从事件队列中取出一个事件，根据这个事件的类型，选择并调用最合适的EventListener，参数Event Context包含事件队列。</DIV>
<DIV>&nbsp;“根据事件类型，选择最合适的EventListener对象”的过程是这样的：比如，</DIV>
<DIV>EventDispatcher从时间队列里取出来一个事件，类型是ChildEvent；Barracuda首先寻找注册了监听ChildEvent的EventListener，如果找不到，再上溯到ChildEvent的父类ParentEvent，看哪些EventListener对ParentEvent感兴趣。</DIV>
<DIV>详细过程参见Barracuda的DefaultEventDispatcher类。</DIV>
<DIV>(3) EventListener根据Event Context包含的request信息，调用商业逻辑，获得结果数据，然后根据不同情况，把新的事件加入到Event Context的事件队列中。</DIV>
<DIV>(4) 控制交还给EventDispatcher，回到第(2)步。</DIV>
<DIV>&nbsp;</DIV><img src ="http://www.blogjava.net/faithwind/aggbug/33235.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-03-02 16:09 <a href="http://www.blogjava.net/faithwind/articles/33235.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>变革性的Java Web模板技术 -- fastm</title><link>http://www.blogjava.net/faithwind/articles/33220.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Thu, 02 Mar 2006 07:12:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/33220.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/33220.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/33220.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/33220.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/33220.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 1．“简单就是美”空想（响）曲&nbsp;在软件设计领域中，有一句脍炙人口的至理名言——简单即美好。几乎所有的软件设计大师，都会在其著作中训导读者：“简单即美好”，“Keep it simple, Stupid”，“Less is more”，…..&nbsp;这是一条耳闻能详，人人都会说的至理名言。但实际上，这也是一条被违背得最广泛、最彻底的至理名言。...&nbsp;&nbsp;<a href='http://www.blogjava.net/faithwind/articles/33220.html'>阅读全文</a><img src ="http://www.blogjava.net/faithwind/aggbug/33220.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-03-02 15:12 <a href="http://www.blogjava.net/faithwind/articles/33220.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>多种方法使用Java发送电子邮件</title><link>http://www.blogjava.net/faithwind/articles/33143.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Thu, 02 Mar 2006 02:54:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/33143.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/33143.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/33143.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/33143.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/33143.html</trackback:ping><description><![CDATA[第一件我们需要知道的事情是，通用电子邮件库实际上是包裹在JavaMail外层的API，所以无论我们选择哪种API，我们都需要<A href="http://java.sun.com/products/javamail/">JavaMail库</A>。您可能还需要JavaBeans激活框架（<A href="http://java.sun.com/products/javabeans/glasgow/jaf.html">JavaBeans Activation Framework</A> (JAF)），该框架将负责处理关于邮件选项的更复杂的内容。最后一个在“购物列表”上的就是通用电子邮件库（<A href="http://jakarta.apache.org/commons/email/">Commons Email</A>），您需要从JAF中将JavaMail库中的mail.jar、JAF中的activation.jar和通用电子邮件库中的commons-email-1.0.jar添加到classpath的设置中。
<P>&nbsp;&nbsp;&nbsp;&nbsp; 我们现在已经拥有了支持部件的工具包，让我们从使用JavaMail发送简单的电子邮件开始讲解，我们将提出所有的设置放到一个单独的静态类MailSettings当中，这样做将在比较代码的时候容易些。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 在发送电子邮件之前，您还需要知道SMTP服务器的主机名，SMTP服务器是负责将您的邮件发送到外部世界去的机器。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; JavaMail使用了Session类的概念来保存诸如SMTP主机和认证的信息，主要想法是基于会话（Sessions）在Java虚拟机中可以被隔离，这可以阻止恶意代码窃取其他用户在其他会话中的信息，这些信息可能包括用户名和密码等认证信息。“但是”，您可能会说，“在同一时间，我只在Java虚拟机上运行一个应用程序，而且我相信我的代码。”JavaMail的目的是开发大型的邮件系统，它有一个具有复杂性的关联层，您可以绕过没有经验的用户，我们用以下的例子来说明：</P>
<P>Properties props=new Properties(); <BR>props.put("mail.smtp.host",MailSettings.smtpHost); <BR>Session session=Session.getDefaultInstance(props,null);</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 此处，并没有创建新的会话，您只是从会话工厂（session factory）中得到并通过Properties的实例来进行设置，我们只对SMTP主机进行了设置和传送，在得到默认的实例的同时创建了一个共享的会话，现在我们可以使用这个会话来创建邮件消息了。</P>
<P>Message message=new MimeMessage(session);</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; JavaMail中有一个Message类，各种各样的消息都是它的子类，如果查看了JavaMail的API，您会发现它只有一个子类：MimeMessage。JavaMail是被设计为通用的电子邮件框架的，所以显然存在冗余的抽象。总之，我们已经使用会话创建了一个MimeMessage，现在我们需要来填充这个消息。</P>
<P>message.setFrom(new InternetAddress (MailSettings.fromAddress, MailSettings.fromName));</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 再次利用抽象，JavaMail有一个Address（地址）类，其他所有的地址类型皆源于此，但是我们现在只关心发送国际互联网的电子邮件，所以我们制造一个InternetAddress，这个地址用来表示电子邮件的来源和一个用于显示的“个人”名字。现自我们来设置邮件将发向何方。</P>
<P>message.setRecipient(Message.RecipientType.TO, new InternetAddress(MailSettings.toAddress));</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 地址的抽象将再次出现，我们设定接收器和接收器的类型，从此处开始，至少纯文本的电子邮件可以稳定地传送了，我们只需要设定邮件的标题、信文并打上时间戳。</P>
<P>message.setSubject(MailSettings.messageSubject);<BR>message.setText(MailSettings.messageBody1);<BR>message.setSentDate(new Date());</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 此时，我们已经准备好发送消息了。</P>
<P>Transport.send(message);</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 这个对Transport类的调用将会去查找适当的会话，并找出如何发送消息，尽管这样做看上去有些不直观。当我们完成这一步的时候，我们的邮件就已经发送出去了。此时，我们还需要添加代码来捕获三种JavaMail可能抛出的异常，它们是AddressException、MessagingException和UnsupportedEncodingException. 但这就是最基本的使用JavaMail发送邮件的方法。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 现在，让我们来看看如何使用Apache的通用电子邮件软件包（参见MailCommons.java）来完成同样的工作，通用电子邮件库是一系列类的集合，它们基于您所要发送的邮件类型，其中最简单的是SimpleEmail类，不需要建立任何会话或属性列表：</P>
<P>SimpleEmail email=new SimpleEmail();<BR>email.setHostName(MailSettings.smtpHost);</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 以上代码创建了我们的邮件并指定它通过我们选定的SMTP服务器发送，因为通用电子邮件库只处理国际互联网的电子邮件，所以就不必在创建InternetAddress实例上浪费时间了，我们可以简单地设定来源地址：</P>
<P>email.setFrom(MailSettings.fromAddress,MailSettings.fromName);</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 我们只需要添加一个地址到收信人列表当中，而不是发送接收器的类型：</P>
<P>email.addTo(MailSettings.toAddress);</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 同样，设定邮件的标题和信文与发送邮件都很简单。</P>
<P>email.setSubject(MailSettings.messageSubject);<BR>email.setMsg(MailSettings.messageBody1);<BR>email.send();</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 我们所需要去捕获的异常只有EmailException这一种，所以，您应该可以看出通过隐藏所有的框架和会话管理，事情变得非常简单并且代码更容易阅读。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 当然，这是我们所能发送的最简单的电子邮件，我们假设SMTP服务器不需要认证，我们发送的邮件只有一个收件人，并没有发送邮件副本。让我们先来看看认证，SMTP认证（SMTP AUTH）需要用户名和密码来发送邮件，在JavaMail中（参见MailJavaMail2.java），需要创建一个认证者（Authenticator）来返回所需的认证证书：</P>
<P>class ForcedAuthenticator extends Authenticator {<BR>public PasswordAuthentication getPasswordAuthentication() {<BR>return new PasswordAuthentication(MailSettings.smtpUsername,<BR>MailSettings.smtpPassword);<BR>}<BR>}</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 当我们创建会话的时候，就给出这个认证者的一个实例。</P>
<P>Session session=Session.getDefaultInstance(props,new ForcedAuthenticator());</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 然而，这里有一点要注意，这种做法将默认的会话绑定到了该认证者，因此您需要一个指向它的引用（reference）以再次取得这个会话。当然，您也可以转而使用Session.getInstance()，来创建一个唯一的不共享的实例，但是这将依靠您自己来管理会话的实例。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 现在让我们来看看如果使用通用电子邮件库的方法来进行认证（参见MailCommons2.java），您将只看到一行新的语句：</P>
<P>email.setAuthentication(MailSettings.smtpUsername,MailSettings.smtpPassword);这就解决了认证过程的所有痛苦。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 还有另外一种认证方式，那就是使用在邮件服务中的POP-before-SMTP，它并没有实现SMTP AUTH，其实SMTP AUTH是SMTP之后的一种扩展，而POP-before-SMTP的工作方式则是将受密码控制的邮件接收者和发送邮件的能力进行绑定，只有当用户经过POP协议访问某个受密码保护的POP邮件服务器一段时间之后，此用户才能够发送邮件。在JavaMail中，为了发送邮件，你需要编写打开POP信箱的代码，这将涉及JavaMail接收端的工作，我们将在下一篇文章中讲解相关内容；而对于通用电子邮件库来讲，事情就容易得多了：</P>
<P>email.setPopBeforeSMTP(true,popHost,popUsername,popPassword);</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 这使您可以操作与POP-before-SMTP相关的所有功能，而根本不需要考虑处理与POP相关的工作。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 让我们再来看看如何设定邮件的抄送收件人，现在JavaMail的Message类有一个setRecipients方法，该方法可以接受一个InternetAddress的数组，但这样做并不精巧，如果您已经有了一个储存电子邮件地址的字符串数组，那就可以结束这种复杂方法的考验。</P>
<P>ArrayList ccs=new ArrayList();<BR>for(String s:MailSettings.ccAddresses) ccs.add(new InternetAddress(s));<BR>message.setRecipients(Message.RecipientType.CC,<BR>(InternetAddress[]) ccs.toArray(new InternetAddress[ccs.size()]));</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 现在让我们来看看通用电子邮件库，我们发现了两件事情：首先，setTo、setCc和setBcc方法都使用了InternetAddress的集合作为它们的参数，这样做更符合Java的现行实践；其次，addTo、addCc和addBcc方法将创建和添加收件人列表变得轻松。（参见MailCommons3.java）</P>
<P>for(String s:MailSettings.ccAddresses) email.addCc(s);</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 这样的代码更加清晰。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;最后，让我们来看看如何发送包含内嵌图片的HTML格式的电子邮件，在此，JavaBeans激活框架将与JavaMail将协同工作，来协助图片的编码。在JavaMail的API中，我们首先创建一个MimeMultipart的实例，然后创建MimeBodyParts的实例，并将它们组装到MimeMultipart的实例中，所以，嵌入单个图片，比如uk-builder-com.gif这个图片文件，并将它显示在HTML消息中，我们需要完成以下步骤（参见HtmlJavaMail.java）</P>
<P>MimeMultipart multipart=new MimeMultipart();<BR>BodyPart msgBodyPart=new MimeBodyPart();<BR>msgBodyPart.setContent("&lt;H1&gt;Hi! From HtmlJavaMail&lt;/H1&gt;<BR>&lt;img src=\"cid:logo\"&gt;","text/html");<BR>BodyPart embedImage=new MimeBodyPart();<BR>DataSource ds=new URLDataSource(new URL(MailSettings.inlineImage));<BR>embedImage.setDataHandler(new DataHandler(ds));<BR>embedImage.setHeader("Content-ID","");<BR>multipart.addBodyPart(msgBodyPart);<BR>multipart.addBodyPart(embedImage);<BR>message.setContent(multipart);</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 第一个BodyPart将它的内容设置为一个字符串和“text/html”的内容类型，注意，在我们设置的HTML代码中，IMG标签指向了一个内容标识符（content-id），这就是MIME（多用途互联网邮件扩充协议）消息部分的名称，该消息将含有GIF格式的标识图文件，为了嵌入这个图片，我们创建了另一个BodyPart和一个JAF的数据源（data source），JAF类库将负责管理内容的工作；然后我们将使用这个DataSource在BodyPart上添加一个DataHandler（数据处理器），这样就可以读出我们的URL所指向的内容了。最后，我们还要在BodyPart的头部设定Content-ID，这样它就可以作为内嵌图片被访问了。我们创建的那些BodyParts将被添加到MimeMultipart的实例中，为此，我们使用setContent（）方法而不是调用setText（）方法。最棘手的部分莫过于管理这些内容标识符了，我们在此只完成了嵌入一个图片的工作，但今后每添加一个图片，您都需要在HTML中加入一个BodyPart和一个内容标识符。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 现在让我们来看看使用通用电子邮件库方法的版本（参见HtmlMailCommons.java）：</P>
<P>HtmlEmail email=new HtmlEmail();…<BR>String cid=email.embed(new URL(MailSettings.inlineImage),"Builder AU Logo");<BR>email.setHtmlMsg("&lt;H1&gt;Hi! From MailJavaMail3&lt;/H1&gt;&lt;img src=\"cid:"+cid+"\"&gt;");<BR>email.setTextMsg("Your email client does not support HTML messages, sorry");</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 这种方法简短了很多，而且这一版本拥有更多的功能：当电子邮件客户端不能处理HTML时，它将显示备用的文字。我们从创建一个SimpleEmail实例改变为创建一个HtmlEmail实例。为了嵌入一个图片，我们只需要简单地调用HtmlEmail的嵌入方法，这将返回一个字符串，该字符串含有被生成和管理的内嵌图片内容标识符的信息，我们可以直接使用这些信息来生成HTML内容。我们只需要简单地对适当的字符串调用setHtmlMsg方法来进行设定，setTextMsg将设定不支持HTML的客户端所显示的文字，然后我们就可以发送邮件了。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 但是，到目前为止，您可能正在想“直接使用JavaMail的意义是什么呢？”答案是，如果您的目的是发送电子邮件，那么对于大多数情况，通用电子邮件库仅仅需要少量的复杂代码就可以完成您的需求，但是它连一点控制邮件会话和接收邮件的功能都没有，如果您的应用软件不需要大规模地进行邮件转换或邮件阅读的操作，那么应该不会造成不便。当然，如果您需要进行上述操作，那您就已经在使用JavaMail库了。这里唯一的一个警告就是，在我写这篇文章之时，通用电子邮件库发布的是1.0版本，其API中可能有一些缺陷和漏洞，但它确实可以工作。现在您应该没有借口不让您的应用软件在工作的时候发送状态邮件了。</P>
<P><A href="http://www.builderau.com.au/resources/MailExamples.zip">您可以下载这篇文章的源代码</A></P>
<P><A href="http://www.builderau.com.au/program/java/soa/Sending_e_mail_in_Java_There_s_more_than_one_way/0,39024620,39222642,00.htm">查看本文的国际来源</A></P><img src ="http://www.blogjava.net/faithwind/aggbug/33143.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-03-02 10:54 <a href="http://www.blogjava.net/faithwind/articles/33143.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Jakarta Commons:巧用类和组件</title><link>http://www.blogjava.net/faithwind/articles/32849.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Tue, 28 Feb 2006 06:32:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/32849.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/32849.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/32849.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/32849.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/32849.html</trackback:ping><description><![CDATA[Jakarta Commons 是Jakarta 的子项目，它创建和维护着许多独立软件包，这些包一般与其他框架或产品<BR>无关，其中收集了大量小型、实用的组件，大部分面向服务器端编程。<BR>Commons的包分成两部分：Sandbox，Commons 代码库。Sandbox 是一个测试平台，用来检验各种设想、<BR>计划。本文介绍的组件属于Commons代码库，文章将展示各个组件的功能、适用场合，并通过简单的例子<BR>介绍其用法。<BR>一、概述<BR>可重用性是Jakarta Commons 项目的灵魂所在。这些包在设计阶段就已经考虑了可重用性问题。其中<BR>一些包，例如Commons 里面用来记录日志的Logging包，最初是为其他项目设计的，例如Jakarta Struts<BR>项目，当人们发现这些包对于其他项目也非常有用，能够极大地帮助其他项目的开发，他们决定为这些包<BR>构造一个"公共"的存放位置，这就是Jakarta Commons项目。<BR>为了真正提高可重用性，每一个包都必须不依赖于其他大型的框架或项目。因此，Commons项目的包<BR>基本上都是独立的，不仅是相对于其他项目的独立，而且相对于Commons内部的大部分其他包独立。虽然<BR>存在一些例外的情况，例如Betwixt 包要用到XML API，但绝大部分只使用最基本的API，其主要目的就<BR>是要能够通过简单的接口方便地调用。<BR>不过由于崇尚简洁，许多包的文档变得过于简陋，缺乏维护和支持，甚至有一部分还有错误的链接，<BR>文档也少得可怜。大部分的包需要我们自己去找出其用法，甚至有时还需要我们自己去分析其适用场合。<BR>本文将逐一介绍这些包，希望能够帮助你迅速掌握这一积累了许多人心血的免费代码库。<BR>说明：Jakarta Commons 和Apache Commons 是不同的，后者是Apache Software Foundation的一个<BR>顶层项目，前者则是Jakarta 项目的一个子项目，同是也是本文要讨论的主角。本文后面凡是提到Commons<BR>的地方都是指Jakarta 的Commons。<BR>为了便于说明，本文把Commons 项目十八个成品级的组件（排除了EL、Latka和Jexl）分成5类，<BR>如下表所示。<BR>必须指出的是，这种分类只是为了方便文章说明，Commons 项目里面实际上并不存在这种分类，同时<BR>这些分类的边界有时也存在一定的重叠。<BR>本文首先介绍Web 相关类和其他类里面的组件，下一篇文章将涉及XML 相关、包装这两类，最后一篇<BR>文章专门介绍属于工具类的包。<BR>二、其他类<BR>CLI、Discovery、Lang 和Collections 包归入其他类，这是因为它们都各自针对某个明确、实用的<BR>小目标，可谓专而精。<BR>2.1 CLI<BR>■ 概况：CLI 即Command Line Interface，也就是"命令行接口"，它为Java 程序访问和解析命令行<BR>参数提供了一种统一的接口。<BR>■ 官方资源：主页，二进制，源代码<BR>■ 何时适用：当你需要以一种一致的、统一的方式访问命令行参数之时。<BR>■ 示例应用：CLIDemo.java。CLASSPATH 中必须包含commons-cli-1.0.jar。<BR>■ 说明：<BR>有多少次你不得不为一个新的应用程序重新设计新的命令行参数处理方式？如果能够只用某个单一<BR>的接口，统一完成诸如定义输入参数（是否为强制参数，数值还是字符串，等等）、根据一系列规则分析<BR>参数、确定应用要采用的路径等任务，那该多好！答案就在CLI。<BR>在CLI中，每一个想要在命令中指定的参数都是一个Option对象。首先创建一个Options 对象，将<BR>各个Option对象加入Options对象，然后利用CLI提供的方法来解析用户的输入参数。Option对象可以<BR>要求用户必须输入某个参数，例如必须在命令行提供文件名字。如果某个参数是必须的，创建Option 对<BR>象的时候就要显式地指定。<BR>下面是使用CLI 的步骤。<BR>// …<BR>// ① 创建一个Options：<BR>Options options = new Options();<BR>options.addOption("t", false, "current time");<BR>// …<BR>// ② 创建一个解析器，分析输入：<BR>CommandLineParser parser = new BasicParser();<BR>CommandLine cmd;<BR>try {<BR>cmd = parser.parse(options, args);<BR>} catch (ParseException pe) {<BR>usage(options);<BR>return;<BR>}<BR>// …<BR>// ③ 最后就可以根据用户的输入，采取相应的操作：<BR>if (cmd.hasOption("n")) {<BR>System.err.println("Nice to meet you: " +<BR>cmd.getOptionValue('n'));<BR>}<BR>这就是使用CLI的完整过程了。当然，CLI 还提供了其他高级选项，例如控制格式和解析过程等，<BR>但基本的使用思路仍是一致的。请参见本文最后提供的示例程序。<BR>2.2 Discovery<BR>■ 概况：Discovery 组件是发现模式（Discovery Pattern）的一个实现，它的目标是按照一种统一<BR>的方式定位和实例化类以及其他资源。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你想用最佳的算法在Java程序中查找Java接口的各种实现之时。<BR>■ 应用实例：DiscoveryDemo.java，MyInterface.java，MyImpl1.java，MyImpl2.java，MyInterface。<BR>要求CLASSPATH 中必须包含commons-discovery.jar和commons-logging.jar。<BR>■ 说明：<BR>Discovery 的意思就是"发现"，它试图用最佳的算法查找某个接口的所有已知的实现。在使用服务的<BR>场合，当我们想要查找某个服务的所有已知的提供者时，Discovery 组件尤其有用。<BR>考虑一下这种情形：我们为某个特别复杂的任务编写了一个接口，所有该接口的实现都用各不相同的<BR>方式来完成这个复杂任务，最终用户可以根据需要来选择完成任务的具体方式。那么，在这种情形下，<BR>最终用户应该用什么办法来找出接口的所有可用实现（即可能的完成任务的方式）呢？<BR>上面描述的情形就是所谓的服务-服务提供者体系。服务的功能由接口描述，服务提供者则提供具体<BR>的实现。现在的问题是最终用户要用某种办法来寻找系统中已经安装了哪些服务提供者。在这种情形下，<BR>Discovery 组件就很有用了，它不仅可以用来查找那些实现了特定接口的类，而且还可以用来查找资源，<BR>例如图片或其他文件等。在执行这些操作时，Discovery遵从Sun的服务提供者体系所定义的规则。<BR>由于这个原因，使用Discovery 组件确实带来许多方便。请读者参阅本文后面示例程序中的接口<BR>MyInterface.java 和两个实现类MyImpl1.java、MyImple2.java，了解下面例子的细节。在使用Discovery<BR>的时候要提供MyInterface 文件，把它放入META-INF/services目录，注意该文件的名字对应接口的完整<BR>限定名称（Fully Qualified Name），如果接口属于某个包，该文件的名字也必须相应地改变。<BR>// …<BR>// ① 创建一个类装入器的实例。<BR>ClassLoaders loaders =<BR>ClassLoaders.getAppLoaders(MyInterface.class, getClass(), false);<BR>// …<BR>// ② 用DiscoverClass 的实例来查找实现类。<BR>DiscoverClass discover = new DiscoverClass(loaders);<BR>// …<BR>// ③ 查找实现了指定接口的类：<BR>Class implClass = discover.find(MyInterface.class);<BR>System.err.println("Implementing Provider: " + implClass.getName());<BR>运行上面的代码，就可以得到在MyInterface 文件中注册的类。再次提醒，如果你的实现是封装在包<BR>里面的，在这里注册的名字也应该作相应地修改，如果该文件没有放在正确的位置，或者指定名字的实现<BR>类不能找到或实例化，程序将抛出DiscoverException，表示找不到符合条件的实现。下面是MyInterface<BR>文件内容的一个例子：MyImpl2 # Implementation 2。<BR>当然，实现类的注册办法并非只有这么一种，否则的话Discovery 的实用性就要大打折扣了！实际上，<BR>按照Discovery 内部的类查找机制，按照这种方法注册的类将是Discovery 最后找到的类。另一种常用的<BR>注册方法是通过系统属性或用户定义的属性来传递实现类的名字，例如，放弃<BR>META-INF/services 目录下<BR>的文件，改为执行java -DMyInterface=MyImpl1 DiscoveryDemo命令来运行示例程序，这里的系统属性<BR>是接口的名字，值是该接口的提供者，运行的结果是完全一样的。<BR>Discovery 还可以用来创建服务提供者的(singleton)实例并调用其方法，语法如下：<BR>((MyInterface)discover.newInstance(MyInterface.class)).myMethod();。注意在这个例子中，我们并<BR>不知道到底哪一个服务提供者实现了myMethod，甚至我们根本不必关心这一点。具体的情形与运行这段<BR>代码的方式以及运行环境中已经注册了什么服务提供者有关，在不同的环境下运行，实际得到的服务提供<BR>者可能不同。<BR>2.3 Lang<BR>■ 概况：Lang是java.lang 的一个扩展包，增加了许多操作String的功能，另外还支持C 风格的枚举量。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当java.lang 包提供的方法未能满足需要，想要更多的功能来处理String、数值和<BR>System 属性时；还有，当你想要使用C风格的枚举量时。<BR>■ 示例应用：LangDemo.java，Mortgage.java，OnTV.java。CLASSPATH中必须包含commons-lang.jar。<BR>■ 说明：<BR>这个包提供了许多出于方便目的而提供的方法，它们中的大多数是静态的，简化了日常编码工作。<BR>StringUtils类是其中的一个代表，它使得开发者能够超越标准的java.lang.String 包来处理字符串。<BR>使用这些方法很简单，通常只要在调用静态方法时提供适当的参数就可以了。例如，如果要将某个单词<BR>的首字符改为大写，只需调用：StringUtils.capitalise("name")，调用的输出结果是Name。请浏览<BR>StringUtils API 文档了解其他静态方法，也许你会找到一些可以直接拿来使用的代码。本文提供的示例<BR>程序示范了其中一些方法的使用。<BR>另一个值得注意的类是RandomStringUtils，它提供了生成随机字符串的方法，用来创建随机密码实<BR>在太方便了。<BR>NumberUtils 类提供了处理数值数据的方法，许多方法值得一用，例如寻找最大、最小数的方法，将<BR>String 转换成数值的方法，等等。NumberRange和CharRange类分别提供了创建和操作数值范围、字符范<BR>围的方法。<BR>Builder包里的类提供了一些特殊的方法，可用来构造类的toString、hashCode、compareTo 和equals<BR>方法，其基本思路就是构造出类的高质量的toString、hashCode、compareTo 和equals 方法，从而免去<BR>了用户自己定义这些方法之劳，只要调用一下Builder 包里面的方法就可以了。例如，我们可以用<BR>ToStringBuilder 来构造出类的toString描述，如下例所示：<BR>public class Mortgage {<BR>private float rate;<BR>private int years;<BR>....<BR>public String toString() {<BR>return new ToStringBuilder(this).<BR>append("rate", this.rate).<BR>append("years", this.years).<BR>toString();<BR>}<BR>}<BR>使用这类方法有什么好处呢？显然，它使得我们有可能通过一种统一的方式处理所有数据类型。所有<BR>Builder 方法的用法都和上例相似。<BR>Java 没有C 风格的枚举量，为此，lang 包提供了一个类型安全的Enum 类型，填补了空白。Enum 类<BR>是抽象的，如果你要创建枚举量，就要扩展Enum 类。下面的例子清楚地说明了Enum 的用法。<BR>import org.apache.commons.lang.enum.Enum;<BR>import java.util.Map;<BR>import java.util.List;<BR>import java.util.Iterator;<BR>public final class OnTV extends Enum {<BR>public static final OnTV IDOL=<BR>new OnTV("Idol");<BR>public static final OnTV SURVIVOR =<BR>new OnTV("Survivor");<BR>public static final OnTV SEINFELD =<BR>new OnTV("Seinfeld");<BR>private OnTV(String show) {<BR>super(show);<BR>}<BR>public static OnTV getEnum(String show){<BR>return (OnTV) getEnum(OnTV.class, show);<BR>}<BR>public static Map getEnumMap() {<BR>return getEnumMap(OnTV.class);<BR>}<BR>public static List getEnumList() {<BR>return getEnumList(OnTV.class);<BR>}<BR>public static Iterator iterator() {<BR>return iterator(OnTV.class);<BR>}<BR>}<BR>以后我们就可以按照下面的方式使用枚举变量：OnTV.getEnum("Idol")。该调用从前面创建的枚举数据<BR>类型返回Idol。这个例子比较简单，实际上Enum类还提供了许多有用的方法，请参见本文后面提供的<BR>完整实例。<BR>2.4 Collections<BR>■ 概况：扩展了Java Collection框架，增添了新的数据结构、迭代机制和比较操作符。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：几乎所有需要操作数据结构的重要Java开发项目都可以使用Collections API。和Java<BR>的标准实现相比，Collections API 有着诸多优势。<BR>■ 示例应用：CollectionsDemo.java。要求CLASSPATH 中包含commons-collections.jar。<BR>■ 说明：<BR>要在有限的文章篇幅之内详尽地介绍Collections API 实在是太困难了，不过这里仍将涵盖大多数<BR>最重要的类，希望能够引起你的兴趣，认真了解一下其余的类。Collections 本身的文档也提供了许多资<BR>料并解释了每一个类的用法。<BR>Bag 接口扩展标准的Java Collection，允许生成计数器来跟踪Bag里面的所有元素。当你想要跟踪<BR>进出某个集合的元素的总数时，Bag 是非常有用的。由于Bag本身是一个接口，所以实际使用<BR>的应该是实<BR>现了该接口的类，例如HashBag 或TreeBag--从这些类的名字也可以看出，HashBag 实现的是一个<BR>HashMap的Bag，而TreeBag实现的是TreeMap的Bag。Bag 接口中两个最重要的方法是：<BR>getCount(Object o)，<BR>用来返回Bag 里面特定对象的出现次数；uniqueSet()，返回所有唯一元素。<BR>Buffer接口允许按照预定义的次序删除集合中的对象，删除次序可以是LIFO（Last In First Out，<BR>后进先出），或FIFO（First In First Out，先进先出），另外还可以是自定义的次序。下面来看看<BR>如何实现一个Buffer，按照自然次序删除元素。<BR>BinaryHeap 类实现了Buffer 接口，能够按照自然次序删除元素。如果要颠倒次序，则必须传入一个<BR>false，告诉Heap 采用自然次序的逆序。<BR>BinaryHeap heap = new BinaryHeap();<BR>// …<BR>// 将元素加入该Heap<BR>heap.add(new Integer(-1));<BR>heap.add(new Integer(-10));<BR>heap.add(new Integer(0));<BR>heap.add(new Integer(-3));<BR>heap.add(new Integer(5));<BR>//…<BR>// 删除一个元素<BR>heap.remove();<BR>调用该Heap 的remove，按照自然次序，元素集合中的-10将被删除。如果我们要求按照逆序排序，<BR>则被删除的将是5。<BR>FastArrayList、FastHashMap 和FastTreeMap 类能够按照两种模式操作，超越了与它们对应的标准<BR>Collection。第一种模式是"慢模式"，类的修改操作（添加、删除元素）是同步的。与此相对，另一种模式<BR>是"快模式"，对这些类的访问假定为只读操作，因此不需要同步，速度较快。在快模式中，结构性的改动<BR>通过下列方式完成：首先克隆现有的类，修改克隆得到的类，最后用克隆得到的类替换原有的类。<BR>FastArrayList、FastHashMap和FastTreeMap 类特别适合于那种初始化之后大部分操作都是只读操作的<BR>多线程环境。<BR>iterators 包为各种集合和对象提供标准Java Collection 包没有提供的迭代器。本文的示例应用示范了<BR>ArrayIterator，通过迭代方式访问Array的内容。iterators 包里面各种迭代器的用法基本上与标准<BR>Java 迭代器一样。<BR>最后，comparators 包提供了一些实用的比较符。所谓比较符其实也是一个类，它定义的是如何比较<BR>两个属于同一类的对象，决定它们的排序次序。例如，在前面提到的Buffer 类中，我们可以定义自己的<BR>比较符，用自定义的比较符来决定元素的排序次序，而不是采用元素的自然排序次序。下面来看看具体的<BR>实现经过。<BR>// …<BR>// ① 创建一个BinaryHeap 类，但这一次参数中<BR>// 指定NullComparator。NullComparator比较<BR>// null与其他对象，根据nullsAreHigh 标记来<BR>// 判断null 值比其他对象大还是小：如果<BR>// nullsAreHigh的值是false，则认为null 要比<BR>// 其他对象小。<BR>BinaryHeap heap2 = new BinaryHeap<BR>(new NullComparator(false));<BR>// …<BR>// ② 将一些数据（包括几个null 值）加入heap：<BR>heap2.add(null);<BR>heap2.add(new Integer("6"));<BR>heap2.add(new Integer("-6"));<BR>heap2.add(null);<BR>// …<BR>// ③ 最后删除一个元素，Bag 包含的null 将减少<BR>// 一个，因为null 要比其他对象小。<BR>heap2.remove();<BR>有关其他类Commons 组件的介绍就到这里结束。如果你想了解更多细节信息，请参见API文档，最好<BR>再看看这些包的源代码。<BR>三、Web类<BR>Web 类的组件用来执行与Web 相关的任务。<BR>3.1 FileUpload<BR>■ 概况：一个可以直接使用的文件上载组件。<BR>■ 官方资源：主页。由于这个组件尚未正式发布，今年二月发布的Beta版又有许多BUG，所以建议<BR>从nightly builds 下载最新的版本。<BR>■ 何时适用：当你想要在Java 服务器环境中加入一个易用、高性能的文件上载组件之时。<BR>■ 示例应用：fileuploaddemo.jsp，fileuploaddemo.htm，和msg.jsp。要求服务器端应用目录的<BR>WEB-INF/lib下面有commons-fileupload-1.0-dev.jar。<BR>■ 说明：<BR>FileUpload 组件解决了常见的文件上载问题。它提供了一个易用的接口来管理上载到服务器的文件，<BR>可用于JSP和Servlet 之中。FileUpload 组件遵从RFC1867，它分析输入请求，向应用程序提供一系列上<BR>载到服务器的文件。上载的文件可以保留在内存中，也可以放入一个临时位置（允许配置一个表示文件大<BR>小的参数，如果上载的文件超过了该参数指定的大小，则把文件写入一个临时位置）。另外还有一些参数<BR>可供配置，包括可接受的最大文件、临时文件的位置等。<BR>下面介绍一下使用FileUpload 组件的步骤。<BR>首先创建一个HTML 页面。注意，凡是要上载文件的表单都必须设置enctype属性，且属性的值必须<BR>是multipart/form-data，同时请求方法必须是POST。下面的表单除了上载两个文件，另外还有一个普通<BR>的文本输入框：<BR>&lt;form name="myform" action="fileuploaddemo.jsp"<BR>method="post" enctype="multipart/form-data"&gt;<BR>输入你的名字:&lt;br /&gt;<BR>&lt;input type="text" name="name" size="15"/&gt;&lt;br /&gt;<BR>图形:&lt;br /&gt;<BR>&lt;input type="file" name="myimage"&gt;&lt;br/&gt;<BR>文件:&lt;br /&gt;<BR>&lt;input type="file" name="myfile"&gt;&lt;br /&gt;&lt;br /&gt;<BR>&lt;input type="submit" name="Submit"<BR>value="Submit your files"/&gt;<BR>接下来创建JSP页面。<BR>// …<BR>// ① 检查输入请求是否为multipart的表单数据。<BR>boolean isMultipart = FileUpload.<BR>isMultipartContent(request);<BR>// …<BR>// ② 为该请求创建一个句柄，通过它来解析请求。执行<BR>// 解析后，所有的表单项目都保存在一个List中。<BR>DiskFileUpload upload = new DiskFileUpload();<BR>// 通过句柄解析请求，解析得到的项目保存在一个List 中<BR>List items = upload.parseRequest(request);<BR>// …<BR>// ③ 通过循环依次获得List里面的文件项目。要区分表示<BR>// 文件的项目和普通的表单输入项目，使用isFormField()<BR>// 方法。根据处理请求的要求，我们可以保存上载的文<BR>// 件，或者一个字节一个字节地处理文件内容，或者打<BR>// 开文件的输入流。<BR>Iterator itr = items.iterator();<BR>while(itr.hasNext()) {<BR>FileItem item = (FileItem) itr.next();<BR>// 检查当前的项目是普通的表单元素，还是一个上载的文件<BR>if(item.isFormField()) {<BR>// 获得表单域的名字<BR>String fieldName = item.getFieldName();<BR>// 如果表单域的名字是name…<BR>if(fieldName.equals("name"))<BR>request.setAttribute("msg",<BR>"Thank You: " + item.getString());<BR>} else {<BR>// 该项目是一个上载的文件，把它保存到磁盘。<BR>// 注意item.getName()<BR>// 会返回上载文件在客户端的完整路径名称，这似乎是一个BUG。<BR>// 为解决这个问题，这里使用了fullFile.getName()。<BR>File fullFile = new File(item.getName());<BR>File savedFile = new File<BR>(getServletContext().getRealPath("/"),<BR>fullFile.getName());<BR>item.write(savedFile);<BR>}<BR>}<BR>我们可以通过上载句柄的upload.setSizeMax 来限制上载文件的大小。当上载文件的大小超过允许的<BR>值时，程序将遇到异常。在上面的例子中，文件大小的限制值是-1，表示允许上载任意大小的文件。<BR>还有其他一些略有变化的使用形式，正如前面所指出的，我们可以在上载的文件上打开一个输入流，<BR>或者让它们驻留在内存中直至空间占用达到一定的限制值，或者在判断文件类型的基础上，以String 或<BR>Byte 数组的形式获取其内容，或者直接删除文件。这一切都只要使用FileItem 类提供的方法就可以方便<BR>地做到（DefaultFileItem 是FileItem的一个实现）。<BR>3.2 HttpClient<BR>■ 概况：这个API 扩展了java.net包，提供了模拟浏览器的功能。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你要构造Web 浏览器的功能；当你的应用需要一种高效的办法进行HTTP/HTTPS通信时。<BR>■ 示例应用：HttpClientDemo.java。要求CLASSPATH中有commons-httpclient.jar，<BR>common-logging.jar。要求使用JDK 1.4 或更高版本。<BR>■ 说明：<BR>HttpClient 扩展和增强了标准java.net 包，是一个内容广泛的代码库，功能极其丰富，能够构造出<BR>各种使用HTTP 协议的分布式应用，或者也可以嵌入到现有应用，为应用增加访问HTTP 协议的能力。<BR>在Commons 稳定版中，HttpClient 的文档似乎要比其他包更完善一些，而且还带有几个实例。下面我们通过<BR>一个简单的例子来了解如何提取一个Web 页面，HttpClient 文档中也有一个类似的例子，我们将扩充那<BR>个例子使其支持SSL。注意本例需要JDK 1.4 支持，因为它要用到Java Secure Socket Connection库，<BR>而这个库只有JDK 1.4 及更高的版本才提供。<BR>① 首先确定一个可以通过HTTPS 下载的页面，本例使用的是https://www.paypal.com/。同时确保<BR>%JAVA_HOME%/jre/lib/security/java.security文件包含了下面这行代码：<BR>security.provider.2=com.sun.net.ssl.internal.ssl.Provider。<BR>除了这些设置之外，HTTPS连接的处理方式没有其他特别的地方--至少对于本例来说如此。不过，如<BR>果远程网站使用的根证书不被你使用的Java 认可，则首先必须导入它的证书。<BR>② 创建一个HttpClient的实例。HttpClient 类可以看成是应用的主驱动程序，所有针对网络的功<BR>能都依赖于它。HttpClient 类需要一个Connection Manager来管理连接。<BR>HttpConnectionManager允许<BR>我们创建自己的连接管理器，或者，我们也可以直接使用内建的SimpleHttpConnectionManager或<BR>MultiThreadedHttpConnectionManager类。如果在创建HttpClient 时没有指定连接管理器，HttpClient<BR>默认使用SimpleHttpConnectionManager。<BR>// 创建一个HttpClient 的实例<BR>HttpClient client = new HttpClient();<BR>③ 创建一个HttpMethod的实例，即确定与远程服务器的通信要采用哪种传输方式，HTTP 允许采用<BR>的传输方式包括：GET，POST，PUT，DELETE，HEAD，OPTIONS，以及TRACE。这些传输方式分别作为一个<BR>独立的类实现，但所有这些类都实现HttpMethod接口。在本例中，我们使用的是GetMethod，创建GetMethod<BR>实例时在参数中指定我们想要GET 的URL。<BR>// 创建一个HttpMethod 的实例<BR>HttpMethod method = new GetMethod(url);<BR>④ 执行HttpMethod 定义的提取操作。执行完毕后，executeMethod方法将返回远程服务器报告的状态<BR>代码。注意executeMethod属于HttpClient，而不是HttpMethod。<BR>// 执行HttpMethod定义的提取操作<BR>statusCode = client.executeMethod(method);<BR>⑤ 读取服务器返回的应答。如果前面的连接操作失败，程序将遇到HttpException或IOException，<BR>其中IOException 一般意味着网络出错，继续尝试也不太可能获得成功。服务器返回的应答可以按照多种<BR>方式读取，例如作为一个字节数组，作为一个输入流，或者作为一个String。获得服务器返回的应答后，<BR>我们就可以按照自己的需要任意处置它了。<BR>byte[] responseBody = method.getResponseBody();<BR>⑥ 最后要做的就是释放连接。<BR>method.releaseConnection();<BR>以上只是非常简单地介绍了一下HttpClient 库，HttpClient 实际的功能要比本文介绍的丰富得多，<BR>不仅健壮而且高效，请参阅API 文档了解详情。<BR>3.3 Net<BR>■ 概况：一个用于操作Internet基础协议的底层API。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你想要访问各种Internet底层协议之时（Finger，Whois，TFTP，Telnet，POP3，FTP，NNTP，以及SMTP）。<BR>■ 示例应用：NetDemo.java。要求CLASSPATH中包含commons-net-1.0.0.jar。<BR>■ 说明：<BR>Net 包是一个强大、专业的类库，类库里的类最初属于一个叫做NetComponents 的商业产品。<BR>Net 包不仅支持对各种低层次协议的访问，而且还提供了一个高层的抽象。大多数情况下，Net包提<BR>供的抽象已能满足一般需要，它使得开发者不再需要直接面对各种协议的Socket 级的低层命令。使用高<BR>层抽象并不减少任何功能，Net API 在这方面做得很出色，既提供了足够的功能，又不至于在特色方面作<BR>过多的妥协。<BR>SocketClient 是支持所有协议的基础类，它是一个抽象类，聚合了各种协议都需要的公用功能。各种<BR>不同协议的使用过程其实很相似，首先利用connect方法建立一个指向远程服务器的连接，执行必要的<BR>操作，最后终止与服务器的连接。下面通过实例介绍具体的使用步骤。<BR>// …<BR>// ① 创建一个客户端。我们将用NNTPClient<BR>// 从新闻服务器下载新闻组清单。<BR>client = new NNTPClient();<BR>// …<BR>// ② 利用前面创建的客户端连接到新闻服务器。<BR>// 这里选用的是一个新闻组较少的服务器。<BR>client.connect("aurelia.deine.net");<BR>// …<BR>// ③ 提取新闻组清单。下面的命令将返回一个<BR>// NewsGroupInfo 对象的数组。如果指定的服<BR>// 务器上不包含新闻组，返回的数组将是空的，<BR>// 如果遇到了错误，则返回值是null。<BR>list = client.listNewsgroups();<BR>//...<BR>// ④ 最后终止与服务器的连接。<BR>if (client.isConnected())<BR>client.disconnect();<BR>必须说明的是，listNewsgroups命令可能需要较长的时间才能返回，一方面是因为网络速度的影响，<BR>另外也可能是由于新闻组清单往往是很庞大的。NewsGroupInfo对象包含有关新闻组的详细信息，并提供<BR>了一些操作新闻组的命令，比如提取文章总数、最后发布的文章、发布文章的权限，等等。<BR>其他客户端，例如FingerClient、POP3Client、TelnetClient 等，用法也差不多。<BR>结束语：有关Web相关类和其他类的介绍就到此结束。在下一篇文章中，我们将探讨XML类和包装类，<BR>最后一篇文章则介绍工具类。<BR>希望读者有兴趣试试本文提供的程序实例。很多时候Jakarta Commons 给人以混乱的感觉，希望本文<BR>使你加深了对Jakarta Commons 了解，或者至少引起了你对Commons 子项目以及它提供的各种实用API 和<BR>库的兴趣。<BR><BR><B>第二部分XML 类和包装类</B><BR>上一篇文章中，我们将Jakarta Commons的组件分成了五类，并介绍了其中的Web类和其他类，本文接着<BR>介绍XML 类和包装类，接下来的最后一篇文章将介绍工具类。注意Commons本身并不进行这种分类，这里<BR>进行分类纯粹是为组织方便起见。<BR>一、包装类<BR>这一类包含Codec 和Modeler 两个组件。<BR>1.1 Codec<BR>■ 概况：提供常用的编码器和解码器。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你需要Base64 和Hex编码功能的标准实现之时。<BR>■ 示例应用：CodecDemo.java。要求CLASSPATH必须包含commons-codec-1.1.jar。<BR>■ 说明：<BR>Codec 里面的类分成两个包，其中一个包实现的是常用的Base64 和Hex 编码机制，另一个包是语言、<BR>语音方面的编码。两个包的用法相似，鉴于语言、语音的编码并不是很常用，所以下面主要介绍第一个包。<BR>Base64编码主要用于Email 传输。定义MIME 文档传输的RFC 规定了Base 64 编码，从而使得任何二进制<BR>数据都可以转换成可打印的ASCII字符集安全地传输。例如，假设要通过Email 传输一个图形文件，<BR>Email 客户端软件就会利用Base64 编码把图形文件的二进制数据转换成ASCII 码。在Base64编码中，<BR>每三个8 位的字节被编码成一个4 个字符的组，每个字符包含原来24 位中的6 位，编码后的字符串大小是<BR>原来的1.3倍，文件的末尾追加"="符号。除了MIME文档之外，Base64 编码技术还用于BASIC认证机制<BR>中HTTP 认证头的"用户：密码"字符串。<BR>Base64类的使用相当简单，最主要的两个静态方法是：Base64.encodeBase64(byte[] byteArray)，<BR>用于对字节数组中指定的内容执行Base64 编码；Base64.decodeBase64(byte[] byteArray)，用于对字节<BR>数组中指定的内容执行Base64解码。另外，Base64还有一个静态方法<BR>Base64.isArrayByteBase64(byte[]<BR>byteArray)，用于检测指定的字节数组是否可通过Base64 测试（即是否包含了经过Base64编码的数据，<BR>如前所述，Base64 编码的结果只包含可打印的ASCII字符）。<BR>byte[] encodedBytes=Base64.encodeBase64(testString.getBytes());<BR>String decodedString=new String(Base64.decodeBase64(encodedBytes));<BR>System.err.println("\'^\'是一个合法的Base64 字符吗？"<BR>+ Base64.isArrayByteBase64(invalidBytes));<BR>Hex 编码/解码就是执行字节数据和等价的十六进制表示形式之间的转换。Hex 编码的编码、解码过程<BR>和Base64 相似，此处不再赘述。<BR>1.2 Modeler<BR>■ 概况：根据JMX（Java Management Extensions）规范的定义，支持对Model MBean（Managed Bean）<BR>的配置和实例化。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你想要创建和管理Model MBean，以便利用标准的管理API来管理应用之时。<BR>■ 示例应用：ModelerDemo.java，DemoManagedBean.java和mbeans-descriptors.xml。要求<BR>CLASSPATH 中包含commons-modeler-1.0.jar、commons-logging.jar、<BR>commons-digester.jar、<BR>commons-collections.jar、commons-beanutils.jar，以及Sun的JMX参考实现jmxri.jar。<BR>■ 说明：<BR>下面的说明要求读者对JMX 有一定的了解。<BR>Managed Bean 简称MBean，是一种关联到应用程序中被管理组件的Bean，是一种对资源抽象。Model<BR>MBean 是一种特殊的MBean，具有高度动态和可配置的特点，但Model MBean 的这种能力是有代价的，<BR>程序员需要设置大量的元信息来告诉JMX如何创建Model MBean，这些元信息包括组件的属性、操作和其它<BR>信息。Modeler 的目的就是降低程序员实现Model MBean 的工作量，它提供的一组函数为处理元数据信息<BR>带来了方便。另外，Modeler还提供了注册工具和一个基本的Model MBean。<BR>Modeler 允许以XML文件的形式定义元数据信息，该XML文件应当遵从随同Modeler 提供的DTD 定义。<BR>元数据信息用来在运行时创建注册信息，注册信息是所有Model MBean 的中心知识库，实际上相当于一个<BR>创建这类Bean 的工厂。<BR>下面我们首先为一个Managed Bean（DemoManagedBean）创建这个XML文件。DemoManagedBean有一<BR>个name 属性，可读写。<BR>&lt;?xml version="1.0" encoding="GB2312" ?&gt;<BR>&lt;!DOCTYPE mbeans-descriptors PUBLIC<BR>"-//Apache Software Foundation<BR>//DTD Model MBeans Configuration File"<BR>"http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd"&gt;<BR>&lt;!-- JMX MBean 的描述--&gt;<BR>&lt;mbeans-descriptors&gt;<BR>&lt;mbean name="ManagedBean" description="Example Managed Bean"<BR>type="ManagedBean"&gt;<BR>&lt;attribute name="name" description="Simple Name"<BR>type="java.lang.String" /&gt;<BR>&lt;constructor name="ManagedBean"/&gt;<BR>&lt;/mbean&gt;<BR>&lt;/mbeans-descriptors&gt;<BR>可以看到，这个XML 文件提供了许多ManagedBean 的信息，包括它的属性、构造函数，另外还有它的<BR>操作（不过本例没有显示），这就是所谓的元数据信息。如果你打算扩展随同Modeler 提供的标准MBean<BR>（称为BaseModelMBean），可以在mbean元素中以属性的形式指定Model MBean的类名称：。在前面的<BR>例子中，标准的Model MBean只是简单地把所有调用直接传递给ManagedBean 类。<BR>接下来，我们要注册上述信息。注意通过描述文件装入注册信息之后，我们通过一个静态方法提取格<BR>式化的注册信息：<BR>// 创建一个Registry<BR>Registry registry = null;<BR>try {<BR>URL url = ModelerDemo.class.getResource<BR>("mbeans-descriptors.xml");<BR>InputStream stream = url.openStream();<BR>Registry.loadRegistry(stream);<BR>stream.close();<BR>registry = Registry.getRegistry();<BR>} catch (Throwable t) {<BR>t.printStackTrace(System.out);<BR>System.exit(1);<BR>}<BR>创建好Registry之后，我们要创建一个Model MBean，并将它注册到默认的管理服务器。这样，任何<BR>JMX 客户程序都可以通过Model MBean 调用Managed Bean 的功能了。<BR>// 获得一个Managed Bean 实例的句柄<BR>DemoManagedBean mBean = new DemoManagedBean();<BR>// 创建一个Model MBean，并将它注册到MBean服务器<BR>MBeanServer mServer = registry.getServer();<BR>ManagedBean managed = registry.findManagedBean("ManagedBean");<BR>try {<BR>ModelMBean modelMBean = managed.createMBean(mBean);<BR>String domain = mServer.getDefaultDomain();<BR>ObjectName oName = new ObjectName(domain +<BR>":type=ManagedBean");<BR>mServer.registerMBean(modelMBean, oName);<BR>} catch(Exception e) {<BR>System.err.println(e);<BR>System.exit(0);<BR>}<BR>try {<BR>ObjectName name =<BR>new ObjectName(mServer.getDefaultDomain() +<BR>":type=ManagedBean");<BR>ModelMBeanInfo info = (ModelMBeanInfo) mServer.<BR>getMBeanInfo(name);<BR>System.err.println(" className="+info.getClassName());<BR>System.err.println(" description="+info.getDescription());<BR>System.err.println(" mbeanDescriptor="+info.getMBeanDescriptor());<BR>System.err.println("==== 测试====");<BR>System.err.println("Name 的原始值: " +<BR>mServer.getAttribute(name, "name"));<BR>mServer.setAttribute(name, new Attribute("name", "Vikram"));<BR>System.err.println("Name 的新值: " +<BR>mServer.getAttribute(name, "name"));<BR>} catch(Exception e) {<BR>System.err.println(e);<BR>System.exit(0);<BR>}<BR>虽然这个例子比较简单，但它仍旧清楚地说明了使用Modeler带来的方便，不妨将它与不使用Modeler<BR>的情况下创建一个类似的Model MBean相比较。通过XML文件来描述ModelMBeanInfo不仅灵活方便，而<BR>且也很容易扩展，比手工编写这类信息改进不少。<BR>二、XML类<BR>XML 类包含了与Java、XML技术相关的类，包括：Betwixt，Digester，Jelly，和JXPath。<BR>2.1 Betwixt<BR>■ 概况：实现XML 和JavaBean 的映射。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你想要以灵活的方式实现XML和Bean 的映射，需要一个数据绑定框架之时。<BR>■示例应用：BetwixtDemo.java，Mortgage.java，mortgage.xml。要求CLASSPATH 中必须包含<BR>commons-betwixt-1.0-alpha-1.jar、commons-logging.jar、commons-beanutils.jar、<BR>commons-collections.jar、以及commons-digester.jar。<BR>■ 说明：<BR>如果你以前曾经用Castor绑定数据，一定会欣赏Betwixt的灵活性。Castor 适合在一个预定义模式<BR>（Schema）的基础上执行Bean和XML 之间的转换；但如果你只想执行数据和XML之间的转换，最好的选<BR>择就是Betwixt。Betwixt 的特点就是灵活，能够方便地将数据输出成为人类可阅读的XML。<BR>Betwixt的用法相当简单。如果要把Bean 转换成XML，首先创建一个BeanWriter 的实例，设置其属性，<BR>然后输出；如果要把XML 转换成Bean，首先创建一个BeanReader的实例，设置其属性，然后用Digester<BR>执行转换。<BR>将Bean转换成XML：<BR>// 用Betwixt 将Bean转换成XML 必须有BeanWriter的实例。<BR>// 由于BeanWriter的构造函数要求有一个写入器对象，<BR>// 所以我们从创建一个StringWriter开始<BR>StringWriter outputWriter = new StringWriter();<BR>// 注意输出结果并不是格式良好的，所以需要在开始位置<BR>// 写入下面的内容：<BR>outputWriter.write("&lt;?xml version='1.0' ?&gt;");<BR>// 创建一个BeanWriter<BR>BeanWriter writer = new BeanWriter(outputWriter);<BR>// 我们可以设置该写入器的各种属性。<BR>// 下面的第一行禁止写入ID，<BR>// 第二行允许格式化输出<BR>writer.setWriteIDs(false);<BR>writer.enablePrettyPrint();<BR>// 创建一个Bean 并将其输出<BR>Mortgage mortgage = new Mortgage(6.5f, 25);<BR>// 将输出结果写入输出设备<BR>try {<BR>writer.write("mortgage", mortgage);<BR>System.err.println(outputWriter.toString());<BR>} catch(Exception e) {<BR>System.err.println(e);<BR>}<BR>将XML 转换成Bean：<BR>// 用Betwixt 来读取XML 数据并以此为基础创建<BR>// Bean，必须用到BeanReader 类。注意BeanReader 类扩展了<BR>// Digester包的Digester 类。<BR>BeanReader reader = new BeanReader();<BR>// 注册类<BR>try {<BR>reader.registerBeanClass(Mortgage.class);<BR>// 并解析它…<BR>Mortgage mortgageConverted =<BR>(Mortgage)reader.parse(new File("mortgage.xml"));<BR>// 检查转换得到的mortgage 是否包含文件中的值<BR>System.err.println("Rate: " + mortgageConverted.getRate() +<BR>", Years: " + mortgageConverted.getYears());<BR>} catch(Exception ee) {<BR>ee.printStackTrace();<BR>}<BR>注意，通过BeanReader 注册类时，如果顶层元素的名称和类的名称不同，必须用另一个方法注册并<BR>指定准确的路径，如reader.registerBeanClass("toplevelelementname", Mortgage.class)。<BR>2.2 Digester<BR>■ 概况：提供友好的、事件驱动的高级XML 文档处理API。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你想要处理XML 文档，而且希望能够根据XML 文档中特定的模式所触发的一组规则<BR>来执行某些操作时。<BR>■ 示例应用：DigesterDemo.java、Employee.java、Company.java、rules.xml以及company.xml。<BR>要求CLASSPATH 中必须包含commons-digester.jar、commons-logging.jar、<BR>commons-beanutils.jar<BR>以及commons-collections.jar。<BR>■ 说明：<BR>Digester 在解析配置文件的时候最为有用。实际上，Digester最初就是为读取Struts 配置文件而开<BR>发的，后来才移到Commons 包。<BR>Digester是一个强大的模式匹配工具，允许开发者在一个比SAX 或DOM API更高的层次上处理XML<BR>文档，当找到特定的模式（或找不到模式）时能够触发一组规则。使用Digester 的基本思路是：首先创<BR>建一个Digester 的实例，然后用它注册一系列模式和规则，最后将XML文档传递给它。此后，Digester<BR>就会分析XML 文档，按照注册次序来触发规则。如果XML文档中的某个元素匹配一条以上的规则，所有<BR>的规则会按照注册次序被依次触发。<BR>Digester本身带有12条预定义的规则。当XML文档中找到一个特定的模式时，想要调用某个方法吗？<BR>很简单，使用预定义的CallMethodRule！另外，你不一定要使用预定的规则，Digester 允许用户通过扩<BR>展Rule 类定义自己的规则。<BR>在指定模式时，元素必须用绝对名称给出。例如，根元素直接用名称指定，下一层元素则通过"/"符<BR>号引出。例如，假设company是根元素，company/employee 就是匹配其中一个子元素的模式。<BR>Digester允许使用通配符，例如*/employee 将匹配XML 文档内出现的所有employee元素。<BR>找到匹配的模式时，关联到该匹配模式的规则内有四个回调方法会被调用，它们是：begin，end，body，<BR>和finish。这些方法被调用的时刻正如其名字所示，例如调用begin 和end 的时刻分别是遇到元素的开<BR>始标记和结束标记之时，body是在遇到了匹配模式之内的文本时被调用，finish 则是在全部对匹配模式<BR>的处理工作结束后被调用。<BR>最后，模式可以在一个外部的规则XML 文档内指定（利用digester-rules.dtd），或者在代码之内<BR>指定，下面要使用的是第一种办法，因为这种办法比较常用。<BR>使用Digester 之前要创建两个XML文档。第一个就是数据或配置文件，也就是我们准备对其应用规<BR>则的文件。下面是一个例子（company.xml）<BR>&lt;?xml version="1.0" encoding="gb2312"?&gt;<BR>&lt;company&gt;<BR>&lt;name&gt;我的公司&lt;/name&gt;<BR>&lt;address&gt;中国浙江&lt;/address&gt;<BR>&lt;employee&gt;<BR>&lt;name&gt;孙悟空&lt;/name&gt;<BR>&lt;employeeNo&gt;10000&lt;/employeeNo&gt;<BR>&lt;/employee&gt;<BR>&lt;employee&gt;<BR>&lt;name&gt;猪八戒&lt;/name&gt;<BR>&lt;employeeNo&gt;10001&lt;/employeeNo&gt;<BR>&lt;/employee&gt;<BR>&lt;/company&gt;<BR>第二个文件是规则文件rules.xml。rules.xml 告诉Digester要在company.xml中查找什么、找到了<BR>之后执行哪些操作：<BR>&lt;?xml version="1.0" encoding="gb2312"?&gt;<BR>&lt;digester-rules&gt;<BR>&lt;!-- 创建顶层的Company对象--&gt;<BR>&lt;object-create-rule pattern="company" classname="Company" /&gt;<BR>&lt;call-method-rule pattern="company/name" methodname="setName"<BR>paramcount="0" /&gt;<BR>&lt;call-method-rule pattern="company/address"<BR>methodname="setAddress" paramcount="0" /&gt;<BR>&lt;pattern value="company/employee"&gt;<BR>&lt;object-create-rule classname="Employee" /&gt;<BR>&lt;call-method-rule pattern="name" methodname="setName"<BR>paramcount="0" /&gt;<BR>&lt;call-method-rule pattern="employeeNo" methodname=<BR>"setEmployeeNo" paramcount="0" /&gt;<BR>&lt;set-next-rule methodname="addEmployee" /&gt;<BR>&lt;/pattern&gt;<BR>&lt;/digester-rules&gt;<BR>这个文件有哪些含义呢？第一条规则，&lt;object-create-rule pattern="company"<BR>classname="Company" /&gt;，告诉Digester 如果遇到了模式company，则必须遵从object-create-rule，<BR>也就是要创建一个类的实例！那么要创建的是哪一个类的实例呢？classname="Company"属性指定了类<BR>的名称。因此，解析company.xml 的时候，当遇到顶级的company元素，等到object-create-rule规则执<BR>行完毕，我们就拥有了一个Digester 创建的Company 类的实例。<BR>现在要理解call-method-rule 规则也应该不那么困难了，这里call-method-rule 的功能是在遇到<BR>company/name 或company/address 模式时调用一个方法（方法的名字通过methodname 属性指定）。<BR>最后一个模式匹配值得注意，它把规则嵌套到了匹配模式之中。两种设定规则和模式的方式都是<BR>Digester 接受的，我们可以根据自己的需要任意选择。在这个例子中，模式里面定义的规则在遇到<BR>company/employee 模式时创建一个Employee 类的对象，设置其属性，最后用set-next-rule将这个雇员<BR>加入到顶层的Company。<BR>创建好上面两个XML 文件之后，只要用两行代码就可以调用Digester了：<BR>Digester digester = DigesterLoader.createDigester(rules.toURL());<BR>Company company = (Company)digester.parse(inputXMLFile);<BR>第一行代码装入规则文件，创建一个Digester。第二行代码利用该Digester 来应用规则。请参见本文<BR>后面提供的DigesterDemo.java 完整源代码。<BR>2.3 Jelly<BR>■ 概况：一种基于Java和XML 的脚本语言。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：简单地说，当你想要一种灵活的、可扩展的XML 脚本工具之时。<BR>■ 示例应用：JellyDemo.java，jellydemo.xml以及TrivialTag.java。要求CLASSPATH 中必须有<BR>commons-jelly-1.0-dev.jar、dom4j.jar、commons-logging.jar、commons-beanutils.jar以及<BR>commons-collections.jar。<BR>■ 说明：<BR>要说清楚Jelly到底是什么以及它扮演着哪种角色是件很不容易的事情。Jelly 试图提供一个通用的<BR>XML 脚本引擎，这种脚本引擎是可以由开发者通过定制动作和标记扩展的，XML文档之中的元素映射到<BR>JavaBean，而XML 元素的属性映射到JavaBean的属性。从某种意义上说，Jelly是一种结合了Betwixt<BR>和Digester的工具，但Jelly更强大，具有更好的可扩展性。<BR>一个Jelly 系统由多个组件构成。第一个组件是Jelly 脚本，它是一种由Jelly引擎解析的XML文档，<BR>经过解析的XML 文档元素被绑定到Jelly 标记动态处理。第二个组件是Jelly标记，它是一种实现了Jelly<BR>的Tag 接口的JavaBean，凡是Jelly 标记都可以实现doTag 方法，这个doTag 方法就是当脚本引擎遇到<BR>XML 文档中的特定元素时所执行的方法。Jelly正是通过这一机制实现动态的脚本处理能力，从某种意义<BR>上看，有点类似于Digester 的工作机制。<BR>Jelly 带有许多预定义的标记，其中部分标记提供核心Jelly支持，其他标记用来提供解析、循环、<BR>条件执行代码等方面的支持。另外，Jelly 还为Ant任务提供了广泛的支持。<BR>要在Java应用程序中使用Jelly，首先要创建一个JellyContext的实例，例如：JellyContext context<BR>= new JellyContext();。我们可以把JellyContext对象看成是一个编译和运行Jelly脚本的运行环境。<BR>有了JellyContext 就可以运行Jelly 脚本。JellyContext的输出实际上是一个XMLOutput类的实例：<BR>context.runScript(new File("jellydemo.xml"), output);。<BR>创建自定义标记时，我们既可以覆盖上面提到的doTag方法（如下面的例子所示），或者提供一个执<BR>行方法，如invoke()或run()：<BR>public void doTag(XMLOutput output) throws Exception {<BR>// 在这里加入要执行的操作，<BR>// 例如设置属性、访问文件系统等…<BR>this.intProp = 3;<BR>}<BR>下面提供了一个定义Jelly 脚本的XML 文件示例：<BR>&lt;j:jelly xmlns:j="jelly:core" xmlns:define="jelly:define"<BR>xmlns:tr="trivialTag"&gt;<BR>&lt;define:taglib uri="trivialTag"&gt;<BR>&lt;define:jellybean name="trivial" className="TrivialTag" /&gt;<BR>&lt;/define:taglib&gt;<BR>&lt;tr:trivial intProp="1" stringProp="ball"&gt;Hello World&lt;/tr:trivial&gt;<BR>&lt;/j:jelly&gt;<BR>这个例子用到jelly:define 和jelly:core标记，以及一个trivialTag 标记。当遇到trivial标记<BR>实例时，Jelly创建相应的JavaBean 的实例，执行doTag 方法（或者也可以是一个run 或invoke之类可<BR>调用的方法）。<BR>Jelly 还有许多其他功能，它既可以直接从命令行或Ant脚本运行，也可以嵌入到应用程序的代码之<BR>内，请参见Jelly 文档了解详情。<BR>2.4 JXPath<BR>■ 概况：Java中的XPath 解释器。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你想要在JavaBean、DOM或其他对象构成的结构中应用XPath 查询之时。<BR>■ 示例应用：JXPathDemo.java，Book.java，Author.java。要求CLASSPATH必须包含<BR>commons-jxpath-1.1.jar。<BR>■ 说明：<BR>下面的说明要求读者已具备基本的XPath 知识。<BR>XPath是一种查询XML文档的语言，JXPath将同一概念应用到了其他Java对象的查询，诸如JavaBean、<BR>Collection、Array和Map 等。<BR>JXPathContext是JXPath中的核心类，它利用一个工厂方法来定位和创建一个上下文的实例。由于<BR>有了这一机制，必要时开发者可以插入一个新的JXPath 的实现。要使用JXPathContext，只要简单地向<BR>它传递一个JavaBean、Collection 或Map，例如：JXPathContext context =<BR>JXPathContext.newContext(book);。<BR>利用JXPathContext 可执行许多任务。例如访问属性或嵌套属性，当然还可以设置属性：<BR>System.err.println(context.getValue("title"));<BR>System.err.println(context.getValue("author/authorId"));<BR>context.setValue("author/authorId", "1001");<BR>利用JXPath 还可以查找其他类型的对象，不过创建上下文对象的方式都一样，都是用上面介绍的静态<BR>方法获得一个新的上下文，传入想要查询的对象。<BR>结束语：有关包装类和XML 类的介绍就到这里结束。在下一篇也是最后一篇文章中，我们将了解工具<BR>类的包。在这个系列文章的第一篇中，我们把Commons项目包含的组件分成了5类，介绍了Web类和其他<BR>类。第二篇文章论及XML 类和包装类。这是最后一篇，探讨工具类的组件。注意Commons本身并不进行这<BR>种分类，这里进行分类纯粹是为说明和组织方便起见。<BR><BR><B>第三部分、工具类</B><BR>工具类包含BeanUtils、Logging、DBCP、Pool和Validator 这几个组件。<BR>一、BeanUtils<BR>■ 概况：提供了动态操作JavaBean 的工具。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你需要动态访问JavaBean，但对已编译好的accessor 和<BR>modifier 一无所知之时。被动态访问的JavaBean 必须遵从JavaBeans<BR>specification 定义的命名设计规范。<BR>■ 示例应用：BeanUtilsDemo.java，AppLayer1Bean.java，<BR>AppLayer2Bean.java，SubBean.java。要求CLASSPATH 中必须包含commons-beanutils.jar、<BR>commons-logging.jar 以及commons-collections.jar。<BR>■ 说明：<BR>在动态Java应用程序设计环境中，我们不一定能够预先获知JavaBean 的各种set、get 方法。即使已经<BR>知道了这些方法的名字，为Bean 的每个属性依次写出setXXX 或getXXX方法也是一件很麻烦的事情。考<BR>虑一下这种情形：几个几乎完全相同的Bean 从应用的一个层传递到另一个层，你会为每一个属性调用<BR>bean1.setXXX(bean2.getXXX())吗？虽然你可以这么做，但并非一定得这么做，因为你可以让BeanUtils<BR>为你完成这些繁琐的操作！BeanUtils可以帮助开发者动态地创建、修改和复制JavaBean。<BR>BeanUtils 能够操作符合下列条件的JavaBean：<BR>⑴ JavaBean必须提供一个没有参数的构造函数。<BR>⑵ JavaBean的属性必须能够通过getXXX和setXXX方法访问和修改。对于Boolean属性，也允许使用isXXX<BR>和setXXX。JavaBean的属性可以是只读或只写的，也就是说，允许只提供属性的set或get方法。<BR>⑶ 如果不采用传统的命名方式（即用get 和set），改用其它方式命名JavaBean 的accessor和modifier，<BR>那么必须通过与JavaBean 关联的BeanInfo 类声明这一点。<BR>下面来看一个简单的例子。<BR>要获取和设置JavaBean 的简单属性，分别使用PropertyUtils.<BR>getSimpleProperty(Object bean, String name)以及PropertyUtils.<BR>setSimpleProperty(Object bean, String name, Object value)方法。如下面的例子所示，其中<BR>AppLayer1Bean.java和AppLayer2Bean.java 定义了两个测试用的JavaBean。<BR>PropertyUtils.setSimpleProperty(app1Bean,"intProp1", new Integer(10));<BR>System.err.println("App1LayerBean, stringProp1: " +<BR>PropertyUtils.getSimpleProperty(app1Bean, "stringProp1"));<BR>既然我们可以通过直接调用Bean 的方法（app1Bean.getStringProp1()或<BR>app1Bean.setIntProp1(10)）来获取或设置Bean 的属性，为什么还要使用setSimpleProperty、<BR>getSimpleProperty方法呢？这是因为，我们不一定能够预先知道JavaBean 属性的名字，因此也不一定<BR>知道要调用哪些方法才能获取/设置对应的属性。这些属性的名字可能来自其他过程或外部应用程序设置<BR>的变量。因此，一旦搞清楚了JavaBean的属性的名字并把它保存到一个变量，你就可以将变量传递给<BR>PropertyUtils，再也不必依靠其他开发者才能预先得知正确的方法名字。<BR>那么，如果JavaBean 的属性不是简单数据类型，又该怎么办呢？例如，JavaBean的属性可能是一个<BR>Collection，也可能是一个Map。在这种情况下，我们要改用PropertyUtils.getIndexedProperty或<BR>PropertyUtils.getMappedProperty。对于集合类属性值，我们必须指定一个索引值，规定待提取或设置<BR>的值在集合中的位置；对于Map 类属性，我们必须指定一个键，表示要提取的是哪一个值。下面是两个例子：<BR>PropertyUtils.setIndexedProperty(<BR>app1Bean, "listProp1[1]", "新字符串1");<BR>System.err.println("App1LayerBean, listProp1[1]: " +<BR>PropertyUtils.getIndexedProperty(app1Bean,<BR>"listProp1[1]"));<BR>请注意，对于可索引的属性，索引值是通过方括号传递的。例如上面的例子中，我们把JavaBean<BR>（app1Bean）的List中索引为1 的值设置成了"新字符串1"，后面的一行代码又从索引1的位置提取同<BR>一个值。还有另一种方式也可以达到同样的目标，即使用<BR>PropertyUtils.setIndexedProperty(Object<BR>bean, String name, int index, Object value)和PropertyUtils.getIndexedProperty(Object bean,<BR>String name, int index)方法，在这两个方法中索引值作为方法的参数传递。对于Map类属性，也有类<BR>似的方法，只要改用键（而不是索引）来获取或设置指定的值。<BR>最后，Bean的属性可能也是一个Bean。那么，怎样来获取或设置那些以属性的形式从属于主Bean 的<BR>属性Bean 呢？只要使用PropertyUtils.getNestedProperty(Object bean, String name)和<BR>PropertyUtils.setNestedProperty(Object bean, String name, Object value)方法就可以了。下面提<BR>供了一个例子。<BR>// 访问和设置嵌套的属性<BR>PropertyUtils.setNestedProperty(app1Bean, "subBean.stringProp",<BR>"来自SubBean 的信息，通过setNestedProperty 设置。");<BR>System.err.println(<BR>PropertyUtils.getNestedProperty(app1Bean,"subBean.stringProp"));<BR>通过上面的例子可以看出，从属Bean 的属性是通过一个句点符号访问的。<BR>上述几种访问属性的方式可以结合在一起使用，嵌套深度不受限制。具体要用到的两个方法是<BR>PropertyUtils.getProperty(Object bean, String name)和PropertyUtils.setProperty(Object bean,<BR>String name, Object value)。例如：PropertyUtils.setProperty(app1Bean, "subBean.listProp[0]",<BR>"属性的值");。<BR>这个例子是把嵌套Bean 对象和可索引属性结合在一起访问。<BR>BeanUtils经常用于动态访问Web 应用中的请求参数。实际上，正是BeanUtils触发了Struts 项目中<BR>把请求参数动态转换成系统JavaBean 的灵感：利用代码把用户填写的表单转换成一个Map，其中参数的<BR>名字变成Map中的键，参数的值则来自于用户在表单中输入的数据，然后由一个简单的<BR>BeanUtils.populate调用把这些值转换成一个系统Bean。<BR>最后，BeanUtils 提供了一个一步到位的方法把数据从一个Bean 复制到另一个Bean：<BR>// 把app1Bean 的数据复制到app2Bean<BR>BeanUtils.copyProperties(app2Bean, app1Bean);<BR>BeanUtils 还有一些这里尚未提及的实用方法。不过不必担心，BeanUtils 是Commons 中文档较为完善的<BR>组件之一，建议读者参阅BeanUtils 包的JavaDoc 文档了解其余方法的相关信息。<BR>二、Logging<BR>■ 概况：一个封装了许多流行日志工具的代码库，并提供统一的日志访问接口。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你的应用需要一种以上的日志工具之时，或者预期以后会有这种需要之时。<BR>■ 示例应用：LoggingDemo.java，commons-logging.properties。要求CLASSPATH中必须包含<BR>commons-logging.jar，有时还需要log4j.jar。<BR>■ 说明：<BR>日志（Logging）使得我们能够调试和跟踪应用程序任意时刻的行为和状态。在任何规模较大的应用中，<BR>Logging 都是不可或缺的组成部分，因此现在已经有许多第三方Logging工具，它们免去了开发者自己编<BR>写Logging API 之劳。实际上，即使JDK 也带有构造好了的Logging API。既然已经有这么多选择（log4j，<BR>JDK，Logkit，等等），通常我们总是可以找到最适合自己应用要求的现成API。<BR>不过也有可能出现例外的情形，例如一个熟悉的Logging API 不能和当前的应用程序兼容，或者是由于某<BR>种硬性规定，或者是由于应用的体系结构方面的原因。Commons项目Logging 组件的办法是将记录日志的<BR>功能封装为一组标准的API，但其底层实现却可以任意修改和变换。开发者利用这个API来执行记录日志<BR>信息的命令，由API 来决定把这些命令传递给适当的底层句柄。因此，对于开发者来说，Logging组件对<BR>于任何具体的底层实现都是中立的。<BR>如果你熟悉log4j，使用Commons 的Logging API 应该不会有什么问题。即使你不熟悉log4j，只要知道<BR>使用Logging必须导入两个类、创建一个Log 的静态实例，下面显示了这部分操作的代码：<BR>import org.apache.commons.logging.Log;<BR>import org.apache.commons.logging.LogFactory;<BR>public class LoggingDemo {<BR>private static Log log = LogFactory.getLog<BR>(LoggingDemo.class);<BR>// ...<BR>}<BR>有必要详细说明一下调用LogFactory.getLog()时发生的事情。调用该函数会启动一个发现过程，即<BR>找出必需的底层日志记录功能的实现，具体的发现过程在下面列出。注意，不管底层的日志工具是怎么找<BR>到的，它都必须是一个实现了Log 接口的类，且必须在CLASSPATH 之中。Commons LoggingAPI直接提供<BR>对下列底层日志记录工具的支持：Jdk14Logger，Log4JLogger，LogKitLogger，NoOpLogger（直接丢弃<BR>所有日志信息），还有一个SimpleLog。<BR>⑴ Commons 的Logging 首先在CLASSPATH 中寻找一个commons-logging.properties 文件。这个属性<BR>文件至少必须定义org.apache.commons.logging.Log 属性，它的值应该是上述任意Log 接口实现的完整<BR>限定名称。<BR>⑵ 如果上面的步骤失败，Commons 的Logging接着检查系统属性<BR>org.apache.commons.logging.Log。<BR>⑶ 如果找不到org.apache.commons.logging.Log系统属性，Logging接着在CLASSPATH中寻找log4j<BR>的类。如果找到了，Logging就假定应用要使用的是log4j。不过这时log4j 本身的属性仍要通过<BR>log4j.properties 文件正确配置。<BR>⑷ 如果上述查找均不能找到适当的Logging API，但应用程序正运行在JRE 1.4 或更高版本上，则<BR>默认使用JRE 1.4 的日志记录功能。<BR>⑸ 最后，如果上述操作都失败，则应用将使用内建的SimpleLog。SimpleLog 把所有日志信息直接输<BR>出到System.err。<BR>获得适当的底层日志记录工具之后，接下来就可以开始记录日志信息。作为一种标准的API，Commons<BR>Logging API 主要的好处是在底层日志机制的基础上建立了一个抽象层，通过抽象层把调用转换成与具体<BR>实现有关的日志记录命令。<BR>本文提供的示例程序会输出一个提示信息，告诉你当前正在使用哪一种底层的日志工具。请试着在不<BR>同的环境配置下运行这个程序，例如，在不指定任何属性的情况下运行这个程序，这时默认将使用<BR>Jdk14Logger；然后指定系统属性-Jorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog <BR>再运行程序，这时日志记录工具将是SimpleLog；最后，把Log4J的类放入<BR>CLASSPATH，只要正确设置了log4j 的log4j.properties 配置文件，就可以得到Log4JLogger输出的信息。<BR>三、Pool<BR>■ 概况：用来管理对象池的代码库。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：当你需要管理一个对象实例池之时。<BR>■ 示例应用：PoolDemo.java 和MyObjectFactory.java。要求CLASSPATH中必须有commons-pool.jar<BR>和commons-collections.jar。<BR>■ 说明：<BR>Pool 组件定义了一组用于对象池的接口，另外还提供了几个通用的对象池实现，以及<BR>一些帮助开发者自己创建对象池的基础类。<BR>对于大多数开发者来说，对象池应该不算什么新概念了。也许许多读者已经在访问数据<BR>库的时候使用过数据库连接池，对象池的概念其实也相似。对象池允许开发者在缓冲区中创<BR>建一组对象（创建对象的操作可以通过应用的配置文件完成，或者也可以在应用的启动阶段<BR>完成），当应用程序需要用到对象时就可以很快获得相响应。如果应用程序不再需要对象，<BR>它仍旧把对象返回给缓冲池，下次需要使用对象时再从缓冲池提取。<BR>Pool 组件允许我们创建对象（实例）池，但不限制我们一定要使用某个具体的实现。<BR>Pool 组件本身提供了几种实现，必要时我们还可以创建自己的实现。<BR>Pool 组件包含三个基本的类：ObjectPool，这是一个定义和维护对象池的接口；<BR>ObjectPoolFactory，负责创建ObjectPool 的实例；还有一个PoolableObjectFacotry，它<BR>为那些用于ObjectPool 之内的实例定义了一组生命周期方法。<BR>如前面指出的，Pool组件包含几种通用的实现，其中一个就是GenericObjectPool，下<BR>面通过一个实例来看看它的用法。<BR>① 创建一个PoolableObjectFactory。这个工厂类定义对象如何被创建、拆除和验证。<BR>import org.apache.commons.pool.PoolableObjectFactory;<BR>public class MyObjectFactory implements<BR>PoolableObjectFactory {<BR>private static int counter;<BR>// 返回一个新的字符串<BR>public Object makeObject() {<BR>return String.valueOf(counter++);<BR>}<BR>public void destroyObject(Object obj) {}<BR>public boolean validateObject(Object obj)<BR>{ return true; }<BR>public void activateObject(Object obj) {}<BR>public void passivateObject(Object obj) {}<BR>}<BR>本例创建了一个序号不断增加的String 对象的池，验证操作（validateObject）总是<BR>返回true。<BR>② 利用PoolableObjectFactory 创建一个GenericObjectPool，maxActive、maxIdle<BR>等选项都采用默认值。<BR>GenericObjectPool pool = new GenericObjectPool<BR>(new MyObjectFactory());<BR>③ 从对象池"借用"一个对象。<BR>System.err.println("Borrowed: " + pool.borrowObject());<BR>④ 把对象返回给对象池。<BR>pool.returnObject("0");<BR>对象池的状态可以通过多种方法获知，例如：<BR>// 有多少对象已经激活（已被借用）？<BR>System.err.println("当前活动的对象数量: " + pool.getNumActive());<BR>本文后面提供的PoolDemo.java 提供了完整的源代码。<BR>四、DBCP<BR>■ 概况：数据库连接池。建立在Pool 组件的基础上。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：需要访问关系数据库之时。<BR>■ 示例应用：DBCPDemo.java。要求CLASSPATH中必须有commons-dbcp.jar、<BR>commons-pool.jar 以及commons-collections.jar。另外还要能够访问数据库，配置适合该<BR>数据库的JDBC 驱动程序。示例应用测试的是一个MySQL数据库连接，驱动程序是MySQL JDBC<BR>driver。注意运行这个程序需要二进制文件的nightly 版，当前的正式发行版缺少某些必需的类。<BR>最后，运行这个示例程序时，应当确保已经为JDBC 驱动程序设置了系统属性（-Djdbc.drivers=com.mysql.jdbc.Driver）。<BR>■ 说明：<BR>DBCP 建立在Pool组件的基础上，提供了数据库连接缓冲池机制。与常规的连接池相比，<BR>DBCP 的使用要稍微复杂一点，因为它的思路是以伪JDBC 驱动程序的形式提供一个通用的体<BR>系。不过，前面我们已经了解了Pool组件的基本知识，现在要理解DBCP 的用法应该也很简单了。<BR>// ...<BR>// ① 创建一个GenericObjectPool 类的实例。<BR>GenericObjectPool pool = new GenericObjectPool(null);<BR>// ...<BR>// ② 在前面讨论Pool 组件时提到GenericObjectPool<BR>// 要求有一个PoolableObjectFactory 来创建需<BR>// 要缓冲的Object 的实例，对于DBCP 来说，<BR>// 这一功能现在由PoolableConnectionFactory提<BR>// 供，如下面的例子所示：<BR>DriverManagerConnectionFactory cf =<BR>new DriverManagerConnectionFactory(<BR>"jdbc:mysql://host/db", "username", "password");<BR>PoolableConnectionFactory pcf = new PoolableConnectionFactory(<BR>CF, pool, null, "SELECT * FROM mysql.db", false, true);<BR>// ...<BR>// ③ 现在，我们只要创建并注册PoolingDriver：<BR>new PoolingDriver().registerPool("myPool", pool);<BR>接下来就可以从这个连接池提取连接了。注意创建这个连接池时采用了maxActive、<BR>maxIdle 等选项的默认值，如有必要，你可以在前面步骤1创建GenericObjectPool 类的实<BR>例时自定义这些值。DBCPDemo.java 提供了一个完整的实例。<BR>五、Validator<BR>■ 概况：一个收集了常见用户输入验证功能的API。<BR>■ 官方资源：主页，二进制，源代码。<BR>■ 何时适用：对JavaBean 执行常规验证操作之时。<BR>■ 示例应用：ValidatorDemo.java，MyValidator.java，MyFormBean.java，<BR>validation.xml。要求CLASSPATH 中必须有commons-validator.jar，<BR>commons-beanutils.jar，commons-collections.jar，commons-digester.jar，以及<BR>commons-logging.jar。<BR>■ 说明：<BR>如果你曾经用Struts 开发过Web 应用，那么应该已经用过Validator包了。Validator<BR>包极大地简化了用户输入数据的检验。不过，Validator 并不局限于Web应用，它还可以方<BR>便地用于其它使用了JavaBean的场合。<BR>Validator 允许为用户输入域定义验证条件，支持错误信息国际化，允许创建自定义的<BR>验证器，此外，Validator 包还提供了一些预定义的可以直接使用的验证器。<BR>验证规则和验证方法用XML文件定义（可以用一个或者多个XML 文件定义，但通常而言，<BR>把它们分开比较好）。验证方法文件定义了要用到的验证器，指定各个实际实现验证器的<BR>Java 类（不要求这些类实现某些特定的接口，也不要求这些类必须从特定的类派生，只需<BR>要遵从方法定义文件中声明的定义就可以了）。<BR>下面我们就来构造一个自定义的验证器，它的功能是检查Bean的一个String属性是否<BR>包含特定的字符（"*"）。<BR>import org.apache.commons.validator.*;<BR>public class MyValidator {<BR>public static boolean validateContainsChar(<BR>Object bean, Field field) {<BR>// 首先获得Bean 的属性（即一个String 值）<BR>String val = ValidatorUtil.getValueAsString<BR>(bean, field.getProperty());<BR>// 根据属性中是否包含"*"字符，返回true 或false。<BR>return ((val.indexOf('*') == -1)?false:true);<BR>}<BR>}<BR>ValidatorUtil类提供了许多实用方法，例如ValidatorUtil.getValueAsString用来<BR>提取Bean的属性值并返回一个String。现在我们要在XML文件中声明MyValidator验证器。<BR>&lt;!-- 定义验证器方法--&gt;<BR>&lt;global&gt;<BR>&lt;validator name="containsStar"<BR>classname="MyValidator"<BR>method="validateContainsChar"<BR>methodParams="java.lang.Object,<BR>org.apache.commons.validator.Field" /&gt;<BR>&lt;/global&gt;<BR>可以看到，XML 文件详细地定义了验证方法的特征，包括该方法的输入参数。下面来看<BR>看使用这个验证器的步骤。<BR>① 在上面的XML文件中加入我们要实现的验证规则。<BR>&lt;!-- 定义验证规则--&gt;<BR>&lt;formset&gt;<BR>&lt;!-- 检查Bean的name 属性是否能够通过<BR>containsStar测试--&gt;<BR>&lt;form name="myFormBean"&gt;<BR>&lt;field property="name" depends="containsStar"&gt;<BR>&lt;arg0 key="myFormBean.name" /&gt;<BR>&lt;/field&gt;<BR>&lt;/form&gt;<BR>&lt;/formset&gt;<BR>可以看到，所有验证规则都在formset 元素之内声明。formset 元素之内首先声明要验<BR>证的表单，表单之内列出了要验证的输入域及其验证条件。在本例中，我们希望验证<BR>myFormBean的name属性，检查该属性是否能够通过containsStar的验证（也即name 属性<BR>的值是否包含"*"字符）。<BR>② 以XML文件为基础，创建一个Validator 实例并予以初始化。<BR>// 装入验证器XML 文件<BR>InputStream in = getClass().getResourceAsStream<BR>("validator.xml");<BR>// 创建一个ValidatorResources<BR>ValidatorResources resources = new ValidatorResources();<BR>// 初始化验证器资源<BR>ValidatorResourcesInitializer.initialize(resources, in);<BR>// 创建Validator<BR>Validator validator = new Validator(resources, "myFormBean");<BR>validator.addResource(Validator.BEAN_KEY, bean);<BR>③ 验证Bean。验证的结果是一个ValidatorResults，其中包含了各个要求验证的属性<BR>按照各自的验证条件执行验证的结果。<BR>// 执行验证<BR>ValidatorResults results = validator.validate();<BR>④ 处理ValidationResults。<BR>//验证结果对象ValidationResults 可能还包含了验证其他表单属性的结果，<BR>//对于每一个属性，我们都可以单独提取其验证结果。<BR>ValidatorResult result = results.getValidatorResult("name");<BR>// 对于每一个属性，我们可以分别检查各个验证条件的检查结果。<BR>// 例如，name 属性通过了containsStar 验证吗？<BR>System.err.println("name 属性包含"*"字符的测试结果：" +<BR>result.isValid("containsStar"));<BR>对于每一个ValidationResult 的实例，我们可以查询它是否通过了某项特定的检查。<BR>例如，在上面的代码中，我们用result.isValid('containsStart')表达式来检查name 属<BR>性的ValidatorResult 实例，看看它是否通过了containsStar 验证。<BR>对于Web 应用来说，Validator是一个相当有用的组件，它提供了一组预定义的验证器，<BR>极大地方便了用户输入合法性的验证。预定义的验证器可以用来（但不限于）检查输入值的<BR>范围、数据类型、长度，以及email 地址和地理位置检查。此外，我们还可以自己定义验证<BR>器并将它加入到Validator框架之中。<BR>结束语：第三篇（也是最后一篇）介绍Jakarta Commons 的文章就到这里结束。虽然<BR>这个系列的文章只涉及了各个组件的基础知识，但希望它们已经足以让你开始下一步的深入研究。<img src ="http://www.blogjava.net/faithwind/aggbug/32849.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-02-28 14:32 <a href="http://www.blogjava.net/faithwind/articles/32849.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java中使用XML创建EMAIL模板</title><link>http://www.blogjava.net/faithwind/articles/32800.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Tue, 28 Feb 2006 01:53:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/32800.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/32800.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/32800.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/32800.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/32800.html</trackback:ping><description><![CDATA[发送邮件是web应用系统的一个基本功能。一般来说，邮件都有特定的类型，比如说密码提醒，欢迎信息，订单确认或者收信确认。尽管不同应用邮件的内容各不相同，但是发送邮件的过程基本上是一样的。 构建消息，发送给邮件服务器，发送。<BR>　　当使用java开发的时候，我们常常使用JavaMail API 来连接邮件服务器发送邮件。但是这种方式过于笨重（主要由邮件的灵活性造成的），所以当你需要多次使用这种方式发送邮件的时候，最好写一个wrapper.根据使用的方式不同，wrapper可以是发送某一特定的邮件，比如说密码提醒，或者作为一种通用的模式，接受主题，接收人，邮件内容作为参数。<BR>　　一旦使用wrapper发送邮件，你需要一个自主构建消息的系统。让我们使用密码提醒作为例子。基本上所有的邮件都包含主题，内容和接收人。当我们发送密码提醒邮件的时候，用户地址和密码是从某个记录登陆信息的知识库里提取的。主题和内容需要和数据库提取的数据合并，并且被保存在某个地方。系统设计最大的问题就是在什么地方保存这种类型的字符串。在很多情形下，字串被保存在属性文件里，这种方式分离了数据和源代码，并且使本地化更加容易。我在很多web应用系统中使用了这种存储机制，但很不幸的是，这种方式有很多缺陷。<BR>　　以下是利用属性文件存储邮件字串不合适的原因：<BR>·属性文件使用一种非常简单的数据结构－名称和值组合。当你需要很多值对应一个名称的时候这种结构就不合适了。比如，一个邮件有4个接收人，3个抄送人，使用属性文件很难解决这个问题。<BR>·属性文件的格式非常严格。名称和值必须在同一行上，所以当你编辑文件的时候长字符串是很难处理的。比如，把一个邮件的所有内容放进属性文件是一件多么痛苦的事情。如果你希望值的内容包括换行，你必须使用<BR>　　另一种选择是使用XML作为邮件模板，这也是本篇文章所要讨论的内容。XML为你构建模板提供了极大的灵活性，并且它不会有属性文件所有的格式限制，因此这种方式很容易处理长字符串。XML主要弱势就是它处理起来比属性文件复杂。使用属性文件的时候，装载文件和装载后访问文件非常容易。而装载XML文件和使用java提供的多个XML处理库之一处理XML文件就需要更多的工作了。<BR>这篇文章和所附的代码提供了一个通用的模板使你能够使用XML文件创建模板并且发送邮件，希望由此能够减轻这个过程的痛苦。在这个模板里，我将使用Jakarta 项目里的Commons Digester 包来处理XML，使用JavaMail API发送邮件。<BR><BR><B>邮件模板</B><BR>　　让我们来看看邮件模板的格式。模板是XML文件，它包含一个根元素和一系列根的子元素。根元素是&lt;email&gt;。必要的子元素是&lt;subject&gt;, &lt;body&gt;, 和 &lt;from&gt;。可选的子元素是 &lt;to&gt;, &lt;cc&gt;, 和 &lt;bcc&gt;。如果你使用过邮件系统，那么你可以推导出这些元素实际包含的内容。可选的元素有多个实例，所以你可以为每种类型的接收者指定多个地址。我待会会在描述消息处理的时候来解释运行机制。以下是一个模板文件的例子。<BR>&lt;email&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;from&gt;rafe@rafe.us&lt;/from&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;to&gt;someone@example.com&lt;/to&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;cc&gt;someoneelse@example.com&lt;/cc&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;bcc&gt;rafe@rafe.us&lt;/bcc&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;subject&gt;This is the subject&lt;/subject&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;body&gt;This is the body of an email message.&lt;/body&gt; <BR>&lt;/email&gt;<BR><BR><B>可定制的模板</B><BR>　　属性文件的一个有用的特性是你可以使用MessageFormat 类用动态传入的值替代属性文件里的被指定参数。比如说，如果你需要在属性文件里指定errors,其中一个errors是file not found， 你可以这样写：<BR>file.not.found.error=Error, could not find file {0}.<BR>然后，在运行时刻，你这样使用MessageFormat：<BR>ResourceBundle bundle = ResourceBundle.getBundle(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"MyProperties", currentLocale); <BR><BR>Object[] arguments&nbsp;&nbsp;&nbsp;&nbsp;= { "some_file.txt" }; <BR><BR>String newString&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= MessageFormat.format(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bundle.getString("file.not.found.error"), arguments);<BR>最后，newString 将包含Error, could not find file some_file.txt.我在这个系统里加入了类似的灵活性。 可以格式化所有的字符串，所以你可以在邮件模版的subject 和body元素里内嵌在属性文件使用的同样的令牌。<BR>　　在某种情形下，你希望在发送邮件的时候插入个人化的信息。比如，你希望在邮件内容里或者订单的内容里包含收件人的姓。本系统使用MessageFormat 来处理邮件模版的内容和主题，从而解决这个问题。处理内容和主题的时候只使用一个参数数组。这样主题里可以包含令牌{0}, {2}, {3},&nbsp;&nbsp;内容可以包含令牌{0}, {1}, {4} 。我之所以采用这种方式是因为在很多情形下主题和内容使用相同的参数，同时这种方式也简化了传递给EmailSender所需要的参数。<BR><BR><B>处理模版</B><BR>　　创建完模版，下一步所要做的就是处理它。我们知道，现在有很多的XML处理包可供选择。Commons Digester是Jakarta的公共项目，最初是为了在Struts项目中快速方便的解析Struts的的配置文件而产生的。它提供了从XML文件里的元素到使用类似于XPath&nbsp;&nbsp;语法的数据结构的映射。 好处在于为了从&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;XML文件里得到某个元素你不必用SAX一个节点一个节点的解析，也不必使用DOM处理树状数据结构。<BR>　　下面这个方法从XML文件里读取数据，然后把数据拷贝到EmailTemplate对象中。<BR><BR>public static EmailTemplate getEmailTemplate(InputStream aStream) <BR>{ <BR>&nbsp;&nbsp;&nbsp;&nbsp;Digester digester = new Digester(); <BR>&nbsp;&nbsp;&nbsp;&nbsp;digester.setValidating(false); <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;digester.addObjectCreate("email", EmailTemplate.class); <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;digester.addBeanPropertySetter("email/subject", "subject"); <BR>&nbsp;&nbsp;&nbsp;&nbsp;digester.addBeanPropertySetter("email/body", "body"); <BR>&nbsp;&nbsp;&nbsp;&nbsp;digester.addBeanPropertySetter("email/from", "from"); <BR>&nbsp;&nbsp;&nbsp;&nbsp;digester.addCallMethod("email/to", "addTo", 0); <BR>&nbsp;&nbsp;&nbsp;&nbsp;digester.addCallMethod("email/cc", "addCc", 0); <BR>&nbsp;&nbsp;&nbsp;&nbsp;digester.addCallMethod("email/bcc", "addBcc", 0); <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;try <BR>&nbsp;&nbsp;&nbsp;&nbsp;{ <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return (EmailTemplate)digester.parse(aStream); <BR>&nbsp;&nbsp;&nbsp;&nbsp;} <BR>&nbsp;&nbsp;&nbsp;&nbsp;catch (IOException e) <BR>&nbsp;&nbsp;&nbsp;&nbsp;{ <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.error("Error: ", e); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null; <BR>&nbsp;&nbsp;&nbsp;&nbsp;} <BR>&nbsp;&nbsp;&nbsp;&nbsp;catch (SAXException e) <BR>&nbsp;&nbsp;&nbsp;&nbsp;{ <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.error("Error: ", e); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null; <BR>&nbsp;&nbsp;&nbsp;&nbsp;} <BR>}<BR><BR>　　让我们来逐行研究这段代码。Commons Digester工作的原理是由你来指定解析文件的一些规则。因为没有规范邮件模版的DTD文件，所以在指定处理规则之前，我将validating flag设定为false。开始处理文件的时候，我实例化Digester对象然后调用方法建立数据映射规则。首先，我调用addObjectCreate()方法来建立创建EmailTemplate对象的规则。email是XML模版文件的根元素。因此模版文件和EmailTemplate 对象一一对应。<BR>　　我使用addBeanPropertySetter()来处理在模版文件中只出现一次的元素。这个方法有两个参数，元素的路径和要调用的赋值方法。在第一次调用的时候，我指定在文件中符合email/subject 模式的元素应该赋值给EmailTemplate 类的subject 。我们用 “/”来描速XML文件的内嵌关系。在这个例子中，符合subject模式的元素是email 子元素。为了提供更多的灵活性我们可以使用Wildcards。参考Commons Digester的JavaDoc 你可以了解详细的模式的构成方式。<BR>　　使用赋值方法处理在模版文件中出现多次的元素是不可行的。我们使用addCallMethod()来处理这种情形，这个方法从元素中取值并且调用指定的方法。我使用这个方法有三个参数的版本，它们是：匹配的模式，调用的方法，调用方法所使用的参数数量。在例子的三种情形中第三个参数都是0，说明符合模式的元素是调用方法的唯一参数。在EmailTemplate类中我定义了三个方法：addTo(), addCc(),&nbsp;&nbsp;addBcc()，这三个方法将模版文件中的收件人列表加入到模版类的收件人集合中。<BR>　　邮件元素的六种类型的子元素的规则都被指定好之后，我开始解析这个文件。在这个例子中， 我传入getEmailTemplate 方法的输入参数InputStream 。parse方法可以解析File，SAX InputSource, InputStream,&nbsp;&nbsp;Reader, 目标文件的URI。我使用InputStream。 由调用这个方法的代码取得XML文件并且把它转化为InputStream 。为了让这个方法更加通用，我可以用Object作为参数，并且在方法内部使用instanceof 来确定参数的类型，再用相应的方式来处理。<BR>　　方法parse 抛出IOException 或者SAXException。把这些异常传给Log4J，由它来处理，返回null. 如果没有异常抛出， 将返回由Digester创建的EmailTemplate对象。<BR><BR><B>EmailTemplate类剩下的部分</B><BR>　　getEmailTemplate()方法是类EmailTemplate的核心。其他的部分是一些属性值和一些辅助性的方法。有3个String 类型的属性值：内容，主题，寄件人地址，3个ArrayList属性值：to, CC, BCC 列表，这3个值都以String作为基本元素。还有相应的get，set和加入集合的方法。还有3个附加的方便的方法：getToAddresses(), getCcAddresses(), 和 getBccAddresses()。JavaMail接口需要InternetAddress 数组作为地址集合的参数，这些方法可以把对象的String数组转化为JavaMail接口需要的数组形式。<BR><BR><B>类EmailSender</B><BR>　　当模版文件被解析成EmailTemplate对象，下一步就是发送邮件信息。EmailSender 类包含一个静态的，重载的方法－sendEmail()。 这个方法可以通过很多种方式调用，所有的方式都是对下面这个完全参数方法的一个引用：<BR>public static void sendEmail( <BR>&nbsp;&nbsp;&nbsp;&nbsp;String aTo, <BR>&nbsp;&nbsp;&nbsp;&nbsp;EmailTemplate aTemplate, <BR>&nbsp;&nbsp;&nbsp;&nbsp;String[] aArgs)<BR>　　参数不需要过多的解释。第一个是邮件的发送地址。你可以在邮件模版里指定很多接收人地址，但是在运行时刻，大多数情况下，系统只需要一个接收人。比如说，你发送一封密码提醒的邮件，只需要指定申请密码的用户的邮件地址。在邮件模版里指定的收件人列表在某种情况下适用：作为测试，系统需要发送邮件到特定收件人列表或者发送时需要包含特定收件人列表。比如说，假设一个系统每当订单提交的时候需要通过一封邮件触发一个workflow，在这种情形下邮件模版种特定的接收人地址是有意义的。<BR>　　第二个参数是EmailTemplate自身。第三个参数是MessageFormat解析邮件主题和内容所需要的参数集。由调用这个方法的代码来创建个性化邮件模版所需要的信息数组。也有其他申明的方法简化了这个方法的调用（所以你可以在不指定收件人，或者在没有参数的情况下调用这个方法）。<BR>　　方法内部由使用JavaMail发送邮件所需要的一系列调用组成。我觉得使用JavaMail会造成许多冗余，我们来具体看一下。首先，我要通过检测来确定EmailTemplate是否为空。如果为空，什么都不能做。设定的第一步是使用SMTP server的设置创建一个Properties对象（Hashtable）。我把SMTP server的设置设定在 文件里，所以我把这个值从属性文件里读出来然后放到我创建的properties对象里去。<BR>　　接着我创建了一个JavaMail Session 对象传入Properties 对象。Session对象在创建MimeMessage对象的时候需要。这个是我待会要做的。然后我将From:的值指定到传入参数EmailTemplate对象的相应栏位。下一步我把To:的值设定到我构建的消息中。这里会有一些技巧，因为用户可以传入To: 地址，同时邮件模版里也包含一些To:地址。问题在于JavaMail 喜欢使用数组描速地址列表，所以由我来决定接收人列表的有多大，然后构建传入的参数。<BR>　　因为CC: BCC:的地址必须在模版里指定，我们可以直接来处理它们。我使用EmailTemplate类里的方法把其他的收件人加入到消息里。就像我开始提到的，我使用MessageFormat解析处理邮件主题和内容的方法所需要的参数集。做完之后，我把新的主题拷贝到消息主体里。如此处理消息的内容。剩下的就是调用Transport.send()并且传入MimeMessage 对象。<BR><BR><B>使用这个系统</B><BR>　　我刚才已经解释了系统的运作原理，现在我来解释如何通过 servlet来使用它，在其他程序里调用的方式是类似的。以下是代码：<BR>// Grab the email template. <BR>InputStream template = <BR>&nbsp;&nbsp;&nbsp;&nbsp;getServlet() <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getServletConfig() <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getServletContext() <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getResourceAsStream( <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"/WEB-INF/email/registrationNotification.xml"); <BR><BR>EmailTemplate notification = EmailTemplate.getEmailTemplate(template); <BR><BR>// Create the section of the email containing the actual user data. <BR>String[] args = { "Rafe" }; <BR><BR>EmailSender.sendEmail("rafe@rafe.us", notification, args);<BR>　　使用这个系统的第一步是把你的XML模版文件转化成InputStream。 因为我使用的是servlet,我从ServletContext取得这个文件。当然还有其他的方式取得这个文件，但是在servlet环境里，这种方式很好用。我只用把InputStream 传给刚才所描述的EmailTemplate.getEmailTemplate()方法就可以了。下一步，建立个性化邮件所需要的参数数组，然后调用方法EmailSender.sendEmail()。<BR>更多<BR>　　这个系统还可以更多的优化，有两个比较明显的需要改善的地方：系统应该同时支持纯文本和HTML；支持附件。创建这种类型的信息需要使用类型javax.mail.MimeMultipart。还有在何处存储附件和如何指定附件的问题。在我的系统里，我没有在模版文件里处理附件，因为我的附件是在邮件发送的时候创建的。<BR><BR><BR>Rafe Colburn 是一个Java开发工程师，同时也是一名计算机图书的作者，他使用过Perl，CGI, HTML, JAVA <!-- the post be hidden --><img src ="http://www.blogjava.net/faithwind/aggbug/32800.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-02-28 09:53 <a href="http://www.blogjava.net/faithwind/articles/32800.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Hibernate Validator </title><link>http://www.blogjava.net/faithwind/articles/29574.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Sun, 05 Feb 2006 02:17:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/29574.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/29574.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/29574.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/29574.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/29574.html</trackback:ping><description><![CDATA[<DIV class=center>
<H4>摘要:</H4>在项目的业务属性中,你是不是要经常验证属性的取值范围呢. 想要了解比较优美的解决方案吗? 看看Hibernate Validator 是怎么做的吧.一见到她,相信你就会说: Oh God, 这就是我需要的. </DIV>
<DIV class=right>
<DIV class=help><BR>作者：icess(作者的blog:<A href="http://blog.matrix.org.cn/page/icess" target=_new>http://blog.matrix.org.cn/page/icess</A>)<BR>关键字：Hibernate Validator <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>用Annotations 给类或者类的属性加上约束(constraint),在运行期检查属性值是很优雅的.Hibernate Validator就是这样的一个框架.该框架是十分容易的(就像参考文档中宣称的那样),几乎没有什么学习曲线,Validator 是一个验证框架 不需要和Hibernate的其他部分绑定就可以使用,只要在你的项目中添加Hibernate-annotations.jar库就可以了.那么下面就让我们看看怎么使用吧.<BR><BR><B>Person.java 类</B><BR></DIV></DIV>
<DIV class=overflow id=text><PRE class=overflow>/*<BR> * Created on 2006-1-12 Person.java<BR> * @author <BR> */<BR>package test.annotation.validator;<BR><BR>import org.hibernate.validator.Length;<BR>import org.hibernate.validator.Min;<BR>import org.hibernate.validator.Valid;<BR>　<BR><BR>//@Serializability&nbsp;&nbsp;//测试自定义约束<BR>public class Person {<BR><BR>&nbsp;&nbsp;private String name;<BR>&nbsp;&nbsp;private int age;<BR>&nbsp;&nbsp;private Address address;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;public Person() {}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;@Valid //注意此处<BR>&nbsp;&nbsp;public Address getAddress() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return address;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public void setAddress(Address address) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.address = address;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;@Min(value = 1)<BR>&nbsp;&nbsp;public int getAge() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return age;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public void setAge(int age) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.age = age;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;@Length(min = 4)<BR>&nbsp;&nbsp;public String getName() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return name;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public void setName(String name) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.name = name;<BR>&nbsp;&nbsp;}<BR>} </PRE><BR><BR><B>Address.java 类</B><BR><PRE class=overflow>/*<BR> * Created on 2006-1-12 Address.java<BR> * @author <BR> */<BR>package test.annotation.validator;<BR><BR>import org.hibernate.validator.Length;<BR>import org.hibernate.validator.Max;<BR>import org.hibernate.validator.Min;<BR><BR>public class Address {<BR><BR>&nbsp;&nbsp;private String street;<BR>&nbsp;&nbsp;private int num;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;public Address() {}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;@Min(value = 1)<BR>&nbsp;&nbsp;@Max(value = 100)<BR>&nbsp;&nbsp;public int getNum() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return num;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public void setNum(int num) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.num = num;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;@Length(min = 3,max = 8)<BR>&nbsp;&nbsp;public String getStreet() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return street;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public void setStreet(String street) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.street = street;<BR>&nbsp;&nbsp;}<BR>} </PRE><BR><BR>　 　 　 <BR><BR>上面是两个用 Validator Annotations 注释的 类. 每个属性都用 约束限制了.&nbsp;&nbsp;下面看看测试的类吧:<BR><BR><B>TestValidator.java 类</B><BR><BR><PRE class=overflow>/*<BR> * Created on 2006-1-12<BR> * @author icerain<BR> */<BR>package test.annotation.validator;<BR><BR>import org.hibernate.validator.ClassValidator;<BR>import org.hibernate.validator.InvalidValue;<BR><BR><BR>public class TestValidator {<BR>&nbsp;&nbsp;public void test() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;Address add = new Address();<BR>&nbsp;&nbsp;&nbsp;&nbsp;add.setNum(0);<BR>&nbsp;&nbsp;&nbsp;&nbsp;add.setStreet("1");<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;Person p = new Person();<BR>&nbsp;&nbsp;&nbsp;&nbsp;p.setAddress(add);<BR>&nbsp;&nbsp;&nbsp;&nbsp;p.setAge(0);<BR>&nbsp;&nbsp;&nbsp;&nbsp;p.setName("ice");<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;/******************Test validator ********/<BR>&nbsp;&nbsp;&nbsp;&nbsp;// 注意该处只验证了Person 为了说明 @Valid 注释的使用<BR>&nbsp;&nbsp;&nbsp;&nbsp;ClassValidator&lt;Person&gt; classValidator = new ClassValidator&lt;Person&gt; (Person.class);<BR>&nbsp;&nbsp;&nbsp;&nbsp;InvalidValue[] validMessages = classValidator.getInvalidValues(p);<BR>&nbsp;&nbsp;&nbsp;&nbsp;for (InvalidValue value : validMessages) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("InvalidValue 的长度是:" + validMessages.length<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+" . 验证消息是: " + value.getMessage() <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ " . PropertyPath 是:" + value.getPropertyPath()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+" .\n\t PropertyName 是: " +value.getPropertyName()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ "Value 是: " + value.getValue()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+" Bean 是: "+ value.getBean()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+"\n\t BeanClass 是:" + value.getBeanClass());<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;public static void main(String[] args) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;new TestValidator().test();<BR>&nbsp;&nbsp;}<BR>}</PRE><BR><BR><BR>程序的输出如下 <BR><BR>InvalidValue 的长度是:4 . 验证消息是: 必须大于等于 1 . PropertyPath 是:age .<BR><BR>PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@dd87b2<BR><BR>BeanClass 是:class test.annotation.validator.Person<BR><BR>InvalidValue 的长度是:4 . 验证消息是: 长度必须介于 4 与 2147483647 之间 . PropertyPath 是:name .<BR><BR>PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@dd87b2<BR><BR>BeanClass 是:class test.annotation.validator.Person<BR><BR>InvalidValue 的长度是:4 . 验证消息是: 必须大于等于 1 . PropertyPath 是:address.num .<BR><BR>PropertyName 是: num. Value 是: 0 Bean 是: test.annotation.validator.Address@197d257<BR><BR>BeanClass 是:class test.annotation.validator.Address<BR><BR>InvalidValue 的长度是:4 . 验证消息是: 长度必须介于 3 与 8 之间 . PropertyPath 是:address.street .<BR><BR>PropertyName 是: street. Value 是: 1 Bean 是: test.annotation.validator.Address@197d257<BR><BR>BeanClass 是:class test.annotation.validator.Address<BR><BR>可以看出不满足约束的值都被指出了.<BR><BR>同时该句: ClassValidator&lt;Person&gt; classValidator = new ClassValidator&lt;Person&gt; (Person.class);<BR><BR>我们只验证了 Person. 在Person里面的Address的属性 由于有@Valid Annotations 所以 Address的相关属性也被机联验证了 .<BR><BR><BR><BR>如果 把@Valid Annotations 去掉,结果如下:<BR><BR>InvalidValue 的长度是:2 . 验证消息是: 必须大于等于 1 . PropertyPath 是:age .<BR><BR>PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@18fef3d<BR><BR>BeanClass 是:class test.annotation.validator.Person<BR><BR>InvalidValue 的长度是:2 . 验证消息是: 长度必须介于 4 与 2147483647 之间 . PropertyPath 是:name .<BR><BR>PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@18fef3d<BR><BR>BeanClass 是:class test.annotation.validator.Person<BR><BR>可以看出 没有验证 Address.<BR><BR><BR><BR>当然了 ,你还可以只验证一个属性 , 没有必要验证整个类.只需要在调用classValidator.getInvalidValues(p,"age")方法时 加上你要验证的属性就可以了.如我们只想验证age 属性 把代码改为如下所示:<BR><BR>InvalidValue[] validMessages = classValidator.getInvalidValues(p,"age"); //只验证age 属性<BR><BR>运行结果如下:<BR><BR>InvalidValue 的长度是:1 . 验证消息是: 必须大于等于 1 . PropertyPath 是:age .<BR><BR>PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@1457cb<BR><BR>BeanClass 是:class test.annotation.validator.Person<BR><BR>只是验证了 age 属性.<BR><BR><BR><BR>怎么样 ,很简单吧. 关于 Hibernate Validator 内建的验证Annotations 大家可以看看 API 或者 参考文档(中文版我正在翻译中 请访问<A href="http://blog.matrix.org.cn/page/icess" target=_new>我的 Blog</A> 获得最新信息).<BR><BR>如果你要写自己的约束呢 , 你不用担心 ,这也是很容易的. 任何约束有两部分组成: [约束描述符 即注释]the constraint descriptor (the annotation) 和[约束validator 即 实现类] the constraint validator (the implementation class).下面我们扩展Hibernate Test suit 中的一个Test 来讲解一下.<BR><BR>首先: 要声明一个constraint descriptor .如下:<BR><BR><PRE class=overflow>package test.annotation.validator;<BR><BR>import java.lang.annotation.Documented;<BR>import static java.lang.annotation.ElementType.TYPE;<BR>import static java.lang.annotation.ElementType.FIELD;<BR>import static java.lang.annotation.ElementType.METHOD;<BR>import java.lang.annotation.Retention;<BR>import static java.lang.annotation.RetentionPolicy.RUNTIME;<BR>import java.lang.annotation.Target;<BR><BR>import org.hibernate.validator.ValidatorClass;<BR><BR>/**<BR> * Dummy sample of a bean-level validation annotation<BR> *<BR> * @author Emmanuel Bernard<BR> */<BR>@ValidatorClass(SerializabilityValidator.class)<BR>@Target({METHOD,FIELD,TYPE})<BR>@Retention(RUNTIME)<BR>@Documented<BR>public @interface Serializability {<BR>&nbsp;&nbsp;int num() default 11; <BR>&nbsp;&nbsp;String message() default "bean must be serialiable";<BR>} <BR><BR>@ValidatorClass(SerializabilityValidator.class) 指出了 constraint validator 类.<BR><BR>@Target({METHOD,FIELD,TYPE})<BR>@Retention(RUNTIME)<BR>@Documented </PRE>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR><BR>这几个我就不用解释了吧.<BR><BR>Serializability 里面声明了一个 message 显示约束的提示信息. num 只是为了说明一个方面 在这里面没有实际用途用 .<BR><BR>然后就是 实现一个constraint validator 类 该类要实现Validator&lt;ConstraintAnnotation&gt;.这里是SerializabilityValidator.java 如下:<BR><BR><PRE class=overflow>//$Id: SerializabilityValidator.java,v 1.3 2005/11/17 18:12:11 epbernard Exp $<BR>package test.annotation.validator;<BR><BR>import java.io.Serializable;<BR><BR>import org.hibernate.validator.Validator;<BR><BR>/**<BR> * Sample of a bean-level validator<BR> *<BR> * @author Emmanuel Bernard<BR> */<BR>public class SerializabilityValidator implements Validator&lt;Serializability&gt;, Serializable {<BR>&nbsp;&nbsp;public boolean isValid(Object value) {<BR>&nbsp;&nbsp; //这里只是Validator 里面的 实现验证规则的 方法. value 是要验证的值.<BR>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("IN SerializabilityValidator isValid:"+value.getClass()+": " +value.toString()); <BR>&nbsp;&nbsp;&nbsp;&nbsp;return value instanceof Serializable;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public void initialize(Serializability parameters) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;// 在这里可以 取得 constraint descriptor 里面的属性 如上面我们声明的 num <BR>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("IN SerializabilityValidator: parameters:"+ parameters.num() );<BR>&nbsp;&nbsp;}<BR>} </PRE><BR><BR>然后在你的类中应用@Serializability&nbsp;&nbsp;就可以约束一个类实现Serializable 接口了. 如下:<BR><BR>在我们的Person.java类 添加@Serializability&nbsp;&nbsp;Annotations ,把Person.java 中的 //@Serializability //测试自定义约束 注释去掉就ok了.<BR><BR>运行结果如下:<BR><BR>InvalidValue 的长度是:3 . 验证消息是: bean must be serialiable . PropertyPath 是:null .<BR><BR>PropertyName 是: null. Value 是: test.annotation.validator.Person@1a73d3c Bean 是: test.annotation.validator.Person@1a73d3c<BR><BR>BeanClass 是:class test.annotation.validator.Person<BR><BR>现在把Person类实现 java.io.Serializable 接口 则没有出现 验证错误消息.<BR><BR>消息的国际化也是很简单的,把Serializability&nbsp;&nbsp;中的message 改为以{}扩住的 属性文件的Key就可以了 <BR><BR><PRE class=overflow>public @interface Serializability {<BR>&nbsp;&nbsp;int num() default 11; <BR>&nbsp;&nbsp;String message() default "{Serializable}"; //"bean must be serialiable"; //消息的国际化<BR>}</PRE><BR><BR>然后编辑资料文件. 注意 该资源文件中要包括 Hibernate Validator 内建的资源. 可以在该org\hibernate\validator\resources 包里面的资源文件基础上修改 ,在打包里面 这样就可以了. 自己打包可能不太方便.你可以把该包里面的文件复制出来.然后放到你自己的项目包下在自己编辑, 该测试中 我是放在 test\resources 包下的.<BR><BR>然后在 资源文件中添加 Serializable = '''''' 这么一行, 样例如下:<BR><BR>#DefaultValidatorMessages.properties (DefaultValidatorMessages_zh.properties 不再列出^_^)<BR><BR>　<BR><BR>#下面是 Hibernate Validator 内建的国际化消息 <BR><BR>validator.assertFalse=assertion failed<BR><BR>validator.assertTrue=assertion failed<BR><BR>validator.future=must be a future date<BR><BR>validator.length=length must be between {min} and {max}<BR><BR>validator.max=must be less than or equal to {value}<BR><BR>validator.min=must be greater than or equal to {value}<BR><BR>validator.notNull=may not be null<BR><BR>validator.past=must be a past date<BR><BR>validator.pattern=must match "{regex}"<BR><BR>validator.range=must be between {min} and {max}<BR><BR>validator.size=size must be between {min} and {max}<BR><BR>#下面是自定义的消息<BR><BR>Serializable=Bean not Serializable&nbsp;&nbsp;//加上自己定义的国际化消息. <BR><BR>在构造ClassValidator 时要添上 资源文件 如下:(在测试类中)<BR><BR>ClassValidator&lt;Person&gt; classValidator = new ClassValidator&lt;Person&gt; (Person.class,ResourceBundle.getBundle("test.resources.DefaultValidatorMessages"));//加载资源<BR><BR>这样就可以了 .&nbsp;&nbsp;当然 你还可以 更改 Hibernate Validator 的消息(不是在上面的资源文件中直接修改validator.length = ... 等等 ) , 还记得 Validator 注释中有个 message 元素吗? 你以前用的都是默认值,现在你可以该为你自己定义的了.如:validator.length 我把他改为 "该字符串的长度不符合规定范围范围". 在资源文件中添加一行键值属性对(key定义为 "myMsg")如下:<BR><BR>myMsg=该字符串的长度不符合规定范围范围<BR><BR>并且还要在@Length 注释中提供message的引用的key 如下@Length(min = 4,message = "{myMsg}")<BR><BR>再一次运行测试 ,我们就可以看到上面两条自定义绑定的消息了 .如下:<BR><BR>InvalidValue 的长度是:3 . 验证消息是: Bean 不是 可 Serializable . PropertyPath 是:null .<BR>PropertyName 是: null. Value 是: test.annotation.validator.Person@1bd4722 Bean 是: test.annotation.validator.Person@1bd4722<BR>BeanClass 是:class test.annotation.validator.Person<BR><BR><BR>InvalidValue 的长度是:3 . 验证消息是: 该字符串的长度不符合规定范围范围 . PropertyPath 是:name .<BR>PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@1bd4722<BR>BeanClass 是:class test.annotation.validator.Person<BR><BR>怎么样,比你想象的简单吧. <BR><BR>OK 上面我们讨论了 Hibernate Validator 的主要用法: 但是 该框架有什么用呢? ^_^<BR><BR>看到这里其实不用我在多说了 大家都知道怎么用,什么时候用. 作为一篇介绍性文章我还是在此给出一个最常用的例子吧,更好的使用方式大家慢慢挖掘吧.<BR><BR>比如 : 你现在在开发一个人力资源(HR)系统 (其实是我们ERP课程的一个作业 ^_^), 里面要处理大量的数据,尤其是在输入各种资料时 如 登记员工信息. 如果你公司的员工的年龄要求是18 -- 60 那么你所输入的年龄就不能超出这个范围. 你可能会说这很容易啊 , 不用Validator就可以解决啊.这保持数据前验证就可以啦 如if ( e.getAge() &gt; 60 || e.getAge() &lt; 18 ) ........ 给出错误信息 然后提示重新输入不就OK啦 用得着 兴师动众的来个第三方框架吗? <BR><BR>是啊 当就验证这一个属性时, 没有必要啊 ! 但是一个真正的HR 系统,会只有一个属性要验证吗? 恐怕要有N多吧<BR><BR>你要是每一个都那样 写一段验证代码 是不是很烦啊 ,况且也不方便代码重用. 现在考虑一些 Validator 是不是更高效啊,拦截到 约束违例的 属性 就可以直接得到 国际化的消息 可以把该消息显示到一个弹出对话框上 提示更正&nbsp;&nbsp;!<BR><BR>Validator的用处不只这一种 ,你可以想到如何用呢 ! 欢迎发表你的高见!!<BR><BR>OK 到此 我们的 Hibernate Validator 之旅就要先告一段落了 . 希望这是令你心旷神怡的一次寒冬之旅,<BR><BR>把你学到的应用到你的项目中吧,一定会提高你的生产率的. 相信我 ,没错的&nbsp;&nbsp;^_^ !<BR><BR><BR>
<DIV class=title>
<H2>解析 Hibernate Validator</H2><A href="http://www.matrix.org.cn/user.shtml;jsessionid=3A8BC27423A1D08594878104F307B16A?userid=72718">icess</A> 发表于 2006-01-19<BR>点击数:427 评论数:0 评价:5/1<BR>关键词:Hibernate validator </DIV><!-- end of div title -->
<DIV class=summary>
<DIV class=left></DIV>
<DIV class=center>
<H4>摘要:</H4>Hibernate Validator 可以是一个独立的验证框架, 所以看完这篇分析 你可以把她独立出来作为你的个人验证框架来使用了 ^_^(如果你有兴趣和时间的话). Hibernate Validator 框架里面有两个主要的类: ClassValidator 和InvalidValue 还有一个接口Validator,在这三个主要的构件?最主要的就只有一个 那就是ClassValidator.另外两个是很好理解的.. </DIV>
<DIV class=right>
<DIV class=help>
<H4>文章工具</H4><A href="http://www.matrix.org.cn/favorite.shtml;jsessionid=3A8BC27423A1D08594878104F307B16A?type=article&amp;title=%25E8%25A7%25A3%25E6%259E%2590%2BHibernate%2BValidator&amp;url=resource%2Farticle%2F44%2F44175_Hibernate%2BValidator.html">收藏</A><BR><A href="http://www.matrix.org.cn/resource/article/44/44175_Hibernate+Validator.html#avote">投票评分</A><BR><A href="http://www.matrix.org.cn/resource/article/44/44175_Hibernate+Validator.html#areview">发表评论</A><BR><A onclick="copyLink('解析 Hibernate Validator');" href="http://www.matrix.org.cn/resource/article/44/44175_Hibernate+Validator.html#">复制链接</A><BR></DIV></DIV></DIV><!-- end of summary line -->
<DIV class=overflow id=text>在前一篇文章 &lt; Hibernate Validator 简介 &gt; <A href="http://www.matrix.org.cn/resource/article/44/44153_Hibernate%20Validator%20.html" target=_new>http://www.matrix.org.cn/resource/article/44/44153_Hibernate%20Validator%20.html</A>中,我们看到了Hibernate Validator的使用方法,和自定义验证Annotation的实现以及错误消息的国际化等常见问题. <BR><BR><SPAN style="COLOR: red">任何获得Matrix授权的网站，转载请保留以下作者信息和链接：</SPAN><BR>作者：icess(作者的blog:<A href="http://blog.matrix.org.cn/page/icess" target=_new>http://blog.matrix.org.cn/page/icess</A>)<BR>关键字：Hibernate Validator <BR><BR>在使用如此优雅的属性验证框架的同时,你是否想了解她的细节呢?她究竟是怎么实现的呢? 那么现在就跟随我来探探她的内核吧!<BR><BR>Hibernate Validator 可以是一个独立的验证框架, 所以看完这篇分析 你可以把她独立出来作为你的个人验证框架来使用了 ^_^(如果你有兴趣和时间的话). Hibernate Validator 框架里面有两个主要的类: ClassValidator 和InvalidValue 还有一个接口Validator,在这三个主要的构件中 最主要的就只有一个 那就是ClassValidator.另外两个是很好理解的..<BR><BR>现在就让我们开始吧. 遵循由浅入深的习惯 我们先看看 Validator 接口吧. 其代码如下:<BR><BR><PRE class=overflow>import java.lang.annotation.Annotation;<BR><BR>/**<BR> * A constraint validator for a particular annotation<BR> *<BR> * @author Gavin King<BR> */<BR>public interface Validator&lt;A extends Annotation&gt; {<BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * does the object/element pass the constraints<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;public boolean isValid(Object value);<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * Take the annotations values<BR>&nbsp;&nbsp; * @param parameters<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;public void initialize(A parameters);<BR>}</PRE><BR><BR>Validator接口就是我们自定义约束的实现类要继承的接口,该接口在&lt; Hibernate Validator 简介 &gt; <A href="http://www.matrix.org.cn/resource/article/44/44153_Hibernate%20Validator%20.html" target=_new>http://www.matrix.org.cn/resource/article/44/44153_Hibernate%20Validator%20.html</A>中已经讨论过了,请参考.<BR><BR>InvalidValue 类 大家看名字就应该可以猜到她的作用了吧. 她就是代表一个没有通过验证的错误实例.该类定义了一些方法,通过这些方法你可以取得与该Validator Annotation 有关的一些参数,如:她所注释的属性的值,错误消息等等. 该类的源代码如下:<BR><BR><PRE class=overflow>import java.io.Serializable;<BR><BR>/**<BR> * A single violation of a class level or method level constraint.<BR> *<BR> * @author Gavin King<BR> */<BR>public class InvalidValue implements Serializable {<BR>&nbsp;&nbsp;private final String message;<BR>&nbsp;&nbsp;private final Object value;<BR>&nbsp;&nbsp;private final String propertyName;<BR>&nbsp;&nbsp;private final Class beanClass;<BR>&nbsp;&nbsp;private final Object bean;<BR>&nbsp;&nbsp;private Object rootBean;<BR><BR>&nbsp;&nbsp;public Object getRootBean() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return rootBean;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public String getPropertyPath() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return propertyPath;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;private String propertyPath;<BR><BR>&nbsp;&nbsp;public InvalidValue(String message, Class beanClass, String propertyName, Object value, Object bean) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.message = message;<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.value = value;<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.beanClass = beanClass;<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.propertyName = propertyName;<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.bean = bean;<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.rootBean = bean;<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.propertyPath = propertyName;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public void addParentBean(Object parentBean, String propertyName) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.rootBean = parentBean;<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.propertyPath = propertyName + "." + this.propertyPath;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public Class getBeanClass() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return beanClass;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public String getMessage() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return message;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public String getPropertyName() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return propertyName;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public Object getValue() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return value;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public Object getBean() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return bean;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public String toString() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return propertyName + ' ' + message;<BR>&nbsp;&nbsp;}<BR><BR>} </PRE><BR><BR>然后,就让我们看看最主要的类吧:ClassValidator . 该类代码有400余行,我都做了详细的注释如下:<BR><BR>import 该部分省略了;<BR><BR><BR><PRE class=overflow>/**<BR> * Engine that take a bean and check every expressed annotation restrictions<BR> *<BR> * @author Gavin King<BR> */<BR>public class ClassValidator&lt;T&gt; implements Serializable {<BR>&nbsp;&nbsp;private static Log log = LogFactory.getLog( ClassValidator.class );<BR>&nbsp;&nbsp;private static final InvalidValue[] EMPTY_INVALID_VALUE_ARRAY = new InvalidValue[]{};<BR>&nbsp;&nbsp;private final Class&lt;T&gt; beanClass;<BR>&nbsp;&nbsp;private transient ResourceBundle messageBundle;<BR>&nbsp;&nbsp;private transient boolean defaultResourceBundle;<BR><BR>&nbsp;&nbsp;private final transient Map&lt;Class, ClassValidator&gt; childClassValidators;<BR>&nbsp;&nbsp;private transient List&lt;Validator&gt; beanValidators;<BR>&nbsp;&nbsp;private transient List&lt;Validator&gt; memberValidators;<BR>&nbsp;&nbsp;private transient List&lt;Member&gt; memberGetters;<BR>&nbsp;&nbsp;private transient Map&lt;Validator, String&gt; messages;<BR>&nbsp;&nbsp;private transient List&lt;Member&gt; childGetters;<BR>&nbsp;&nbsp;private static final String DEFAULT_VALIDATOR_MESSAGE = "org.hibernate.validator.resources.DefaultValidatorMessages";<BR><BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * create the validator engine for this bean type<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;public ClassValidator(Class&lt;T&gt; beanClass) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;this( beanClass, null );<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * create the validator engine for a particular bean class, using a resource bundle<BR>&nbsp;&nbsp; * for message rendering on violation<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;public ClassValidator(Class&lt;T&gt; beanClass, ResourceBundle resourceBundle) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;this( beanClass, resourceBundle, new HashMap&lt;Class, ClassValidator&gt;() );<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;protected ClassValidator(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&lt;T&gt; beanClass, ResourceBundle resourceBundle, Map&lt;Class, ClassValidator&gt; childClassValidators<BR>&nbsp;&nbsp;) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.beanClass = beanClass;<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.messageBundle = resourceBundle == null ?<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getDefaultResourceBundle() :<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resourceBundle;<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.childClassValidators = childClassValidators;<BR>&nbsp;&nbsp;&nbsp;&nbsp;initValidator( beanClass, childClassValidators, this.messageBundle );&nbsp;&nbsp;//重要的是该初始化函数<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;private ResourceBundle getDefaultResourceBundle() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;ResourceBundle rb;<BR>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rb = ResourceBundle.getBundle( "ValidatorMessages" );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;catch( MissingResourceException e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//the user did not override the default ValidatorMessages<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.debug( "ResourceBundle ValidatorMessages not found. Delegate to " + DEFAULT_VALIDATOR_MESSAGE);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rb = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;defaultResourceBundle = true;<BR>&nbsp;&nbsp;&nbsp;&nbsp;return rb;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;private void initValidator(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&lt;T&gt; beanClass, Map&lt;Class, ClassValidator&gt; childClassValidators,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ResourceBundle resourceBundle<BR>&nbsp;&nbsp;) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;beanValidators = new ArrayList&lt;Validator&gt;(); // 保存类级别的验证约束实现类<BR>&nbsp;&nbsp;&nbsp;&nbsp;memberValidators = new ArrayList&lt;Validator&gt;(); // 保存方法级别的验证约束实现类<BR>&nbsp;&nbsp;&nbsp;&nbsp;memberGetters = new ArrayList&lt;Member&gt;();// 保存类的成员(字段or方法)和构造函数方法的标识信息<BR>&nbsp;&nbsp;&nbsp;&nbsp;messages = new HashMap&lt;Validator, String&gt;(); // 利用Map保存与每个Validator相对应的验证消息<BR>&nbsp;&nbsp;&nbsp;&nbsp;childGetters = new ArrayList&lt;Member&gt;();//&nbsp;&nbsp;保存子类的成员(字段or方法)和构造函数方法的标识信息<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;childClassValidators.put( beanClass, this ); //map Map&lt;Class, ClassValidator&gt; childClassValidators;<BR>&nbsp;&nbsp;&nbsp;&nbsp;Annotation[] classAnnotations = beanClass.getAnnotations();<BR>&nbsp;&nbsp;&nbsp;&nbsp;for ( int i = 0; i &lt; classAnnotations.length ; i++ ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Annotation classAnnotation = classAnnotations[i];<BR>&nbsp;&nbsp;&nbsp;&nbsp; Validator beanValidator = createValidator( classAnnotation );//根据Annotation来得到Validator,参考对该函数的解释<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( beanValidator != null ) beanValidators.add( beanValidator );//保存该Validator<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;//build the class hierarchy to look for members in<BR>&nbsp;&nbsp;&nbsp;&nbsp;Collection&lt;Class&gt; classes = new HashSet&lt;Class&gt;();<BR>&nbsp;&nbsp;&nbsp;&nbsp;addSuperClassesAndInterfaces( beanClass, classes );//把beanClass的所有超类和实现的接口添加的集合classes中<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;//Check on all selected classes<BR>&nbsp;&nbsp;&nbsp;&nbsp;for ( Class currClass : classes ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Method[] methods = currClass.getDeclaredMethods();// 扫描Method上面的注释<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for ( int i = 0; i &lt; methods.length ; i++ ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Method method = methods[i];<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;createMemberValidator( method ); // 创建方法上的约束实现类(Validator), 参考对该函数的解释<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class clazz = method.getReturnType();// 得到该方法的返回类型<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;createChildValidator( resourceBundle, method, clazz );// 创建子类的Validator<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Field[] fields = currClass.getDeclaredFields(); // 扫描Field上面的注释, 下面和上面Method的实现一样<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for ( int i = 0; i &lt; fields.length ; i++ ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Field field = fields[i];<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;createMemberValidator( field );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class clazz = field.getType();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;createChildValidator( resourceBundle, field, clazz );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;private void addSuperClassesAndInterfaces(Class clazz, Collection&lt;Class&gt; classes) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;for ( Class currClass = clazz; currClass != null ; currClass = currClass.getSuperclass() ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( ! classes.add( currClass ) ) return;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class[] interfaces = currClass.getInterfaces();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (Class interf : interfaces) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addSuperClassesAndInterfaces( interf, classes );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * 创建内嵌类的Validator. 如果该内嵌类被Valid Annotation 注释的话则 <BR>&nbsp;&nbsp; * 创建另外一个ClassValidator<BR>&nbsp;&nbsp; * @param resourceBundle<BR>&nbsp;&nbsp; * @param member<BR>&nbsp;&nbsp; * @param clazz<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;private void createChildValidator(ResourceBundle resourceBundle, Member member, Class clazz) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;if ( ( (AnnotatedElement) member ).isAnnotationPresent( Valid.class ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setAccessible( member );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;childGetters.add( member );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( !childClassValidators.containsKey( clazz ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new ClassValidator( clazz, resourceBundle, childClassValidators );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * 利用传入的Method(实现了AnnotatedElement, GenericDeclaration, Member接口)<BR>&nbsp;&nbsp; * 得到 方法上的Annotations 然后利用私有方法createValidator(Annotation a)来创建<BR>&nbsp;&nbsp; * 每一个Annotation 的实现类 Validator 并保存Validator和member<BR>&nbsp;&nbsp; * @param member<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;private void createMemberValidator(Member member) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;Annotation[] memberAnnotations = ( (AnnotatedElement) member ).getAnnotations();<BR>&nbsp;&nbsp;&nbsp;&nbsp;for ( int j = 0; j &lt; memberAnnotations.length ; j++ ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Annotation methodAnnotation = memberAnnotations[j];<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Validator propertyValidator = createValidator( methodAnnotation );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( propertyValidator != null ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;memberValidators.add( propertyValidator );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setAccessible( member ); // 设置访问属性<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;memberGetters.add( member );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;private static void setAccessible(Member member) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;if ( !Modifier.isPublic( member.getModifiers() ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( (AccessibleObject) member ).setAccessible( true );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * 该方法产生了该Annotation的约束实现类 并初始化该类对应的消息<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;private Validator createValidator(Annotation annotation) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//得到ValidatorClass Annotation <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ValidatorClass validatorClass = annotation.annotationType().getAnnotation( ValidatorClass.class );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( validatorClass == null ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 然后 利用ValidatorClass Annotation 来得到里面的值(即实现该注释的Class),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//再利用Class 构造一个instance<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Validator beanValidator = validatorClass.value().newInstance();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;beanValidator.initialize( annotation ); // 初始化Annotation中的参数(注意:在自定义约束中该方法有你来实现)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String messageTemplate = (String) annotation.getClass()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getMethod( "message", (Class[]) null )<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.invoke( annotation );&nbsp;&nbsp;// 取得 constraint descriptor&nbsp;&nbsp;中的message 的值<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String message = replace( messageTemplate, annotation ); // 初始化取得的模板消息 请参考 replace函数<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;messages.put( beanValidator, message ); // 把message 放在map中,以便使用<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return beanValidator; // 返回 产生的Validator<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;catch (Exception e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new IllegalArgumentException( "could not instantiate ClassValidator", e );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public boolean hasValidationRules() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return beanValidators.size() != 0 || memberValidators.size() != 0;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * apply constraints on a bean instance and return all the failures.<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;public InvalidValue[] getInvalidValues(T bean) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;return this.getInvalidValues( bean, new IdentitySet() );<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * apply constraints on a bean instance and return all the failures.<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;protected InvalidValue[] getInvalidValues(T bean, Set&lt;Object&gt; circularityState) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;if ( circularityState.contains( bean ) ) {&nbsp;&nbsp;// 该if else 是和Hibernate Core由关的,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return EMPTY_INVALID_VALUE_ARRAY; //Avoid circularity<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;else {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;circularityState.add( bean );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;if ( !beanClass.isInstance( bean ) ) { // 如果beanClass不是该bean的实例,则抛出异常<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new IllegalArgumentException( "not an instance of: " + bean.getClass() );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;List&lt;InvalidValue&gt; results = new ArrayList&lt;InvalidValue&gt;();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;for ( int i = 0; i &lt; beanValidators.size() ; i++ ) { // 验证类级别的约束<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Validator validator = beanValidators.get( i );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( !validator.isValid( bean ) ) { //调用isValid方法,如果没有通过则添加到list&lt;InvalidValue&gt;中<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;//如果是自定义约束则isValid方法 由你来实现<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;results.add( new InvalidValue( messages.get( validator ), beanClass, null, bean, bean ) );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;for ( int i = 0; i &lt; memberValidators.size() ; i++ ) {//验证方法级别的约束<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Member getter = memberGetters.get( i );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( Hibernate.isPropertyInitialized(bean, getter.getName() ) ) {// ? 检查该属性是否已初始化<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object value = getMemberValue( bean, getter );//利用反射 取得该属性的值<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Validator validator = memberValidators.get( i ); //取得该约束的验证实现类<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( !validator.isValid( value ) ) {//调用isValid方法,如果没有通过则添加到list&lt;InvalidValue&gt;中<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String propertyName = getPropertyName( getter );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;results.add( new InvalidValue( messages.get( validator ), beanClass, propertyName, value, bean ) );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;for ( int i = 0; i &lt; childGetters.size() ; i++ ) {// 处理子类类<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Member getter = childGetters.get( i );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( Hibernate.isPropertyInitialized(bean, getter.getName() ) ) { //检查该属性是否已初始化<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object value = getMemberValue( bean, getter );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( value != null &amp;&amp; Hibernate.isInitialized( value ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String propertyName = getPropertyName( getter );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InvalidValue[] invalidValues = getClassValidator( value )<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getInvalidValues( value, circularityState );// 通过参数value 得到 Class, 然后由Class作为key&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//在childClassValidators map中得到其ClassValidator<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; //如果不存在 则创建新的 ,然后再调用ClassValidator的getInvalidValues方法<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 注意在调用getInvalidValues方法时 用到了circularityState 参数, 当调用循环一周时 返回(递归结束)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for ( InvalidValue invalidValue : invalidValues ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;invalidValue.addParentBean( bean, propertyName );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;results.add( invalidValue ); //添加的结果中<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;return results.toArray( new InvalidValue[results.size()] ); //返回InvalidValue数组<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * 通过参数value 得到 Class, 然后由Class作为key 在childClassValidators map中得到其ClassValidator<BR>&nbsp;&nbsp; * 如果不存在 则创建新的 然后返回<BR>&nbsp;&nbsp; * @param value<BR>&nbsp;&nbsp; * @return<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;private ClassValidator getClassValidator(Object value) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;Class clazz = value.getClass();<BR>&nbsp;&nbsp;&nbsp;&nbsp;ClassValidator validator = childClassValidators.get( clazz );<BR>&nbsp;&nbsp;&nbsp;&nbsp;if ( validator == null ) { //handles polymorphism<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;validator = new ClassValidator( clazz );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;return validator;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * Apply constraints of a particular property on a bean instance and return all the failures.<BR>&nbsp;&nbsp; * Note this is not recursive.<BR>&nbsp;&nbsp; * 验证单个属性的约束.<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;//TODO should it be recursive ?<BR>&nbsp;&nbsp;public InvalidValue[] getInvalidValues(T bean, String propertyName) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;List&lt;InvalidValue&gt; results = new ArrayList&lt;InvalidValue&gt;();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;for ( int i = 0; i &lt; memberValidators.size() ; i++ ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Member getter = memberGetters.get( i );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( getPropertyName( getter ).equals( propertyName ) ) {// 验证该属性的约束<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object value = getMemberValue( bean, getter );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Validator validator = memberValidators.get( i );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( !validator.isValid( value ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;results.add( new InvalidValue( messages.get( validator ), beanClass, propertyName, value, bean ) );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;return results.toArray( new InvalidValue[results.size()] );<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * Apply constraints of a particular property value of a bean type and return all the failures.<BR>&nbsp;&nbsp; * The InvalidValue objects returns return null for InvalidValue#getBean() and InvalidValue#getRootBean()<BR>&nbsp;&nbsp; * Note this is not recursive.<BR>&nbsp;&nbsp; * 验证 value 是否满足当前属性的约束.<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;//TODO should it be recursive?<BR>&nbsp;&nbsp;public InvalidValue[] getPotentialInvalidValues(String propertyName, Object value) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;List&lt;InvalidValue&gt; results = new ArrayList&lt;InvalidValue&gt;();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;for ( int i = 0; i &lt; memberValidators.size() ; i++ ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Member getter = memberGetters.get( i );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( getPropertyName( getter ).equals( propertyName ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Validator validator = memberValidators.get( i );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( !validator.isValid( value ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;results.add( new InvalidValue( messages.get( validator ), beanClass, propertyName, value, null ) );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;return results.toArray( new InvalidValue[results.size()] );<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;private Object getMemberValue(T bean, Member getter) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;Object value;<BR>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value = getValue( getter, bean );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;catch (Exception e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new IllegalStateException( "Could not get property value", e );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;return value;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;private Object getValue(Member member, T bean) throws IllegalAccessException, InvocationTargetException {<BR>&nbsp;&nbsp;&nbsp;&nbsp;if ( member instanceof Field ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ( (Field) member ).get( bean );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;else if ( member instanceof Method ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ( (Method) member ).invoke( bean );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;else {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new AssertionFailure( "Unexpected member: " + member.getClass().getName() );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;public String getPropertyName(Member member) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;//Do no try to cache the result in a map, it's actually much slower (2.x time)<BR>&nbsp;&nbsp;&nbsp;&nbsp;String propertyName;<BR>&nbsp;&nbsp;&nbsp;&nbsp;if ( member instanceof Field ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;propertyName = member.getName();<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;else if ( member instanceof Method ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;propertyName = member.getName();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( propertyName.startsWith( "is" ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;propertyName = Introspector.decapitalize( propertyName.substring( 2 ) );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( propertyName.startsWith( "get" ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;propertyName = Introspector.decapitalize( propertyName.substring( 3 ) );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//do nothing for non getter method, in case someone want to validate a PO Method<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;else {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new AssertionFailure( "Unexpected member: " + member.getClass().getName() );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;return propertyName;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;private String replace(String message, Annotation parameters) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;StringTokenizer tokens = new StringTokenizer( message, "{}", true );<BR>&nbsp;&nbsp;&nbsp;&nbsp;StringBuilder buf = new StringBuilder( 30 );<BR>&nbsp;&nbsp;&nbsp;&nbsp;boolean escaped = false;<BR>&nbsp;&nbsp;&nbsp;&nbsp;while ( tokens.hasMoreTokens() ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String token = tokens.nextToken();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( "{".equals( token ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;escaped = true;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( "}".equals( token ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;escaped = false;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( !escaped ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf.append( token );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Method member;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;member = parameters.getClass().getMethod( token, (Class[]) null );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (NoSuchMethodException nsfme) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;member = null;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( member != null ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf.append( member.invoke( parameters ) );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (Exception e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new IllegalArgumentException( "could not render message", e );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( messageBundle != null ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String string = messageBundle.getString( token );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( string != null ) buf.append( replace( string, parameters ) );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;return buf.toString();<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * apply the registred constraints rules on the hibernate metadata (to be applied on DB schema...)<BR>&nbsp;&nbsp; *该方法是与实体类绑定的 不推荐使用 有兴趣的读者可以自己研究一下<BR>&nbsp;&nbsp; * @param persistentClass hibernate metadata<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;public void apply(PersistentClass persistentClass) {&nbsp;&nbsp;<BR>&nbsp;&nbsp; //源代码省略<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * 断言该bean 是否符合所有约束. 负责抛出异常<BR>&nbsp;&nbsp; * @param bean<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;public void assertValid(T bean) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;InvalidValue[] values = getInvalidValues( bean );<BR>&nbsp;&nbsp;&nbsp;&nbsp;if ( values.length &gt; 0 ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new InvalidStateException( values );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * 该方法应该是序列化ResourceBundle的 为private方法 但并没有用到, 不知道为什么 可能以后会有用<BR>&nbsp;&nbsp; * @param oos<BR>&nbsp;&nbsp; * @throws IOException<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;private void writeObject(ObjectOutputStream oos) throws IOException {<BR>&nbsp;&nbsp;&nbsp;&nbsp;ResourceBundle rb = messageBundle;<BR>&nbsp;&nbsp;&nbsp;&nbsp;if ( rb != null &amp;&amp; ! ( rb instanceof Serializable ) ) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;messageBundle = null;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( ! defaultResourceBundle )<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.warn( "Serializing a ClassValidator with a not serializable ResourceBundle: ResourceBundle ignored" );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;oos.defaultWriteObject();<BR>&nbsp;&nbsp;&nbsp;&nbsp;oos.writeObject( messageBundle );<BR>&nbsp;&nbsp;&nbsp;&nbsp;messageBundle = rb;<BR>&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;/**<BR>&nbsp;&nbsp; * 该方法应该是读取序列化的ResourceBundle的 为private方法 但并没有用到,不知道为什么 可能以后会有用<BR>&nbsp;&nbsp; * @param ois<BR>&nbsp;&nbsp; * @throws IOException<BR>&nbsp;&nbsp; * @throws ClassNotFoundException<BR>&nbsp;&nbsp; */<BR>&nbsp;&nbsp;private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {<BR>&nbsp;&nbsp;&nbsp;&nbsp;ois.defaultReadObject();<BR>&nbsp;&nbsp;&nbsp;&nbsp;ResourceBundle rb = (ResourceBundle) ois.readObject();<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (rb == null) rb = getDefaultResourceBundle();<BR>&nbsp;&nbsp;&nbsp;&nbsp;initValidator( beanClass, new HashMap&lt;Class, ClassValidator&gt;(), rb );<BR>&nbsp;&nbsp;}<BR>} </PRE><BR><BR>还记得我们在验证时候所写的代码吗:<BR><BR><PRE class=overflow> ClassValidator&lt;Person&gt; classValidator = new ClassValidator&lt;Person&gt; (Person.class);<BR> InvalidValue[] validMessages = classValidator.getInvalidValues(p);</PRE><BR><BR>只调用了classValidator的getInvalidValues(p);方法 我们就得到了InvalidValue[] validMessages, 该方法做了什么事情呢? 有上面的注释看起来就轻松多了 ^_^.<BR><BR>首先:在你创建ClassValidator时, 会调用ClassValidator的构造方法 她一供有三个构造函数 :<BR>&nbsp;&nbsp;<BR><BR>有两个构造函数(一个传递要验证的类为参数,一个还要加上你自定义的ResourceBundle)来供我们使用, 还有一个protected 的构造函数. 在构造函数中都做了写什么呢? <BR><BR>第一: 把要验证的类保存起来,第二:决定使用的消息资源,如果你提供了自己的ResourceBundle 就使用自定义消息,否则使用默认的消息资源.第三: 根据java反射机制,利用Annotation初始化所有的验证约束类,然后验证是否满足验证条件.<BR><BR>下面我们来关注一下initValidator 方法,看看是如何初始化验证约束类的. 现在请仔细看看 initValidator 里面的注释.然后在继续往下看.^_^<BR><BR>通过上面的分析,可以看到在 initValidator函数中,初始化了你传入类的所有的约束Annotations 的相关的东东(如: 其约束验证实现类, 如果有内嵌的类,如果该类被Valid Annotation注释的话 也构造一个内嵌类的Validator 并初始化其相关的东东 如此递归执行下去).该函数执行完后,可以说构造了一个以你传入的类为跟的 约束注释树(自创的名词,不了解也没关系 ^_^),然后由此树来逐个验证没有个约束.此时已经具备验证约束的条件了.你只有调用classValidator.getInvalidValues(p)方法就可以验证类p 上的所有约束了.<BR><BR>GetInvalidValues()方法有做了什么呢, 现在你要再回到上面看看她的注释了&nbsp;&nbsp;^_^:<BR><BR>怎么样现在知道 GetInvalidValues 做了什么了吧.她就是取出 约束注释树中的每一个约束注释(分为 类注释, 方法注释, 属性注释 和内嵌类注释 (也就是类里面的类属性)),并验证相应的成员是否满足该约束注释的要求,也就是调用Validator的isValid() 方法.最后用不满足要求的 成员信息构造InvalidValue 数组 并返. ClassValidator 类我们基本上已经讲解完了,剩下的该Validatro里面的就是一些内建的约束Annotation和约束验证实现类了,这些看看前一篇文章就明白怎么回事了.到此 HibernateValidator 框架基本上分析完了. 通过分析该框架.让我看到了Annotation的一种高级用法的实现机制,和反射机制的巧妙应用,以及几个巧妙的设计模式(我就不在举例了 大家可以相互探讨一下). 你从中学到了什么呢?<BR><BR>对想把Hibernate Validator做成一个独立框架的几点说明:<BR><BR>1.去掉apply 函数.<BR><BR>2. 在getPropertyName 和 getMemberValue 中 如果得到的值为null 则抛出org.hibernate.AssertionFailure异常. 可以重写该异常,或者从Hibernate源代码中提取(建议重写).<BR><BR>3.用到了Hibernate.isPropertyInitialized(Object o,String name)方法 判断该类(o)的属性(name)是否以及加载的, 该函数的doc 注释为 Check if the property is initialized. If the named property does not exist or is not persistent, this method always returns true.可以替换为判断该属性(name)是否为null, null即代表没有赋初值(可能违反约束);否则验证该值是否违反约束.<BR><BR>4.里面还用到了org.hibernate.util.IdentitySet 一个set实现,可以自己实现或者从Hibernate中提取(推荐提取);<BR><BR>这样一个独立的Validation frameWork 就出来了. 不依赖任何第三方代码,完全可以作为你自己的验证框架在项目中使用.<BR><BR>资源：<BR>PS: 关于在实体类上(持久化层)使用Validator是否有好处,<BR>大家可以看看:<A href="http://www.hibernate.org.cn/viewtopic.php?t=18131" target=_new>http://www.hibernate.org.cn/viewtopic.php?t=18131</A><BR></DIV></DIV><img src ="http://www.blogjava.net/faithwind/aggbug/29574.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-02-05 10:17 <a href="http://www.blogjava.net/faithwind/articles/29574.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在Java中读取Excel文件的内容</title><link>http://www.blogjava.net/faithwind/articles/28196.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Mon, 16 Jan 2006 07:34:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/28196.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/28196.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/28196.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/28196.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/28196.html</trackback:ping><description><![CDATA[原作者：SonyMusic<BR>在Java中读取Excel文件的内容<BR>在这里，我使用的是一个叫Java Excel API的东西，类似的还有jakarta的POI，不过感觉那个<BR>太复杂了点儿。而且jxl对中文的支持相当的好，至少我在用的过程中一点问题没出。<BR><BR>一、下载地址<BR>http://www.andykhan.com/jexcelapi/<BR><BR>二、特性<BR>可以读取Excel 95, 97, 2000文件<BR>可以读或写Excel 97及其以后版本的的公式（不过我发现好像有bug）<BR>生成Excel 97格式的电子表格<BR>支持字体、数字和日期格式化<BR>支持单元格的颜色和阴影<BR>可以编辑现有的文件 <BR><BR>三、读文件<BR>//声明一下，记得后面要关闭哦。。<BR>Workbook workbook = null;<BR><BR>try {<BR>&nbsp;&nbsp; workbook = Workbook.getWorkbook(new File("d:\\temp\\TestRead.xls"));<BR>} catch (Exception e) {<BR>&nbsp;&nbsp; throw new Exception("file to import not found!");<BR>}<BR><BR>Sheet sheet = workbook.getSheet(0);<BR>Cell cell = null;<BR><BR>int columnCount=3;<BR>int rowCount=sheet.getRows();<BR>for (int i = 0; i &lt;rowCount; i++) {<BR>&nbsp;&nbsp; for (int j = 0; j &lt;columnCount; j++) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //注意，这里的两个参数，第一个是表示列的，第二才表示行<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cell=sheet.getCell(j, i);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //要根据单元格的类型分别做处理，否则格式化过的内容可能会不正确<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(cell.getType()==CellType.NUMBER){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.print(((NumberCell)cell).getValue());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if(cell.getType()==CellType.DATE){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.print(((DateCell)cell).getDate());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.print(cell.getContents());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //System.out.print(cell.getContents());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.print("\t");<BR>&nbsp;&nbsp; }<BR>&nbsp;&nbsp; System.out.print("\n");<BR>}<BR>//关闭它，否则会有内存泄露<BR>workbook.close();<BR><BR><BR>写：wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww<BR>在Java中向Excel文件写入内容<BR><BR><BR>四、导出数据到Excel文件中<BR>下面的例子，设置了数字、日期的格式，还有字体，颜色等。<BR><BR>File tempFile=new File("d:/temp/output.xls");<BR>WritableWorkbook workbook = Workbook.createWorkbook(tempFile);<BR>WritableSheet sheet = workbook.createSheet("TestCreateExcel", 0); <BR><BR>//一些临时变量，用于写到excel中<BR>Label l=null;<BR>jxl.write.Number n=null;<BR>jxl.write.DateTime d=null;<BR><BR>//预定义的一些字体和格式，同一个Excel中最好不要有太多格式<BR>WritableFont headerFont = new WritableFont(WritableFont.ARIAL, 12, WritableFont.BOLD, false, Underlinestyle.NO_UNDERLINE, jxl.format.Colour.BLUE); <BR>WritableCellFormat headerFormat = new WritableCellFormat (headerFont); <BR><BR>WritableFont titleFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false, Underlinestyle.NO_UNDERLINE, jxl.format.Colour.RED); <BR>WritableCellFormat titleFormat = new WritableCellFormat (titleFont); <BR><BR>WritableFont detFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false, Underlinestyle.NO_UNDERLINE, jxl.format.Colour.BLACK); <BR>WritableCellFormat detFormat = new WritableCellFormat (detFont); <BR><BR>NumberFormat nf=new NumberFormat("0.00000");&nbsp;&nbsp;//用于Number的格式<BR>WritableCellFormat priceFormat = new WritableCellFormat (detFont, nf); <BR><BR>DateFormat df=new DateFormat("yyyy-MM-dd");//用于日期的<BR>WritableCellFormat dateFormat = new WritableCellFormat (detFont, df); <BR><BR>//剩下的事情，就是用上面的内容和格式创建一些单元格，再加到sheet中<BR>l=new Label(0, 0, "用于测试的Excel文件", headerFormat);<BR>sheet.addCell(l);<BR><BR>//add Title<BR>int column=0;<BR>l=new Label(column++, 2, "标题", titleFormat);<BR>sheet.addCell(l);<BR>l=new Label(column++, 2, "日期", titleFormat);<BR>sheet.addCell(l);<BR>l=new Label(column++, 2, "货币", titleFormat);<BR>sheet.addCell(l);<BR>l=new Label(column++, 2, "价格", titleFormat);<BR>sheet.addCell(l);<BR><BR>//add detail<BR>int i=0;<BR>column=0;<BR>l=new Label(column++, i+3, "标题 "+i, detFormat);<BR>sheet.addCell(l);<BR>d=new DateTime(column++, i+3, new java.util.Date(), dateFormat);<BR>sheet.addCell(d);<BR>l=new Label(column++, i+3, "CNY", detFormat);<BR>sheet.addCell(l);<BR>n=new jxl.write.Number(column++, i+3, 5.678, priceFormat);<BR>sheet.addCell(n);<BR><BR>i++;<BR>column=0;<BR>l=new Label(column++, i+3, "标题 "+i, detFormat);<BR>sheet.addCell(l);<BR>d=new DateTime(column++, i+3, new java.util.Date(), dateFormat);<BR>sheet.addCell(d);<BR>l=new Label(column++, i+3, "SGD", detFormat);<BR>sheet.addCell(l);<BR>n=new jxl.write.Number(column++, i+3, 98832, priceFormat);<BR>sheet.addCell(n);<BR><BR>//设置列的宽度<BR>column=0;<BR>sheet.setColumnView(column++, 20);<BR>sheet.setColumnView(column++, 20);<BR>sheet.setColumnView(column++, 10);<BR>sheet.setColumnView(column++, 20);<BR><BR>workbook.write();<BR>workbook.close();<BR><BR><BR>要往xls文件里面写入数据的时候需要注意的是第一要新建一个xls文件 <BR>OutputStream os=new FileOutputStream("c:\\excel2.xls"); <BR><BR>再建完这个文件的时候再建立工作文件 <BR>jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(os)); <BR><BR>如果这个文件已经存在,那么我们可以在这个文件里面加入一个sheet为了和以前的数据进行分开; <BR>jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0); <BR>在createSheet方法里前面的参数是sheet名，后面是要操作的sheet号 <BR><BR>接下来就可以往这个文件里面写入数据了 <BR><BR><BR>写入数据的时候注意的格式 <BR><BR><BR>（1）添加的字体样式 <BR>jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true); <BR>WritableFont()方法里参数说明： <BR>这个方法算是一个容器，可以放进去好多属性 <BR>第一个: TIMES是字体大小，他写的是18 <BR>第二个: BOLD是判断是否为斜体,选择true时为斜体 <BR>第三个: ARIAL <BR>第四个: UnderlineStyle.NO_UNDERLINE 下划线 <BR>第五个: jxl.format.Colour.RED 字体颜色是红色的 <BR><BR>jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf); <BR><BR>jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell"，wcfF); <BR>ws.addCell(labelC); <BR>在Label()方法里面有三个参数 <BR>第一个是代表列数, <BR>第二是代表行数， <BR>第三个代表要写入的内容 <BR>第四个是可选项，是输入这个label里面的样式 <BR>然后通过写sheet的方法addCell（）把内容写进sheet里面。 <BR><BR>（2）添加带有formatting的Number对象 <BR>jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##"); <BR><BR><BR>（3）添加Number对象 <BR>（3.1）显示number对象数据的格式 <BR><BR>jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##"); <BR>jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf); <BR><BR>jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN); <BR>ws.addCell(labelNF); <BR>Number()方法参数说明: <BR>前两上表示输入的位置 <BR>第三个表示输入的内容 <BR><BR><BR>（4）添加Boolean对象 <BR>jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false); <BR>ws.addCell(labelB); <BR><BR><BR>（5）添加DateTime对象 <BR>jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date()); <BR>ws.addCell(labelDT); <BR>DateTime()方法的参数说明 <BR>前两个表示输入的位置 <BR>第三个表示输入的当前时间 <BR><BR><BR>（6）添加带有formatting的DateFormat对象 <BR>这个显示当前时间的所有信息，包括年月日小时分秒 <BR>jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss"); <BR>jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df); <BR>jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF); <BR>ws.addCell(labelDTF); <BR><BR>（7）添加带有字体颜色Formatting的对象 <BR>jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.RED); <BR>jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc); <BR><BR>import="jxl.format.* <BR>jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL,20,WritableFont.BOLD,false,UnderlineStyle.NO_UNDERLINE,jxl.format.Colour.GREEN); <BR><BR>（8）设置单元格样式 <BR><BR>jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc); <BR>wcfFC.setBackGround(jxl.format.Colour.RED);//设置单元格的颜色为红色 <BR>wcfFC = new jxl.write.Label(6,0,"i love china",wcfFC);<BR><BR><U><FONT color=#800080>利用JExcelApi来动态生成excel文档 </FONT></U>
<P>首先，请到<A href="http://www.andykhan.com/jexcelapi/index.html">http://www.andykhan.com/jexcelapi/index.html</A>下载java excel api，主页上同时有比较详细的介绍。最新版本为2.4.3，同时也可以到：<A href="http://www.andykhan.com/jexcelapi/jexcelapi_2_4_3.tar.gz">http://www.andykhan.com/jexcelapi/jexcelapi_2_4_3.tar.gz</A>下载到该最新版的API，由于该项目是开源的，所以下载的文件中已经包含了源代码，同样的，文件中也有javadoc，大家在开发中可以参考javadoc。</P>
<P>下载完毕后，我们需要把文件中的jxl.jar加入到你的开发classpath中。<BR>下图是现在要生产的excel截图：<BR><A href="http://blog.csdn.net/beming/gallery/image/3437.aspx">http://blog.csdn.net/beming/gallery/image/3437.aspx</A><BR></P>
<P>代码如下：</P>
<P>&nbsp;&nbsp;&nbsp;File excel = new File("d:/aming.xls");<BR>&nbsp;&nbsp;&nbsp;if(!excel.exists()){<BR>&nbsp;&nbsp;&nbsp;&nbsp;excel.createNewFile();<BR>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;WritableWorkbook wwb = Workbook.createWorkbook(excel);<BR>&nbsp;&nbsp;&nbsp;WritableSheet ws = wwb.createSheet("testexcel",0);<BR>&nbsp;&nbsp;&nbsp;Label lable = null;<BR>&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;//对中文的支持非常好<BR>&nbsp;&nbsp;&nbsp;lable = new Label(0,0,"我的中国心");<BR>&nbsp;&nbsp;&nbsp;ws.addCell(lable);<BR>&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;//可以定义模板格式化你的cell<BR>&nbsp;&nbsp;&nbsp;WritableFont wf = new WritableFont(WritableFont.ARIAL,10,WritableFont.NO_BOLD,false,UnderlineStyle.NO_UNDERLINE, Colour.BLACK);<BR>&nbsp;&nbsp;&nbsp;WritableCellFormat wcf = new WritableCellFormat(wf);<BR>&nbsp;&nbsp;&nbsp;wcf.setBackground(Colour.WHITE);<BR>&nbsp;&nbsp;&nbsp;lable = new Label(0,1,"fdsl",wcf);<BR>&nbsp;&nbsp;&nbsp;ws.addCell(lable);<BR>&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;wf = new WritableFont(WritableFont.TIMES,18,WritableFont.BOLD,true);<BR>&nbsp;&nbsp;&nbsp;wcf = new WritableCellFormat(wf);<BR>&nbsp;&nbsp;&nbsp;lable = new Label(0,2,"aming",wcf);<BR>&nbsp;&nbsp;&nbsp;ws.addCell(lable);<BR>&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;//cell的类型同样可以定义为数字类型<BR>&nbsp;&nbsp;&nbsp;Number nb = new Number(0,3,21.4321321);<BR>&nbsp;&nbsp;&nbsp;ws.addCell(nb);<BR>&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;//支持格式化你的数字串<BR>&nbsp;&nbsp;&nbsp;NumberFormat nf = new NumberFormat("#.###");<BR>&nbsp;&nbsp;&nbsp;wcf = new WritableCellFormat(nf);<BR>&nbsp;&nbsp;&nbsp;nb = new Number(0,4,21.43254354354354,wcf);<BR>&nbsp;&nbsp;&nbsp;ws.addCell(nb);</P>
<P>&nbsp;&nbsp;&nbsp;//cell的类型可以为boolean类型<BR>&nbsp;&nbsp;&nbsp;Boolean bl = new Boolean(0,5,true);<BR>&nbsp;&nbsp;&nbsp;ws.addCell(bl);</P>
<P>&nbsp;&nbsp;&nbsp;//cell的类型同样可以为日期，时间<BR>&nbsp;&nbsp;&nbsp;DateTime dt = new DateTime(0,6,new Date());<BR>&nbsp;&nbsp;&nbsp;ws.addCell(dt);</P>
<P>&nbsp;&nbsp;&nbsp;//并且可以很好格式化你的日期格式<BR>&nbsp;&nbsp;&nbsp;DateFormat df = new DateFormat("MM dd yyyy hh:mm:ss");<BR>&nbsp;&nbsp;&nbsp;wcf = new WritableCellFormat(df);<BR>&nbsp;&nbsp;&nbsp;dt = new DateTime(0,7,new Date(),wcf);<BR>&nbsp;&nbsp;&nbsp;ws.addCell(dt);<BR>&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;//开始写文件了<BR>&nbsp;&nbsp;&nbsp;wwb.write();<BR>&nbsp;&nbsp;&nbsp;wwb.close();<BR></P><img src ="http://www.blogjava.net/faithwind/aggbug/28196.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-01-16 15:34 <a href="http://www.blogjava.net/faithwind/articles/28196.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java打印程序设计[转]</title><link>http://www.blogjava.net/faithwind/articles/28150.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Mon, 16 Jan 2006 01:17:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/28150.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/28150.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/28150.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/28150.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/28150.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 1&nbsp;前言在我们的实际工作中，经常需要实现打印功能。但由于历史原因，Java提供的打印功能一直都比较弱。实际上最初的jdk根本不支持打印，直到jdk1.1才引入了很轻量的打印支持。所以，在以前用Java/Applet/JSP/Servlet设计的程序中，较复杂的打印都是通过调用ActiveX/OCX控件或者VB/VC程序来实现的，非常麻烦。实际上，SUN公司也一直致力于Java打印功能...&nbsp;&nbsp;<a href='http://www.blogjava.net/faithwind/articles/28150.html'>阅读全文</a><img src ="http://www.blogjava.net/faithwind/aggbug/28150.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-01-16 09:17 <a href="http://www.blogjava.net/faithwind/articles/28150.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在servlet中使用jfreereport直接产生pdf文件报表【转】</title><link>http://www.blogjava.net/faithwind/articles/27912.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Fri, 13 Jan 2006 05:39:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/27912.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/27912.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/27912.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/27912.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/27912.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 摘要:目前基于Web的精确打印解决方案主要有两类：一是加强客户端的逻辑；二是在服务器端产生适于精确打印的文件。流行的打印工具中用Applet实现、用浏览器插件实现或是开发嵌入浏览器的客户端的方法属于第一类；而在服务器端产生图片文件、pdf或ps等.精确打印文件的方法属于第二类。下面就给出一个servlet通过模板生成pdf文件的实现.选择的报表工具是jfreereport.首先说些废话。基于J...&nbsp;&nbsp;<a href='http://www.blogjava.net/faithwind/articles/27912.html'>阅读全文</a><img src ="http://www.blogjava.net/faithwind/aggbug/27912.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-01-13 13:39 <a href="http://www.blogjava.net/faithwind/articles/27912.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用Java实现PDF报表 [转]</title><link>http://www.blogjava.net/faithwind/articles/27911.html</link><dc:creator>黑咖啡</dc:creator><author>黑咖啡</author><pubDate>Fri, 13 Jan 2006 05:36:00 GMT</pubDate><guid>http://www.blogjava.net/faithwind/articles/27911.html</guid><wfw:comment>http://www.blogjava.net/faithwind/comments/27911.html</wfw:comment><comments>http://www.blogjava.net/faithwind/articles/27911.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faithwind/comments/commentRss/27911.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faithwind/services/trackbacks/27911.html</trackback:ping><description><![CDATA[<P><FONT face=宋体 size=2>一、前言 </FONT></P>
<P></P>
<P>　　在企业的信息系统中，报表处理一直占比较重要的作用，本文将介绍一种生成PDF报表的Java组件--iText。通过在服务器端使用Jsp或JavaBean生成PDF报表，客户端采用超级连接显示或下载得到生成的报表，这样就很好的解决了B/S系统的报表处理问题。</P>
<P>　　二、iText简介</P>
<P>　　iText是著名的开放源码的站点sourceforge一个项目，是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档，而且可以将XML、Html文件转化为PDF文件。</P>
<P>　　iText的安装非常方便，在<A href="http://www.lowagie.com/iText/download.html" target=_blank><FONT color=#0000ff>http://www.lowagie.com/iText/download.html</FONT></A> - download 网站上下载iText.jar文件后，只需要在系统的CLASSPATH中加入iText.jar的路径，在程序中就可以使用iText类库了。</P>
<P>　　三、建立第一个PDF文档</P>
<P>　　用iText生成PDF文档需要5个步骤：</P>
<P>　　①建立com.lowagie.text.Document对象的实例。</P>
<P><BR>Document document = new Document(); </P>
<P>　　②建立一个书写器(Writer)与document对象关联，通过书写器(Writer)可以将文档写入到磁盘中。</P>
<P><BR>PDFWriter.getInstance(document, new FileOutputStream("Helloworld.PDF")); </P>
<P>　　③打开文档。</P>
<P><BR>document.open(); </P>
<P>　　④向文档中添加内容。</P>
<P><BR>document.add(new Paragraph("Hello World")); </P>
<P>　　⑤关闭文档。</P>
<P><BR>document.close(); </P>
<P>　　通过上面的5个步骤，就能产生一个Helloworld.PDF的文件，文件内容为"Hello World"。</P>
<P>　　建立com.lowagie.text.Document对象的实例</P>
<P>　　com.lowagie.text.Document对象的构建函数有三个，分别是：</P>
<P><BR>public Document();<BR>public Document(Rectangle pageSize);<BR>public Document(Rectangle pageSize,<BR>int marginLeft,<BR>int marginRight,<BR>int marginTop,<BR>int marginBottom); </P>
<P>　　构建函数的参数pageSize是文档页面的大小，对于第一个构建函数，页面的大小为A4，同Document(PageSize.A4)的效果一样；对于第三个构建函数，参数marginLeft、marginRight、marginTop、marginBottom分别为左、右、上、下的页边距。</P>
<P>　　通过参数pageSize可以设定页面大小、面背景色、以及页面横向/纵向等属性。iText定义了A0-A10、AL、LETTER、HALFLETTER、_11x17、LEDGER、NOTE、B0-B5、ARCH_A-ARCH_E、FLSA 和FLSE等纸张类型，也可以通过Rectangle pageSize = new Rectangle(144, 720);自定义纸张。通过Rectangle方法rotate()可以将页面设置成横向。</P>
<P>　　书写器（Writer）对象</P>
<P>　　一旦文档(document)对象建立好之后，需要建立一个或多个书写器(Writer)对象与之关联。通过书写器(Writer)对象可以将具体文档存盘成需要的格式，如com.lowagie.text.PDF.PDFWriter可以将文档存成PDF文件，com.lowagie.text.html.HtmlWriter可以将文档存成html文件。</P>
<P>　　设定文档属性</P>
<P>　　在文档打开之前，可以设定文档的标题、主题、作者、关键字、装订方式、创建者、生产者、创建日期等属性，调用的方法分别是：</P>
<P><BR>public boolean addTitle(String title)<BR>public boolean addSubject(String subject)<BR>public boolean addKeywords(String keywords)<BR>public boolean addAuthor(String author)<BR>public boolean addCreator(String creator)<BR>public boolean addProducer()<BR>public boolean addCreationDate()<BR>public boolean addHeader(String name, String content) </P>
<P>　　其中方法addHeader对于PDF文档无效，addHeader仅对html文档有效，用于添加文档的头信息。<BR>当新的页面产生之前，可以设定页面的大小、书签、脚注（HeaderFooter）等信息，调用的方法是：</P>
<P><BR>public boolean setPageSize(Rectangle pageSize)<BR>public boolean add(Watermark watermark)<BR>public void removeWatermark()<BR>public void setHeader(HeaderFooter header)<BR>public void resetHeader()<BR>public void setFooter(HeaderFooter footer)<BR>public void resetFooter()<BR>public void resetPageCount()<BR>public void setPageCount(int pageN)&nbsp;&nbsp;</P>
<P>　　如果要设定第一页的页面属性，这些方法必须在文档打开之前调用。</P>
<P>　　对于PDF文档，iText还提供了文档的显示属性，通过调用书写器的setViewerPreferences方法可以控制文档打开时Acrobat Reader的显示属性，如是否单页显示、是否全屏显示、是否隐藏状态条等属性。</P>
<P>　　另外，iText也提供了对PDF文件的安全保护，通过书写器（Writer）的setEncryption方法，可以设定文档的用户口令、只读、可打印等属性。</P>
<P>　　添加文档内容</P>
<P>　　所有向文档添加的内容都是以对象为单位的，如Phrase、Paragraph、Table、Graphic对象等。比较常用的是段落(Paragraph)对象，用于向文档中添加一段文字。</P>
<P>四、文本处理</P>
<P>　　iText中用文本块(Chunk)、短语(Phrase)和段落(paragraph)处理文本。<BR>文本块(Chunk)是处理文本的最小单位，有一串带格式（包括字体、颜色、大小）的字符串组成。如以下代码就是产生一个字体为HELVETICA、大小为10、带下划线的字符串：</P>
<P><BR>Chunk chunk1 = new Chunk("This text is underlined", FontFactory.getFont(FontFactory.HELVETICA, 12, Font.UNDERLINE));&nbsp;&nbsp;</P>
<P>　　短语(Phrase)由一个或多个文本块(Chunk)组成，短语(Phrase)也可以设定字体，但对于其中以设定过字体的文本块(Chunk)无效。通过短语(Phrase)成员函数add可以将一个文本块(Chunk)加到短语(Phrase)中，如：phrase6.add(chunk);</P>
<P>　　段落(paragraph)由一个或多个文本块(Chunk)或短语(Phrase)组成，相当于WORD文档中的段落概念，同样可以设定段落的字体大小、颜色等属性。另外也可以设定段落的首行缩进、对齐方式（左对齐、右对齐、居中对齐）。通过函数setAlignment可以设定段落的对齐方式，setAlignment的参数1为居中对齐、2为右对齐、3为左对齐，默认为左对齐。</P>
<P>　　五、表格处理</P>
<P>　　iText中处理表格的类为：com.lowagie.text.Table和com.lowagie.text.PDF.PDFPTable，对于比较简单的表格处理可以用com.lowagie.text.Table，但是如果要处理复杂的表格，这就需要com.lowagie.text.PDF.PDFPTable进行处理。这里就类com.lowagie.text.Table进行说明。</P>
<P>　　类com.lowagie.text.Table的构造函数有三个：</P>
<P>　　　①Table (int columns)<BR>　　　②Table(int columns, int rows)<BR>　　　③Table(Properties attributes)</P>
<P>　　参数columns、rows、attributes分别为表格的列数、行数、表格属性。创建表格时必须指定表格的列数，而对于行数可以不用指定。</P>
<P>　　建立表格之后，可以设定表格的属性，如：边框宽度、边框颜色、衬距（padding space 即单元格之间的间距）大小等属性。下面通过一个简单的例子说明如何使用表格，代码如下：</P>
<P><BR>1:Table table = new Table(3);<BR>2:table.setBorderWidth(1);<BR>3:table.setBorderColor(new Color(0, 0, 255));<BR>4:table.setPadding(5);<BR>5:table.setSpacing(5);<BR>6:Cell cell = new Cell("header");<BR>7:cell.setHeader(true);<BR>8:cell.setColspan(3);<BR>9:table.addCell(cell);<BR>10:table.endHeaders();<BR>11:cell = new Cell("example cell with colspan 1 and rowspan 2");<BR>12:cell.setRowspan(2);<BR>13:cell.setBorderColor(new Color(255, 0, 0));<BR>14:table.addCell(cell);<BR>15:table.addCell("1.1");<BR>16:table.addCell("2.1");<BR>17:table.addCell("1.2");<BR>18:table.addCell("2.2");<BR>19:table.addCell("cell test1");<BR>20:cell = new Cell("big cell");<BR>21:cell.setRowspan(2);<BR>22:cell.setColspan(2);<BR>23:table.addCell(cell);<BR>24:table.addCell("cell test2"); </P>
<P>　　运行结果如下：</P>
<P><BR>header&nbsp;&nbsp;<BR>example cell with colspan 1 and rowspan 2&nbsp;&nbsp;1.1 2.1&nbsp;&nbsp;<BR>1.2 2.2&nbsp;&nbsp;<BR>cell test1 big cell&nbsp;&nbsp;<BR>cell test2&nbsp;&nbsp;</P>
<P>　　代码1-5行用于新建一个表格，如代码所示，建立了一个列数为3的表格，并将边框宽度设为1，颜色为蓝色，衬距为5。</P>
<P>　　代码6-10行用于设定表格的表头，第7行cell.setHeader(true);是将该单元格作为表头信息显示；第8行cell.setColspan(3);指定了该单元格占3列；为表格添加表头信息时，要注意的是一旦表头信息添加完了之后，必须调用endHeaders()方法，如第10行，否则当表格跨页后，表头信息不会再显示。</P>
<P>　　代码11-14行是向表格中添加一个宽度占一列，长度占二行的单元格。</P>
<P>　　往表格中添加单元格(cell)时，按自左向右、从上而下的次序添加。如执行完11行代码后，表格的右下方出现2行2列的空白，这是再往表格添加单元格时，先填满这个空白，然后再另起一行，15-24行代码说明了这种添加顺序。</P>
<P>　　六、图像处理</P>
<P>　　iText中处理表格的类为com.lowagie.text.Image，目前iText支持的图像格式有：GIF, Jpeg, PNG, wmf等格式，对于不同的图像格式，iText用同样的构造函数自动识别图像格式。通过下面的代码分别获得gif、jpg、png图像的实例。</P>
<P><BR>Image gif = Image.getInstance("vonnegut.gif");<BR>Image jpeg = Image.getInstance("myKids.jpg");<BR>Image png = Image.getInstance("hitchcock.png"); </P>
<P>　　图像的位置</P>
<P>　　图像的位置主要是指图像在文档中的对齐方式、图像和文本的位置关系。IText中通过函数public void setAlignment(int alignment)进行处理，参数alignment为Image.RIGHT、Image.MIDDLE、Image.LEFT分别指右对齐、居中、左对齐；当参数alignment为Image.TEXTWRAP、Image.UNDERLYING分别指文字绕图形显示、图形作为文字的背景显示。这两种参数可以结合以达到预期的效果，如setAlignment(Image.RIGHT|Image.TEXTWRAP)显示的效果为图像右对齐，文字围绕图像显示。</P>
<P>　　图像的尺寸和旋转</P>
<P>　　如果图像在文档中不按原尺寸显示，可以通过下面的函数进行设定：</P>
<P><BR>public void scaleAbsolute(int newWidth, int newHeight)<BR>public void scalePercent(int percent)<BR>public void scalePercent(int percentX, int percentY) </P>
<P>　　函数public void scaleAbsolute(int newWidth, int newHeight)直接设定显示尺寸；函数public void scalePercent(int percent)设定显示比例，如scalePercent(50)表示显示的大小为原尺寸的50%；而函数scalePercent(int percentX, int percentY)则图像高宽的显示比例。</P>
<P>　　如果图像需要旋转一定角度之后在文档中显示，可以通过函数public void setRotation(double r)设定，参数r为弧度，如果旋转角度为30度，则参数r= Math.PI / 6。</P>
<P>　　七、中文处理</P>
<P>　　默认的iText字体设置不支持中文字体，需要下载远东字体包iTextAsian.jar，否则不能往PDF文档中输出中文字体。通过下面的代码就可以在文档中使用中文了：</P>
<P><BR>BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);<BR>com.lowagie.text.Font FontChinese = new com.lowagie.text.Font(bfChinese, 12, com.lowagie.text.Font.NORMAL);<BR>Paragraph pragraph=new Paragraph("你好", FontChinese);&nbsp;&nbsp;</P>
<P>　　八、后记</P>
<P>　　iText还有很多高级的功能，这里就不一一介绍了，具体开发时可参考发布的文档。总的来说，iText是一套java环境下不错的制作PDF的组件。因为iText支持jsp/javabean下的开发，这使得B/S应用中的报表问题能得到很好的解决。由于iText毕竟不是专门为制作报表设计，所有报表中的内容、格式都需要通过写代码实现，相对于那些专业的支持可视化设计的报表软件来说，编程的工作量就有一定程度的增加。</P><img src ="http://www.blogjava.net/faithwind/aggbug/27911.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faithwind/" target="_blank">黑咖啡</a> 2006-01-13 13:36 <a href="http://www.blogjava.net/faithwind/articles/27911.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>