﻿<?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高级</title><link>http://www.blogjava.net/esdsoftware/category/10839.html</link><description>&lt;-------------------------------------------------------------------&amp; 珍惜生命 . 善用时间 . 把握未来 . 创造价值。</description><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 06:20:51 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 06:20:51 GMT</pubDate><ttl>60</ttl><item><title>关于 J2SE5.0新特性之监控与管理</title><link>http://www.blogjava.net/esdsoftware/articles/69507.html</link><dc:creator>吴名居</dc:creator><author>吴名居</author><pubDate>Wed, 13 Sep 2006 15:44:00 GMT</pubDate><guid>http://www.blogjava.net/esdsoftware/articles/69507.html</guid><wfw:comment>http://www.blogjava.net/esdsoftware/comments/69507.html</wfw:comment><comments>http://www.blogjava.net/esdsoftware/articles/69507.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/esdsoftware/comments/commentRss/69507.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/esdsoftware/services/trackbacks/69507.html</trackback:ping><description><![CDATA[
		<h4 class="TextColor1" id="subjcns!42D5DAEFFDE7D929!273" style="MARGIN-BOTTOM: 0px">j2se 5.0使用 Java Management Extensions (JMX)来管理和监控java平台。<br />我们以一个例子来测试一下:<br /></h4>
		<div id="msgcns!42D5DAEFFDE7D929!273">
				<div>
						<div>
								<ol>
										<li>
										</li>
										<li>
												<b>
														<font color="#0000ff">import</font>
												</b> java.lang.management.ClassLoadingMXBean; 
</li>
										<li>
												<b>
														<font color="#0000ff">import</font>
												</b> java.lang.management.CompilationMXBean; 
</li>
										<li>
												<b>
														<font color="#0000ff">import</font>
												</b> java.lang.management.ManagementFactory; 
</li>
										<li>
												<b>
														<font color="#0000ff">import</font>
												</b> java.lang.management.MemoryMXBean; 
</li>
										<li>
												<b>
														<font color="#0000ff">import</font>
												</b> java.lang.management.MemoryManagerMXBean; 
</li>
										<li>
												<b>
														<font color="#0000ff">import</font>
												</b> java.lang.management.MemoryPoolMXBean; 
</li>
										<li>
												<b>
														<font color="#0000ff">import</font>
												</b> java.lang.management.OperatingSystemMXBean; 
</li>
										<li>
												<b>
														<font color="#0000ff">import</font>
												</b> java.lang.reflect.<b><a title="http://www.javaresearch.org/source/jdk142/java/lang/reflect/InvocationTargetException.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/reflect/InvocationTargetException.java.html"><font color="#0000ff"><u>InvocationTargetException</u></font></a></b>; 
</li>
										<li>
												<b>
														<font color="#0000ff">import</font>
												</b> java.lang.reflect.<b><a title="http://www.javaresearch.org/source/jdk142/java/lang/reflect/Method.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/reflect/Method.java.html"><font color="#0000ff"><u>Method</u></font></a></b>; 
</li>
										<li>
												<b>
														<font color="#0000ff">import</font>
												</b> java.util.<b><a title="http://www.javaresearch.org/source/jdk142/java/util/List.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/util/List.java.html"><font color="#0000ff"><u>List</u></font></a></b>; 
</li>
										<li>
										</li>
										<li>
												<b>
														<font color="#0000ff">public</font>
												</b> <b><font color="#0000ff">class</font></b> JDKMBean 
</li>
										<li>{ 
</li>
										<li>     
</li>
										<li>    <b><font color="#0000ff">public</font></b> <b><font color="#0000ff">static</font></b> &lt;T&gt; <b><font color="#0000ff">void</font></b> printMXBean(<b><a title="http://www.javaresearch.org/source/jdk142/java/lang/Class.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/Class.java.html"><font color="#0000ff"><u>Class</u></font></a></b>&lt;T&gt; t,<b><a title="http://www.javaresearch.org/source/jdk142/java/lang/Object.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/Object.java.html"><font color="#0000ff"><u>Object</u></font></a></b> object) 
</li>
										<li>    { 
</li>
										<li>        <b><a title="http://www.javaresearch.org/source/jdk142/java/lang/reflect/Method.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/reflect/Method.java.html"><font color="#0000ff"><u>Method</u></font></a></b>[] methods = t.getMethods(); 
</li>
										<li>        T instance = (T)object; 
</li>
										<li>        <b><a title="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html"><font color="#0000ff"><u>System</u></font></a></b>.out.printf(<font color="#ff33ff">"%n---%s---%n"</font>, t.getName()); 
</li>
										<li>        <b><font color="#0000ff">for</font></b>(<b><a title="http://www.javaresearch.org/source/jdk142/java/lang/reflect/Method.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/reflect/Method.java.html"><font color="#0000ff"><u>Method</u></font></a></b> m:methods) 
</li>
										<li>        { 
</li>
										<li>            <b><font color="#0000ff">if</font></b> (m.getName().startsWith(<font color="#ff33ff">"get"</font>)) 
</li>
										<li>            { 
</li>
										<li>                <b><font color="#0000ff">try</font></b></li>
										<li>                { 
</li>
										<li>                    <b><a title="http://www.javaresearch.org/source/jdk142/java/lang/Object.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/Object.java.html"><font color="#0000ff"><u>Object</u></font></a></b> rtValue = m.invoke(instance,<b><font color="#0000ff">new</font></b> <b><a title="http://www.javaresearch.org/source/jdk142/java/lang/Object.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/Object.java.html"><font color="#0000ff"><u>Object</u></font></a></b>[0]); 
</li>
										<li>                    <b><a title="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html"><font color="#0000ff"><u>System</u></font></a></b>.out.printf(<font color="#ff33ff">"%s:%s%n"</font>,m.getName().substring(3),rtValue); 
</li>
										<li>                 } 
</li>
										<li>                <b><font color="#0000ff">catch</font></b> (<b><a title="http://www.javaresearch.org/source/jdk142/java/lang/IllegalArgumentException.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/IllegalArgumentException.java.html"><font color="#0000ff"><u>IllegalArgumentException</u></font></a></b> e1) 
</li>
										<li>                {     
</li>
										<li>                 } 
</li>
										<li>                <b><font color="#0000ff">catch</font></b> (<b><a title="http://www.javaresearch.org/source/jdk142/java/lang/IllegalAccessException.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/IllegalAccessException.java.html"><font color="#0000ff"><u>IllegalAccessException</u></font></a></b> e) 
</li>
										<li>                {    
</li>
										<li>                 } 
</li>
										<li>                <b><font color="#0000ff">catch</font></b> (<b><a title="http://www.javaresearch.org/source/jdk142/java/lang/reflect/InvocationTargetException.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/reflect/InvocationTargetException.java.html"><font color="#0000ff"><u>InvocationTargetException</u></font></a></b> e) 
</li>
										<li>                { 
</li>
										<li>                 } 
</li>
										<li>             } 
</li>
										<li>         } 
</li>
										<li>     } 
</li>
										<li>    <b><font color="#0000ff">public</font></b> <b><font color="#0000ff">static</font></b> &lt;T&gt; <b><font color="#0000ff">void</font></b> printMXBeans(<b><a title="http://www.javaresearch.org/source/jdk142/java/lang/Class.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/Class.java.html"><font color="#0000ff"><u>Class</u></font></a></b>&lt;T&gt; t,<b><a title="http://www.javaresearch.org/source/jdk142/java/util/List.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/util/List.java.html"><font color="#0000ff"><u>List</u></font></a></b>&lt;T&gt; list) 
</li>
										<li>    { 
</li>
										<li>        <b><font color="#0000ff">for</font></b>(T bean:list) 
</li>
										<li>        { 
</li>
										<li>            printMXBean(t,bean); 
</li>
										<li>         } 
</li>
										<li>     } 
</li>
										<li>    <b><font color="#0000ff">public</font></b> <b><font color="#0000ff">static</font></b> <b><font color="#0000ff">void</font></b> main(<b><a title="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html&lt;br/&gt;&lt;br/&gt;#### ==&gt; TARGET=_blank &lt;== ###" href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html"><font color="#0000ff"><u>String</u></font></a></b>[] args) 
</li>
										<li>    { 
</li>
										<li>        JDKMBean.printMXBean(OperatingSystemMXBean.<b><font color="#0000ff">class</font></b>,ManagementFactory.getOperatingSystemMXBean()); 
</li>
										<li>        JDKMBean.printMXBean(CompilationMXBean.<b><font color="#0000ff">class</font></b>,ManagementFactory.getCompilationMXBean()); 
</li>
										<li>        JDKMBean.printMXBean(ClassLoadingMXBean.<b><font color="#0000ff">class</font></b>,ManagementFactory.getClassLoadingMXBean()); 
</li>
										<li>        JDKMBean.printMXBean(MemoryMXBean.<b><font color="#0000ff">class</font></b>,ManagementFactory.getMemoryMXBean()); 
</li>
										<li>        JDKMBean.printMXBeans(MemoryManagerMXBean.<b><font color="#0000ff">class</font></b>,ManagementFactory.getMemoryManagerMXBeans()); 
</li>
										<li>        JDKMBean.printMXBeans(MemoryPoolMXBean.<b><font color="#0000ff">class</font></b>,ManagementFactory.getMemoryPoolMXBeans()); 
</li>
										<li>     } 
</li>
										<li>} 
</li>
										<li>
										</li>
								</ol>
						</div>
						<br />运行结果:<br /><br />---java.lang.management.OperatingSystemMXBean---<br />Arch:x86<br />AvailableProcessors:2<br />Name:Windows 2000<br />Version:5.0<br /><br />---java.lang.management.CompilationMXBean---<br />TotalCompilationTime:5<br />Name:HotSpot Client Compiler<br /><br />---java.lang.management.ClassLoadingMXBean---<br />LoadedClassCount:431<br />UnloadedClassCount:0<br />TotalLoadedClassCount:431<br /><br />---java.lang.management.MemoryMXBean---<br />HeapMemoryUsage:init = 0(0K) used = 458288(447K) committed = 2031616(1984K) max = 66650112(65088K)<br />NonHeapMemoryUsage:init = 29556736(28864K) used = 12541248(12247K) committed = 29851648(29152K) max = 121634816(118784K)<br />ObjectPendingFinalizationCount:0<br /><br />---java.lang.management.MemoryManagerMXBean---<br />MemoryPoolNames:[Ljava.lang.String;@6ca1c<br />Name:CodeCacheManager<br /><br />---java.lang.management.MemoryManagerMXBean---<br />MemoryPoolNames:[Ljava.lang.String;@1bf216a<br />Name:Copy<br /><br />---java.lang.management.MemoryManagerMXBean---<br />MemoryPoolNames:[Ljava.lang.String;@12ac982<br />Name:MarkSweepCompact<br /><br />---java.lang.management.MemoryPoolMXBean---<br />CollectionUsage:null<br />MemoryManagerNames:[Ljava.lang.String;@c20e24<br />PeakUsage:init = 196608(192K) used = 482048(470K) committed = 491520(480K) max = 33554432(32768K)<br />Usage:init = 196608(192K) used = 524352(512K) committed = 557056(544K) max = 33554432(32768K)<br />UsageThreshold:0<br />UsageThresholdCount:0<br />Name:Code Cache<br />Type:Non-heap memory<br /><br />---java.lang.management.MemoryPoolMXBean---<br />CollectionUsage:init = 524288(512K) used = 0(0K) committed = 0(0K) max = 4194304(4096K)<br />CollectionUsageThreshold:0<br />CollectionUsageThresholdCount:0<br />MemoryManagerNames:[Ljava.lang.String;@2e7263<br />PeakUsage:init = 524288(512K) used = 511160(499K) committed = 524288(512K) max = 4194304(4096K)<br />Usage:init = 524288(512K) used = 521688(509K) committed = 524288(512K) max = 4194304(4096K)<br />Name:Eden Space<br />Type:Heap memory<br /><br />---java.lang.management.MemoryPoolMXBean---<br />CollectionUsage:init = 65536(64K) used = 0(0K) committed = 0(0K) max = 458752(448K)<br />CollectionUsageThreshold:0<br />CollectionUsageThresholdCount:0<br />MemoryManagerNames:[Ljava.lang.String;@157f0dc<br />PeakUsage:init = 65536(64K) used = 65528(63K) committed = 65536(64K) max = 458752(448K)<br />Usage:init = 65536(64K) used = 65528(63K) committed = 65536(64K) max = 458752(448K)<br />Name:Survivor Space<br />Type:Heap memory<br /><br />---java.lang.management.MemoryPoolMXBean---<br />CollectionUsage:init = 1441792(1408K) used = 0(0K) committed = 0(0K) max = 61997056(60544K)<br />CollectionUsageThreshold:0<br />CollectionUsageThresholdCount:0<br />MemoryManagerNames:[Ljava.lang.String;@863399<br />PeakUsage:init = 1441792(1408K) used = 142120(138K) committed = 1441792(1408K) max = 61997056(60544K)<br />Usage:init = 1441792(1408K) used = 142120(138K) committed = 1441792(1408K) max = 61997056(60544K)<br />UsageThreshold:0<br />UsageThresholdCount:0<br />Name:Tenured Gen<br />Type:Heap memory<br /><br />---java.lang.management.MemoryPoolMXBean---<br />CollectionUsage:init = 8388608(8192K) used = 0(0K) committed = 0(0K) max = 67108864(65536K)<br />CollectionUsageThreshold:0<br />CollectionUsageThresholdCount:0<br />MemoryManagerNames:[Ljava.lang.String;@a59698<br />PeakUsage:init = 8388608(8192K) used = 641040(626K) committed = 8388608(8192K) max = 67108864(65536K)<br />Usage:init = 8388608(8192K) used = 641040(626K) committed = 8388608(8192K) max = 67108864(65536K)<br />UsageThreshold:0<br />UsageThresholdCount:0<br />Name:Perm Gen<br />Type:Non-heap memory<br /><br />---java.lang.management.MemoryPoolMXBean---<br />CollectionUsage:init = 8388608(8192K) used = 0(0K) committed = 0(0K) max = 8388608(8192K)<br />CollectionUsageThreshold:0<br />CollectionUsageThresholdCount:0<br />MemoryManagerNames:[Ljava.lang.String;@141d683<br />PeakUsage:init = 8388608(8192K) used = 5601632(5470K) committed = 8388608(8192K) max = 8388608(8192K)<br />Usage:init = 8388608(8192K) used = 5601632(5470K) committed = 8388608(8192K) max = 8388608(8192K)<br />UsageThreshold:0<br />UsageThresholdCount:0<br />Name:Perm Gen [shared-ro]<br />Type:Non-heap memory<br /><br />---java.lang.management.MemoryPoolMXBean---<br />CollectionUsage:init = 12582912(12288K) used = 0(0K) committed = 0(0K) max = 12582912(12288K)<br />CollectionUsageThreshold:0<br />CollectionUsageThresholdCount:0<br />MemoryManagerNames:[Ljava.lang.String;@16a55fa<br />PeakUsage:init = 12582912(12288K) used = 5850024(5712K) committed = 12582912(12288K) max = 12582912(12288K)<br />Usage:init = 12582912(12288K) used = 5850024(5712K) committed = 12582912(12288K) max = 12582912(12288K)<br />UsageThreshold:0<br />UsageThresholdCount:0<br />Name:Perm Gen [shared-rw]<br />Type:Non-heap memory</div>
		</div>
<img src ="http://www.blogjava.net/esdsoftware/aggbug/69507.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/esdsoftware/" target="_blank">吴名居</a> 2006-09-13 23:44 <a href="http://www.blogjava.net/esdsoftware/articles/69507.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在 Java 应用程序中访问USB设备</title><link>http://www.blogjava.net/esdsoftware/articles/59324.html</link><dc:creator>吴名居</dc:creator><author>吴名居</author><pubDate>Thu, 20 Jul 2006 18:06:00 GMT</pubDate><guid>http://www.blogjava.net/esdsoftware/articles/59324.html</guid><wfw:comment>http://www.blogjava.net/esdsoftware/comments/59324.html</wfw:comment><comments>http://www.blogjava.net/esdsoftware/articles/59324.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/esdsoftware/comments/commentRss/59324.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/esdsoftware/services/trackbacks/59324.html</trackback:ping><description><![CDATA[Java 平台一直都以其平台无关性自豪。虽然这种无关性有许多好处，但是它也使得编写与硬件交互的 Java 应用程序的过程变得相当复杂。在本文中，研究科学家蒋清野讨论了两个项目，它们通过提供使Java 应用程序可以使用 USB 设备的 API 而使这个过程变得更容易。虽然这两个项目仍然处于萌芽状态，但是它们都显示了良好的前景，并已经成为一些实用应用程序的基础。<br /><br />　　通用串行总线(Universal Serial Bus USB)规范的第一个版本发表于 1996年 1月。因为它的低成本、高数据传输率、使用容易和灵活性，USB 在计算机行业里获得了广泛接受。今天，许多周边设备和装置都是通过 USB 接口连接到计算机上的。目前，大多数一般用途的操作系统都提供了对 USB 设备的支持，并且用 C 或者 C++ 可以相对容易地开发访问这些外设的应用程序。不过，Java 编程语言在设计上对硬件访问提供的支持很少，所以编写与 USB 设备交互的应用程序是相当困难的。 <br /><br />　　IBM 的 Dan Streetman 最早开始了在 Java 语言中提供对 USB 设备的访问的努力。2001年，他的项目通过 Java 规范请求(Java Specification Request，JSR)过程被接受为 Java 语言的候选扩展标准。这个项目现在称为 JSR-80 并且指定了官方包 javax.usb。同时，在 2000年 6月，Mojo Jojo 和 David Brownell 在 SourceForge 开始了 jUSB 项目。这两个项目都开发出了 Linux 开发人员可以使用的包，尽管它们都还很不完善。这两个项目也都开始试图向其他操作系统上的 Java 应用程序提供对 USB 设备的访问，尽管它们都还没有开发出可以使用的包(参阅 参考资料 中有关本文中讨论的这两个项目及其他项目的资料)。<br /><br />　　在本文中，将对 jUSB 和 JSR-80 项目作一个简要介绍，不过，我们首先要看一下 USB 协议的具体细节，这样您就可以理解这两个项目是如何与 USB 设备交互的。我们还将提供代码片段以展示如何用这两个项目的 API 访问 USB 设备。 USB 介绍<br /><br />　　1994年，一个由四个行业伙伴(Compaq、Intel、Microsoft 和 NEC)组成的联盟开始制定 USB 协议。该协议最初的目的是将 PC 与电话相连并提供容易扩展和重新配置的 I/O 接口。1996年 1月，发表了 USB 规范的第一个版本，1998年 9月发表了后续版本(版本 1.1)。这个规范允许 127台设备同时连接到一起，总的通信带宽限制为 12 Mbps。后来，又有三个成员(Hewlett-Packard、Lucent 和 Philips)加入了这个联盟。2000年 4月，发表了 USB 规范的 2.0版本，它支持高达 480 Mbps 的传输率。今天，USB 在高速(视频、图像、储存)和全速(音频、宽带、麦克风)数据传输应用中起了关键作用。它还使各种低速设备(键盘、鼠标、游戏外设、虚拟现实外设)连接到 PC 上。<br /><br />　　USB 协议有严格的层次结构。在所有 USB 系统中，只有一个主设备，到主计算机的的 USB 接口称为主控器(host controller)。主控器有两个标准??开放主控器接口(Compaq 的 Open Host Controller Interface，OHCI)和通用主控器接口(Intel 的 Universal Host Controller Interface，UHCI)。这两个标准提供了同样的能力，并可用于所有的 USB 设备，UHCI 的硬件实现更简单一些，但是需要更复杂的设备驱动程序(因而 CPU 的负荷更大一些)。<br /><br />　　USB 物理互连是分层的星形拓朴，最多有七层。一个 hub 是每个星形的中心，USB 主机被认为是 root hub。每一段连线都是 hub 与 USB 设备的点对点连接，后者可以是为系统提供更多附加点的另一个 hub，也可以是一个提供功能的某种设备。主机使用主/从协议与 USB 设备通信。这种方式解决了包冲突的问题，但是同时也阻止了附加的设备彼此建立直接通信。<br /><br />　　所有传输的数据都是由主控器发起的。数据从主机流向设备称为下行(downstream)或者输出(out)传输，数据从设备流向主机称为上 行(upstream)或者输入(in)传输。数据传输发生在主机和 USB 设备上特定的端点(endpoint) 之间，主机与端点之间的数据链接称为管道(pipe)。 一个给定的 USB 设备可以有许多个端点，主机与设备之间数据管道的数量与该设备上端点的数量相同。一个管道可以是单向或者是双向的，一个管道中的数据流与所有其他管道中的数据流无关。<br /><br />　　USB 网络中的通信可以使用下面四种数据传输类型中的任意一种：<br /><br />　　控制传输： 这些是一些短的数据包，用于设备控制和配置，特别是在设备附加到主机上时。 <br /><br />　　批量传输： 这些是数量相对大的数据包。像扫描仪或者 SCSI 适配器这样的设备使用这种传输类型。 <br /><br />　　中断传输： 这些是定期轮询的数据包。主控器会以特定的间隔自动发出一个中断。 <br /><br />　　等时传输： 这些是实时的数据流，它们对带宽的要求高于可靠性要求。音频和视频设备一般使用这种传输类型。 <br /><br />　　像串行端口一样，计算机上每一个 USB 端口都由 USB 控制器指定了一个惟一的标识数字(端口 ID)。当 USB 设备附加到 USB 端口上时，就将这个 惟一端口 ID 分配给这台设备，并且 USB 控制器会读取设备描述符。设备描述符包括适用于该设备的全局信息、以及设备的配置信息。配置定义了一台 USB 设备的功能和 I/O 行为。一台 USB 设备可以有一个或者多个配置，这由它们相应的配置描述符所描述。每一个配置都有一个或者多个接口，它可以视为一个物理通信渠道 ；每一个接口有零个或者多个端点，它可以是数据提供者或者数据消费者，或者同时具有这两种身份。接口由接口描述符描述，端点由端点描述符描述。并且一台 USB 设备可能还有字符串描述符以提供像厂商名、设备名或者序列号这样的附加信息。<br /><br />　　正如您所看到的，像 USB 这样的协议为使用 Java 这种强调平台和硬件无关性的语言的开发人员提出了挑战。现在让我们看两个试图解决这个问题的项目。<br /><strong>jUSB API<br /><br /></strong>　　jUSB 项目是由 Mojo Jojo 和 David Brownell 于 2000年 6月创立的。其目标是提供一组免费的、在 Linux 平台上访问 USB 设备的 Java API。这个 API 是按照 Lesser GPL (LGPL)条款发表的，这意味着您可以在专有和免费软件项目中使用它。这个 API 提供了对多个物理 USB 设备的多线程访问，并支持本机和远程设备。具有多个接口的设备可以同时被多个应用程序(或者设备驱动程序)所访问，其中每一个应用程序(或者设备驱动程序)都占据一个不同的接口。该 API 支持控制传输、批量传输和中断传输，不支持等时传输，因为等时传输用于媒体数据(如音频和视频)，JMF API 已经在其他标准设备驱动程序上对此提供了很好的支持(参阅 参考资料)。当前，该 API 可以在具有 Linux 2.4 核心或者以前的 2.2.18 核心的 GNU/Linux 版本上工作。因此可支持大多数最新的版本，例如，该 API 可以在没有任何补丁或者升级的 Red Hat 7.2 和 9.0 上工作。<br /><br />　　jUSB API 包括以下包：<br />　<br />　　·usb.core: 这个包是 jUSB API 的核心部分。它使得 Java 应用程序可以从 USB 主机访问 USB 设备。<br /><br />　　·usb.linux: 这个包包含 usb.core.Host 对象的 Linux 实现、bootstrapping 支持和其他可以提升 Linux USB 支持的类。这个实现通过虚拟 USB 文件系统(usbdevfs)访问 USB 设备。<br /><br />　　·usb.windows: 这个包包含 usb.core.Host 对象的 Windows 实现、bootstrapping 支持和其他可以提升 Windows USB 支持的类。这个实现仍然处于非常初级的阶段。<br /><br />　　·usb.remote: 这个包是 usb.core API 的远程版本。它包括一个 RMI proxy 和一个 daemon 应用程序，它让 Java 应用程序可以访问远程计算机上的 USB 设备。<br /><br />　　·usb.util: 这个包提供了一些有用的实用程序，可以将 firmware下载到 USB 设备上、将 USB 系统的内容转储到 XML 中、以及将只有 bulk I/O 的 USB 设备工具转换成一个套接字(socket)。<br /><br />　　·usb.devices: 这个可选包收集了用 jUSB API 访问不同 USB 设备的 Java 代码，包括柯达数码相机和 Rio 500 MP3 播放器。这些 API 经过特别编写以简化访问特定 USB 设备的过程，并且不能用于访问其他设备。这些 API 是在 usb.core API 之上构建的，它们可以工作在所有支持 jUSB 的操作系统上。<br /><br />　　·usb.view: 这个可选包提供了基于 Swing 的 USB 树简单浏览器。它是一个展示 jUSB API 应用的很好的示例程序。 <br /><br />　　尽管 usb.core.Host 对象的实现对于不同的操作系统是不同的，但是 Java 程序员只需要理解 usb.core 包就可以用 jUSB API 开始应用程序的开发。表 1 列出了 usb.core 的接口和类，Java 程序员应该熟悉它们：<br /><br />　　表 1. jUSB 中的接口和类<br /><br /><table cellspacing="0" cellpadding="2" width="90%" align="center" border="1"><tbody><tr><td>接口</td><td>说明</td></tr><tr><td>Bus</td><td>将一组 USB 设备连接到 Host 上 </td></tr><tr><td>Host</td><td>表示具有一个或者多个 Bus 的 USB 控制器 </td></tr></tbody></table><br /><table cellspacing="0" cellpadding="2" width="90%" align="center" border="1"><tbody><tr><td width="17%">类 </td><td width="83%">说明</td></tr><tr><td width="17%">Configuration</td><td width="83%">提供对设备所支持的 USB 配置的访问，以及对与该配置关联的接口的访问 </td></tr><tr><td width="17%">Descriptor </td><td width="83%">具有 USB 类型的描述符的实体的基类 </td></tr><tr><td width="17%">Device </td><td width="83%">提供对 USB 设备的访问 </td></tr><tr><td width="17%">DeviceDescriptor </td><td width="83%">提供对 USB 设备描述符的访问 </td></tr><tr><td width="17%">EndPoint </td><td width="83%">提供对 USB 端点描述符的访问、在给定设备配置中构造设备数据输入或者输出</td></tr><tr><td width="17%">HostFactory</td><td width="83%">包含 bootstrapping 方法 </td></tr><tr><td width="17%">Hub</td><td width="83%">提供对 USB hub 描述符以及一些 hub 操作的访问 </td></tr><tr><td width="17%">Interface </td><td width="83%">描述一组端点，并与一个特定设备配置相关联 </td></tr><tr><td width="17%">PortIdentifier </td><td width="83%">为 USB 设备提供稳定的字符串标识符，以便在操作和故障诊断时使用</td></tr></tbody></table><br />　　用 jUSB API 访问一台 USB 设备的正常过程如下：<br /><br />　　·通过从 HostFactory 得到 USB Host 进行 Bootstrap。<br /><br />　　·从 Host 访问 USB Bus，然后从这个 Bus 访问 USB root hub(即 USB Device)。<br /><br />　　·得到 hub 上可用的 USB 端口数量，遍历所有端口以找到正确的 Device。<br /><br />　　·访问附加到特定端口上的 USB Device。可以用一台 Device 的 PortIdentifier 直接从 Host 访问它，也可以通过从 root hub 开始遍历 USB Bus 找到它。<br /><br />　　·用 ControlMessage 与该 Device 直接交互，或者从该 Device 的当前 Configuration 中要求一个 Interface，并与该 Interface 上可用的 Endpoint 进行 I/O 。 <br /><br />　　清单 1 展示了如何用 jUSB API 获得 USB 系统中的内容。这个程序编写为只是查看 root hub 上可用的 USB 设备，但是很容易将它改为遍历整个 USB 树。这里的逻辑对应于上述步骤 1 到步骤 4。<br /><br />　　清单 1. 用 jUSB API 获得 USB 系统的内容<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>import usb.core.*;<br /><br />public class ListUSB<br />{<br />　public static void main(String[] args)<br />　{<br />　　try<br />　　{<br />　　　// Bootstrap by getting the USB Host from the HostFactory.<br />　　　Host host = HostFactory.getHost();<br /><br />　　　// Obtain a list of the USB buses available on the Host.<br />　　　Bus[] bus = host.getBusses();<br />　　　int total_bus = bus.length;<br /><br />　　　// Traverse through all the USB buses.<br />　　　for (int i=0; i&lt;total_bus; i++)<br />　　　{<br />　　　　// Access the root hub on the USB bus and obtain the<br />　　　　// number of USB ports available on the root hub.<br />　　　　Device root = bus[i].getRootHub();<br />　　　　int total_port = root.getNumPorts();<br /><br />　　　　// Traverse through all the USB ports available on the <br />　　　　// root hub. It should be mentioned that the numbering <br />　　　　// starts from 1, not 0.<br />　　　　for (int j=1; j&lt;=total_port; j++)<br />　　　　{<br />　　　　　// Obtain the Device connected to the port.<br />　　　　　Device device = root.getChild(j);<br />　　　　　if (device != null)<br />　　　　　{<br />　　　　　　// USB device available, do something here.<br />　　　　　}<br />　　　　}<br />　　　}<br />　　} catch (Exception e)<br />　　{<br />　　　System.out.println(e.getMessage());<br />　　}<br />　}</td></tr></tbody></table><br />清单 2 展示了在应用程序成功地找到了 Device 的条件下，如何与 Interface 和 EndPoint 进行批量 I/O。 这个代码段也可以修改为执行控制或者中断 I/O。它对应于上述步骤 5。<br /><br />　　清单 2. 用 jUSB API 执行批量 I/O <br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>if (device != null)<br />{<br />　// Obtain the current Configuration of the device and the number of <br />　// Interfaces available under the current Configuration.<br />　Configuration config = device.getConfiguration();<br />　int total_interface = config.getNumInterfaces();<br /><br />　// Traverse through the Interfaces<br />　for (int k=0; k&lt;total_interface; k++)<br />　{<br />　　// Access the currently Interface and obtain the number of <br />　　// endpoints available on the Interface. <br />　　Interface itf = config.getInterface(k, 0);<br />　　int total_ep = itf.getNumEndpoints();<br /><br />　　// Traverse through all the endpoints.<br />　　for (int l=0; l&lt;total_ep; l++)<br />　　{<br />　　　// Access the endpoint, and obtain its I/O type.<br />　　　Endpoint ep = itf.getEndpoint(l);<br />　　　String io_type = ep.getType();<br />　　　boolean input = ep.isInput();<br /><br />　　　// If the endpoint is an input endpoint, obtain its<br />　　　// InputStream and read in data.<br />　　　if (input)<br />　　　{<br />　　　　InputStream in;<br />　　　　in = ep.getInputStream();<br />　　　　// Read in data here<br />　　　　in.close();<br />　　　}<br />　　　// If the Endpoint is and output Endpoint, obtain its <br />　　　// OutputStream and write out data.<br />　　　else<br />　　　{<br />　　　　OutputStream out;<br />　　　　out = ep.getOutputStream();<br />　　　　// Write out data here.<br />　　　　out.close();<br />　　　}<br />　　}<br />　}<br />}</td></tr></tbody></table><br />　　jUSB 项目在 2000年 6月到 2001年 2月期间非常活跃。该 API 的最新的版本 0.4.4发表于 2001年 2月 14日。从那以后只提出了很少的改进，原因可能是 IBM 小组成功地成为了 Java 语言的候选扩展标准。不过，基于 jUSB 已经开发出一些第三方应用程序，包括 JPhoto 项目(这是一个用 jUSB 连接到数码照相机的应用程序)和 jSyncManager 项目(这是一个用 jUSB 与使用 Palm 操作系统的 PDA 同步的应用程序)。<br /><br /><br /><strong>JSR-80 API (javax.usb)<br /><br /></strong>　　正如前面提到的，JSR-80 项目是由 IBM 的 Dan Streetman 于 1999年创立的。2001年，这个项目通过 Java 规范请求(JSR)过程被接受为 Java 语言的候选扩展标准。这个项目现在称为 JSR-80 并且被正式分派了 Java 包 javax.usb。这个项目使用 Common Public License 的许可证形式，并通过 Java Community Process 进行开发。这个项目的目标是为 Java 平台开发一个 USB 接口，可以从任何 Java 应用程序中完全访问 USB 系统。JSR-80 API 支持 USB 规范所定义的全部四种传输类型。目前，该 API 的 Linux 实现可以在支持 2.4 核心的大多数最新 GNU/Linux 版本上工作，如 Red Hat 7.2 和 9.0。<br /><br />　　JSR-80 项目包括三个包：javax-usb (javax.usb API)、javax-usb-ri (操作系统无关的基准实现的公共部分)以及 javax-usb-ri-linux (Linux 平台的基准实现，它将公共基准实现链接到 Linux USB 堆栈)。所有这三个部分都是构成 Linux 平台上 java.usb API 完整功能所必需的。在该项目的电子邮件列表中可以看到有人正在致力于将这个 API 移植到其他操作系统上(主要是 Microsoft Windows)，但是还没有可以工作的版本发表。<br /><br />　　尽管 JSR-80 API 的操作系统无关的实现在不同的操作系统上是不同的，但是 Java 程序员只需要理解 javax.usb 包就可以开始开发应用程序了。表 2 列出了 javax.usb 中的接口和类， Java 程序员应该熟悉它们：<br /><br />　　表 2. JSR-80 API 中的接口和类<br /><br /><table cellspacing="0" cellpadding="2" width="90%" align="center" border="1"><tbody><tr><td width="25%">接口 </td><td width="75%">说明</td></tr><tr><td width="25%">UsbConfiguration </td><td width="75%">表示 USB 设备的配置 </td></tr><tr><td width="25%">UsbConfigurationDescriptor</td><td width="75%">USB 配置描述符的接口 </td></tr><tr><td width="25%">UsbDevice</td><td width="75%">USB 设备的接口</td></tr><tr><td width="25%">UsbDeviceDescriptor</td><td width="75%">USB 设备描述符的接口 </td></tr><tr><td width="25%">UsbEndpoint</td><td width="75%">USB 端点的接口 </td></tr><tr><td width="25%">UsbEndpointDescriptor</td><td width="75%">USB 端点描述符的接口</td></tr><tr><td width="25%">UsbHub</td><td width="75%">USB hub 的接口</td></tr><tr><td width="25%">UsbInterface</td><td width="75%">USB 接口的接口 </td></tr><tr><td width="25%">UsbInterfaceDescriptor</td><td width="75%">USB 接口描述符的接口 </td></tr><tr><td width="25%">UsbPipe </td><td width="75%">USB 管道的接口 </td></tr><tr><td width="25%">UsbPort </td><td width="75%">USB 端口的接口 </td></tr><tr><td width="25%">UsbServices </td><td width="75%">javax.usb 实现的接口 </td></tr></tbody></table><br /><table cellspacing="0" cellpadding="2" width="90%" align="center" border="1"><tbody><tr><td width="25%">类 </td><td width="75%">说明 </td></tr><tr><td width="25%">UsbHostManager </td><td width="75%">javax.usb 的入口点</td></tr></tbody></table><br />　　用 JSR-80 API 访问 USB 设备的正常过程如下：<br /><br />　　·通过从 UsbHostManager 得到相应的 UsbServices 进行 Bootstrap。<br /><br />　　·通过 UsbServices 访问 root hub。在应用程序中 root hub 就是一个 UsbHub。<br /><br />　　·获得连接到 root hub 的 UsbDevices 清单。遍历所有低级 hub 以找到正确的 UsbDevice。<br /><br />　　·用控制消息(UsbControlIrp)与 UsbDevice 直接交互，或者从 UsbDevice 的相应 UsbConfiguration 中要求一个 UsbInterface 并与该 UsbInterface 上可用的 UsbEndpoint 进行 I/O。<br /><br />　　·如果一个 UsbEndpoint 用于进行 I/O，那么打开与它关联的 UsbPipe。通过这个 UsbPipe 可以同步或者异步提交上行数据(从 USB 设备到主计算机)和下行数据(从主计算机到 USB 设备)。<br /><br />　　·当应用程序不再需要访问该 UsbDevice 时，关闭这个 UsbPipe 并释放相应的 UsbInterface。 <br /><br />　　在清单 3 中，我们用 JSR-80 API 获得 USB 系统的内容。这个程序递归地遍历 USB 系统上的所有 USB hub 并找出连接到主机计算机上的所有 USB 设备。这段代码对应于上述步骤 1 到步骤 3。<br /><br />　　清单 3. 用 JSR-80 API 获得 USB 系统的内容<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>import javax.usb.*;<br />import java.util.List;<br /><br />public class TraverseUSB<br />{<br />　public static void main(String argv[])<br />　{<br />　　try<br />　　{<br />　　　// Access the system USB services, and access to the root <br />　　　// hub. Then traverse through the root hub.<br />　　　UsbServices services = UsbHostManager.getUsbServices();<br />　　　UsbHub rootHub = services.getRootUsbHub();<br />　　　traverse(rootHub);<br />　　} catch (Exception e) {}<br />　}<br /><br />　public static void traverse(UsbDevice device)<br />　{<br />　　if (device.isUsbHub())<br />　　{ <br />　　　// This is a USB Hub, traverse through the hub.<br />　　　List attachedDevices = ((UsbHub) device).getAttachedUsbDevices();<br />　　　for (int i=0; i&lt;attachedDevices.size(); i++)<br />　　　{<br />　　　　traverse((UsbDevice) attachedDevices.get(i));<br />　　　}<br />　　}<br />　　else<br />　　{<br />　　　// This is a USB function, not a hub.<br />　　　// Do something.<br />　　}<br />　}<br />}</td></tr></tbody></table><br />　　清单 4 展示了在应用程序成功地找到 Device 后，如何与 Interface 和 EndPoint 进行 I/O。这段代码还可以修改为进行所有四种数据传输类型的 I/O。它对应于上述步骤 4 到步骤 6。<br /><br />　　清单 4. 用 JSR-80 API 进行 I/O <br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>public static void testIO(UsbDevice device)<br />{<br />　try<br />　{<br />　　// Access to the active configuration of the USB device, obtain <br />　　// all the interfaces available in that configuration.<br />　　UsbConfiguration config = device.getActiveUsbConfiguration();<br />　　List totalInterfaces = config.getUsbInterfaces();<br /><br />　　// Traverse through all the interfaces, and access the endpoints <br />　　// available to that interface for I/O.<br />　　for (int i=0; i&lt;totalInterfaces.size(); i++)<br />　　{<br />　　　UsbInterface interf = (UsbInterface) totalInterfaces.get(i);<br />　　　interf.claim();<br />　　　List totalEndpoints = interf.getUsbEndpoints();<br />　　　for (int j=0; j&lt;totalEndpoints.size(); j++)<br />　　　{<br />　　　　// Access the particular endpoint, determine the direction<br />　　　　// of its data flow, and type of data transfer, and open the <br />　　　　// data pipe for I/O.<br />　　　　UsbEndpoint ep = (UsbEndpoint) totalEndpoints.get(i);<br />　　　　int direction = ep.getDirection();<br />　　　　int type = ep.getType();<br />　　　　UsbPipe pipe = ep.getUsbPipe();<br />　　　　pipe.open();<br />　　　　// Perform I/O through the USB pipe here.<br />　　　　pipe.close();<br />　　　}<br />　　　interf.release();<br />　　}<br />　} catch (Exception e) {} <br />}</td></tr></tbody></table><br />　　JSR-80 项目从一开始就非常活跃。2003年 2月发表了 javax.usb API、RI 和 RI 的 0.10.0 版本。看起来这一版本会提交给 JSR-80 委员会做最终批准。预计正式成为 Java 语言的扩展标准后，其他操作系统上的实现会很快出现。Linux 开发者团体看来对 JSR-80 项目的兴趣比 jUSB 项目更大，使用 Linux 平台的 javax.usb API 的项目数量在不断地增加。<br /><br />　　<b>结束语</b><br />　 <br />　　jUSB API 和 JSR-80 API 都为应用程序提供了从运行 Linux 操作系统的计算机中访问 USB 设备的能力。JSR-80 API 提供了比 jUSB API 更多的功能，很有可能成为 Java 语言的扩展标准。目前，只有 Linux 开发人员可以利用 jUSB 和 JSR-80 API 的功能。不过，有人正在积极地将这两种 API 移植到其他操作系统上。Java 开发人员应该在不久就可以在其他操作系统上访问 USB 设备。从现在起就熟悉这些 API，当这些项目可以在多个平台上发挥作用时，您就可以在自己的应用程序中加入 USB 功能了。<br /><img src ="http://www.blogjava.net/esdsoftware/aggbug/59324.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/esdsoftware/" target="_blank">吴名居</a> 2006-07-21 02:06 <a href="http://www.blogjava.net/esdsoftware/articles/59324.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何使用J2EE打印服务</title><link>http://www.blogjava.net/esdsoftware/articles/44837.html</link><dc:creator>吴名居</dc:creator><author>吴名居</author><pubDate>Sun, 07 May 2006 00:27:00 GMT</pubDate><guid>http://www.blogjava.net/esdsoftware/articles/44837.html</guid><wfw:comment>http://www.blogjava.net/esdsoftware/comments/44837.html</wfw:comment><comments>http://www.blogjava.net/esdsoftware/articles/44837.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/esdsoftware/comments/commentRss/44837.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/esdsoftware/services/trackbacks/44837.html</trackback:ping><description><![CDATA[
		<p>
				<strong>
						<span style="FONT-SIZE: 16px">摘要</span>
						<br />
				</strong>在这篇文章里，来自Pro Java Programming (Apress, June 2005)专家Brett Spell解释了如何一步一步的定位打印设备，创建打印工程，创建一个Doc文档接口的实例来描述你想要打印的数据并且初始化打印。（4500字，2005年7月25日）<br /><br />Java自从问世以来在各方面发展迅速，但是一直以来，打印输出是java最弱的方面。事实上，java1.0不支持任何打印功能。Java1.1在java.awt包里包含了一个叫做PrintJob的类，但是这个类提供的打印功能十分粗糙和不可靠。当java1.2问世，它围绕PrinterJob设计了一个完整独立的打印机制（叫做java2D printing API），并且在java.awt.print包里定义了一些新的类和接口。这些使得基于PrintJob打印机制（就是AWT printing）基本荒废，虽然PrintJob从未被抨击而且至少在这篇文章里仍然是一个提供技术的类。<br /><br />在J2SE1.3里当PrintJob的功能扩展到可以通过在java.awt包里的JobAttributes 和PageAttributes两个类设定工程和页面的属性时发生了一些额外的改变。随着J2SE1.3的发布，打印功能相应的得到了完善；但是在混合使用这两种完全不同的打印机制的时候仍然存在一些问题。比如，这两种机制使用java.awt.Graphics这个类的一个接口来展现打印内容，意味着所有要打印的东西都必须用一张图片表示。另外，完善的PrintJob提供了很有限的工程相关属性的设置；这两种机制都没有办法通过程序来选择目标打印机。<br /><br />Java打印最大的改变来自于J2SE的发布带来的Java打印服务API。这个第三代Java打印支持接口突破了先前提到的使用javax.print包的PrintService和DocPrintJob接口的局限性。因为新的API就是以前两种旧的打印机制定义的功能函数的一个父集，它是目前我们常用的方法并且是这篇文章的焦点。<br />更深入来说,以下的步骤包含了怎么使用这个新的Java打印服务API：<br />1.定义打印机，限制那些返回到提供你要实现功能的函数的列表。打印服务实现了PrintService接口.<br /><br />2.通过调用接口中定义的createPrintJob()方法创建一个打印事件，作为DocPrintJob的一个实例。<br /><br />3.创建一个实现Doc接口的类来描述你想要打印的数据 , 你也可以创建一个PrintRequestAttributeSet的实例来定义你想要的打印选项。<br /><br />4.通过DocPrintJob接口定义的printv()方法来初始化打印，指定你先前创建的Doc，指定PrintRequestAttributeSet或者设为空值。<br /><br />现在你可以检查每一步并且试着完成它们。<br /><br /><br /></p>
		<center>
				<span style="COLOR: red">注意</span>
		</center>
		<p>        <br /><hidden name="N/A">在这篇文章里，我将交替使用打印机和打印服务，因为在大部分情况下，打印服务不亚于一台真实的打印机。 一般的打印服务反映了理论上可以发送到不仅仅是打印机的的输出。举例来说，打印服务也许根本不能打印东西但是可以往磁盘上的文件写数据。换句话说，所有的打印机可以看成是特殊的打印服务，但是并不是所有打印服务和打印机有联系。就像你一般把你的文本送到打印机那里一样，我有时候使用更为简便的打印机这个名词来代替技术上更精确的打印服务。        <br /><br /><br /><b><span style="FONT-SIZE: 16px">定义打印服务</span></b><br />你可以使用在PrintServiceLookup类中定义的三种静态方法中的一种来定义。最简单的一种就是lookupDefaultPrintService()，正如它的名字一样，它返回一个你默认的打印机：<br /><br /></hidden></p>
		<pre class="overflow" title="pre code">PrintService service = PrintServiceLookup.lookupDefaultPrintService(); </pre>
		<p>
				<br />
				<br />虽然用这个办法很简单也很方便，用它来选择你的打印机意味着用户的打印机一直都支持你的程序所要精确传输的数据输出。实际上，你真正想要的是那种可以处理你想要的数据的类型并且可以支持你要的特征例如颜色或者两边打印。为了从列表中中返回你所要求的特殊功能支持的打印机，你可以使用剩下两个方法中的lookupPrintServices() 或者lookupMultiDocPrintServices()。<br /><br />lookupPrintServices()方法有两个参数：一个DocFlavor的实例和实现AttributeSet接口的实例。<br />你马上将看到，你可以使用两者中任意一个来限制返回的打印机，但是lookupPrintServices()允许你指定这两个参数为空值。如果把两者都设为空，那么你得到的返回值将是任意一个可用的打印机。在这种情况下，你并不需要查看PrintService中定义的方法，其中一个getName()方法返回了一个字符串，代表打印机的名字。你可以编译下面的代码来列出你的系统现有的打印机：<br /><br /></p>
		<pre class="overflow" title="pre code">PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);<br />for (int i = 0; i &lt; services.length; i++) {<br />   System.out.println(services[i].getName());<br />} </pre>
		<p>
				<br />
				<br />例如你的系统名为PrintServer，下面有Alpha, Beta, 和Gamma 打印机,用以上代码可以得到以下输出: <br />\PrintServerAlpha<br />\PrintServerBeta<br />\PrintServerGamma <br /><br />现在查看那些你可以传给lookupPrintServices()方法的参数来看看如何返回拥有特殊功能的打印机。<br /><br /><b>DocFlavor</b><br />第一个你可以指定的参数是一个DocFlavor类的实例，它描述了将要打印的数据的类型和数据如何存储。在大部分情况下，并不需要去创建一个新的实例因为Java包含了很多预先定义的实例，使得你可以用它们来传给lookupPrintServices()。然而，我们还是来看一下DocFlavor的结构和方法来探讨打印服务如何使用这个实例。<br /><br />创建DocFlavor实例需要的两个参数都是字符串，一个是MIME (Multipurpose Internet Mail Extensions)类型另一个是类的名字。MIME类型被用于描述数据类型。例如，你要打印一个gif文件，你需要使用MIME类型是image/gif的DocFlavor。相类似，如果你要打印HTML文件里的文本信息要使用MIME类型似text/plain或者text/html。<br />MIME类型描述将要打印的数据的类型，表现的类则表示如何让打印服务得到这些数据。DocFlavor包含了几个静态的内部类，每一个相对应一个表现类和如何装载要打印得数据。<br /><br />表1中列出了上面提到的内部类和表现类。注意在SERVICE_FORMATTED（一会我会更详细地解释）旁边，每一个和"binary"或者 "character"相对应。事实上，这些差别是人为的，因为"character"数据类型本身就是一种特殊的binary类型。这种情况下，我们说的二进制（binary）数据包括人们可以看懂的字符和一些格式化的字符比如tabs，换行回车等。当然，这些差别很重要，反映出面向字符的表现类并不适合存储二进制数据。<br /><br />例如，你不会用字符队列或者字符串来保存一个gif文件，你也不能通过Reader接口来访问它。另一方面，因为字符也是一种特殊的二进制数据，它完全适合储存文本信息到字节数组里或者通过InputStream或者一个URL来访问它。<br /><br /><b>Table 1. DocFlavor的表现类</b><br /><img onmouseover="javascript:ImgShowTip(this);" style="DISPLAY: inline" onclick="javascript:ImgClick(this);" height="500" alt="image" src="http://matrix.org.cn/resource/upload/content/2005_08_25_201937_JMyayhbEMv.jpg" width="500" onload="javascript:ImgLoad(this);" border="0" /><br /><br />上面定义的任何一个静态内部类相对应一个表现类，但是请记住我说过每一个DocFlavor的实例通过一个表现类和一个MIME来确认要打印的数据的类型。<br />要访问这样一个实例，你要通过表1总列出的内部类。例如，我们假设你要打印一个在网上通过URL访问的gif文件，这样的话，就选择表现类是javav.net.url，对应的在DocFlavor中的静态类就是URL类。如果你打开那个内部类的文档，你会发现其实它定义了一系列静态的内部类，每一个对应一种打印机支持的MIME类型。表2描述了在DocFlavor.URL里的内部类和MIME<br /><br /><b>Table 2. The DocFlavor.URL inner classes</b><br /><img onmouseover="javascript:ImgShowTip(this);" style="DISPLAY: inline" onclick="javascript:ImgClick(this);" height="500" alt="image" src="http://matrix.org.cn/resource/upload/content/2005_08_25_202120_vjndGWQhbN.jpg" width="500" onload="javascript:ImgLoad(this);" border="0" /><br /><br />因为要通过URL打印gif图片，你可以用一下代码来获得实例<br /><br /></p>
		<pre class="overflow" title="pre code">DocFlavor flavor = DocFlavor.URL.GIF; </pre>
		<p>
				<br />
				<br />这个代码创建了一个DocFlavor实例，代表类是java.net.URL，MIME是image/gif。<br />表2列出的了DocFlavor.URL的类，那么其他六个内部类呢？我们等下来讨论一下SERVICE_FORMATTED，这之前，看看与二进制数据联系的所有三种类型（BYTE_ARRAY, INPUT_STREAM, and URL)相关的内部类。例如，如果你把gif储存到了一个字节数组里，那么你可以用以下代码：<br /><br /></p>
		<pre class="overflow" title="pre code">DocFlavor flavor = DocFlavor.BYTE_ARRAY.GIF; </pre>
		<p>
				<br />
				<br />正如有三个与二进制类型关联的内部类一样，与字符类型相关的另外三个类列在表3里 <br /><br /><hidden name="N/A"><b>Table 3. CHAR_ARRAY, READER, and STRING </b><br /><br /><img onmouseover="javascript:ImgShowTip(this);" style="DISPLAY: inline" onclick="javascript:ImgClick(this);" height="500" alt="image" src="http://matrix.org.cn/resource/upload/content/2005_08_25_202254_sNCELVkRJp.jpg" width="500" onload="javascript:ImgLoad(this);" border="0" /><br /><br />所以，如果你想打印储存在字符串中的文本数据，用以下代码：<br /><br /></hidden></p>
		<pre class="overflow" title="pre code">DocFlavor flavor = DocFlavor.STRING.TEXT_PLAIN;</pre>
		<p>
				<br />
				<br />类似，如果文本来自于网页上的HTML文档，用以下代码：<br /><br /></p>
		<pre class="overflow" title="pre code">DocFlavor flavor = DocFlavor.STRING.TEXT_HTML;</pre>
		<p>
				<br />
				<br />
				<b>选择正确的打印机</b>
				<br />还记得我们在开始关于讨论DocFlavor之前关于打印机的那个精确支持你想要打印的数据类型的假设吗？这似乎看起来没有必要。实际上，你会对给你的打印机所支持的文档类型感到吃惊。例如，刚提到文本类型看起来似乎是最容易支持的，所以，如果你的程序要打印一个普通文本或者HTML文本，你可以随便选择一个打印服务并把它送到打印机那去。然而大部分打印机不支持基于文本的表现类，如果你试图向打印机发送它不支持的DocFlavor，会产生下面的异常：<br /><br /></p>
		<pre class="overflow" title="pre code">Exception in thread "main" sun.print.PrintJobFlavorException: invalid flavor<br />   at sun.print.Win32PrintJob.print(Win32PrintJob.java:290)<br />   at PrintTest.main(PrintTest.java:11) </pre>
		<p>
				<br />
				<br />现在你已经知道了如何得到一个DocFlavor的引用而且我们也讨论了选择支持这个flavor的打印机重要性，接下来我来告诉你如何确定你使用的打印机支持它。我先前说过lookupPrintServices()允许你指定一个DocFlavor作为第一个参数，如果你指定的参数非空，那么方法会返回相应支持这个的打印机的实例。例如以下代码将返回可以通过URL来打印gif文件的打印机的列表：<br /><br /></p>
		<pre class="overflow" title="pre code">DocFlavor flavor = DocFlavor.URL.GIF;<br />PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, null); </pre>
		<p>
				<br />
				<br />
				<br />另外，如果你的程序已经获得了打印服务的实例，而你想知道它是否支持另一种特定的flavor，你可以调用isDocFlavorSupported()方法。在下面的代码里，将得到一个默认打印机的引用，如果不能打印gif就会出现错误信息：<br /><br /></p>
		<pre class="overflow" title="pre code">PrintService service = PrintServiceLookup.lookupDefaultPrintService();<br />DocFlavor flavor = DocFlavor.URL.GIF;<br />if (!service.isDocFlavorSupported(flavor)) {<br />   System.err.println("The printer does not support the appropriate DocFlavor");<br />} </pre>
		<p>
				<br />
				<br />
				<b>AttributeSet</b>
				<br />正如你看到的，DocFlavor描述打印数据而且可以用来确定打印服务是否支持这种数据。然而，你的程序需要选择一个基于那些支持的元素的打印机。例如，你要打印图片用不同的颜色来描述不同的信息，你想知道提供的服务是否支持彩色打印，如果不，那么要么禁止它使用或者要求提供一个黑白图片。<br /><br />类似彩色打印，两边打印或者使用不同的定位取决于打印机本身的属性，而javax.print.attribute包包含了许多你可以用于描述这些属性的包和接口。其中一个接口是前面提到的lookupPrintServices()中第二个参数AttributeSet。正如你愿，它返回属性的集合，在调用lookupPrintServices()指定一个不为空的值将返回支持这些属性的打印服务。换句话说，如果DocFlavor和 AttributeSet都不为空，那么方法将返回那些这两种属性都支持的打印机<br /><strong>Attribute<br /></strong>AttributeSet 是属性的集合,一个显而易见的问题是如何指定属性的值呢？ javax.print.attribute包里同时含有一个叫Attribute的接口，你马上可以看到通过调用add方法来给AttributeSet创建一个Attribute实例来获得这个集合。在javax.print.attribute.standard包里定义了大量你将要用到的接口。在之前，你可以查看javax.print.attribute这个包里的其他接口。<br /><br /><b>属性模块</b><br />目前为止，我们把属性描述成打印服务的功能，而实际上在java支持的属性中算很简单的。对应每个属性，java都有相应的模块。只有遵循这些模块属性才有效。在不同的java打印服务位置使用不同的属性，而不是所有的属性在任何地方都适用。<br />为了更好的理解这个，来看一下javax.print.attribute.standard 包里定义的<br />OrientationRequested和 ColorSupported接口。创建一个新的打印文档时可以指定OrientationRequested属性和用于打印的定位。ColorSupported在你调用PrintService接口的getAttributes方法时返回。OrientationRequested是一个你用来传给打印机的属性，而ColorSupported是打印服务用来提供给你关于打印机能力信息的工具。你可以在创建打印文档时把ColorSupported作为属性指定，因为打印机是否支持彩色打印是你的程序不能控制的。<br /><br /><b>接口和继承</b><br />你第一次查看javax.print.attribute包里的接口和类时你也许会感到选择那些列表里的接口和类很麻烦。除了Attribute 和AttributeSet和继承AttributeSet的HashAttributeSet，javax.print.attribute包里有4个子接口和类，列出在表4和图1中。<br /><br />Table 4. javax.print.attribute 里定义的接口和类 <br /><br /><img onmouseover="javascript:ImgShowTip(this);" style="DISPLAY: inline" onclick="javascript:ImgClick(this);" height="500" alt="image" src="http://matrix.org.cn/resource/upload/content/2005_08_25_202846_kbvgfeTAtq.jpg" width="500" onload="javascript:ImgLoad(this);" border="0" /><br /><br /><img onmouseover="javascript:ImgShowTip(this);" style="DISPLAY: inline" onclick="javascript:ImgClick(this);" height="500" alt="image" src="http://matrix.org.cn/resource/upload/content/2005_08_25_202852_uEuWwwcYhc.jpg" width="500" onload="javascript:ImgLoad(this);" border="0" />  Figure 1. javax.print.attribute 包的一部分类的层次结构.        <br /><br />那么有了Attribute, AttributeSet, 和 HashAttributeSet为什么需要使用这些不同的接口和继承类呢？是因为这些特殊的类是为那些特殊的属性量身定做的。比方说，我提到过当你创建打印文档的时候有个地方可以使用的属性例如ColorSupported在那里不能使用。当创建这样的文档，你可以使用DocAttributeSet接口（或者更专业一点，HashDocAttributeSet这个继承的类），这个继承类只允许你添加继承DocAttribute这个接口的属性。这四种不同的模块如下：<br /><hidden name="N/A">        Doc: 在创建文档时指        定如何打印文档<br />        PrintJob: 打印任务的属性描述任务的状态 <br />        PrintRequest: 初始化打印时传给任务的请求 <br />        PrintService:由打印服        务返回来描述打印机的功能<br /><br />要知道如何工作，我们来创建一个DocAttributeSet的实例然后为AttributeSet设置DocAttributeSet和OrientationRequested属性。HashDocAttributeSet定义了很好的结构，所有你可以很简便的如下创建实例：<br /><br /></hidden></p>
		<pre class="overflow" title="pre code">DocAttributeSet attrs = new HashDocAttributeSet(); </pre>
		<p>
				<br />
				<br />现在你已经创建了AttributeSet，你可以调用add方法并把它传给Attribute的继承实例去。如果你看了OrientationRequested这个类的文档，你会发现它包含了一系列静态的OrientationRequest实例，每一个对应一种文档定位方式。要指定你想要的类型，你所要做的只是按下面的方法传给add方法一个静态的实例的引用：<br /><br /></p>
		<pre class="overflow" title="pre code">DocAttributeSet attrs = new HashDocAttributeSet();<br />attrs.add(OrientationRequested.PORTRAIT); </pre>
		<p>
				<br />
				<br />ColorSupported类有一点不同但一样很简单，它定义了两种静态实例:一个表示支持彩色打印另一个不是。你可以试着增加一个ColorSupported属性到DocAttributeSet去，代码如下：<br /><br /></p>
		<pre class="overflow" title="pre code">DocAttributeSet attrs = new HashDocAttributeSet();<br />attrs.add(OrientationRequested.PORTRAIT);<br />attrs.add(ColorSupported.SUPPORTED); </pre>
		<p>
				<br />
				<br />早先提过，去指定是否支持彩色打印不恰当因为这不是程序所能控制的内容。换句话说，ColorSupported这个属性放到一系列文档属性中并不合适，所以，运行先前的代码当添加ColorSupported属性时会抛出一个ClassCastException异常。<br /><br />要学习怎么运行，记住每一个AttributeSet子接口都有一个相应Attribute子接口和继承子类。当添加一个属性时，继承的子类试图把Attribute作为参数给相应的子接口，这样来确保只有当前适当的属性会成功添加。<br /><br />这样的话，HashDocAttributeSet 的add方法第一次和OrientationRequested的一个实例一起调用，并成功的把它作为一个object传给DocAttribute。因为如图2所示，OrientationRequested继承了那个接口。与之相对应，传ColorSupported实例的时候因为没有继承DocAttribute所以失败了。<br /><br /><img onmouseover="javascript:ImgShowTip(this);" style="DISPLAY: inline" onclick="javascript:ImgClick(this);" height="500" alt="image" src="http://matrix.org.cn/resource/upload/content/2005_08_25_203046_dWImlEsbwU.jpg" width="500" onload="javascript:ImgLoad(this);" border="0" /><br />Figure 2. javax.print.attribute 包的一部分类的层次结构         <br /><br />这个例子举例说明，表4里的四个接口和类组来保证使用正确的属性。注意模块和不同的属性之间有大量的交互，所以很多属性与不止一个模块关联。例如，许多属性继承了PrintJobAttribute 和 PrintRequestAttribute因为大部分是通过一个相关的打印任务获得提供给你的。你可以在初始化时指定它们。举个例子，你可以把它加到PrintRequestAttributeSet中去来指定任务名，并且在打印的时候通过PrintJobAttributeSet来返回它。因此，JobName属性类同时继承<br />PrintRequestAttribute 和 PrintJobAttribute。<br />AttributeSet and HashAttributeSet<br /><br />你已经知道了为什么会有四个子类，但是AttributeSet接口和HashAttributeSet父类又是什么呢？AttributeSet/HashAttributeSet在你不能确定要存储在这个集合中的那些仅仅和一个模块相关的属性时使用。记得我以前提到的lookupPrintServices()方法允许你指定AttributeSet参数来限制返回的打印服务。表面上看来最好指定PrintServiceAttributeSet的实例，但是很多你可能用到的属性并不继承PrintServiceAttribute。<br /><br />我们假设你想要让lookupPrintServices()方法返回支持彩色打印和风景画打印的打印机。这些属性与ColorSupported和OrientationRequested属性关联，但是请注意这些类并不共享模块，前者是一个PrintServiceAttribute而OrientationRequested与另外三个模块(Doc, PrintRequest,和 PrintJob)关联。这意味着不存在单个的AttributeSet接口或类来同时包含ColorSupported和Sides属性。<br /><br />创建AttributeSet的方法使用一个HashAttributeSet实例同时包含一个OrientationRequested 和 ColorSupported太简单了。不像它的子类，它并不限制你往上加特殊的属性，所以可以用以下代码成功执行：<br /><br /></p>
		<pre class="overflow" title="pre code">AttributeSet attrs = new HashAttributeSet();<br />attrs.add(ColorSupported.SUPPORTED);<br />attrs.add(OrientationRequested.LANDSCAPE);<br />PrintService[] services = PrintServiceLookup.lookupPrintServices(null, attrs); </pre>
		<p>
				<br />
				<br />
				<b>通过用户界面的打印机选择</b>
				<br />就此观点而言，我认为使用的打印机应该由应用程序计划选择。但操作过程中，打印输出内容时往往会显示一个对话框让用户选择。幸运的是，Java通过使用ServiceUI类（在javax.print包中定义）中的静态printDialog()方法使得这些操作简单化。<br />在显示的对话框旁边，仅在调用printDialog()时必须指定的参数值如下：<br />用户可选用的PrintService实例数组。<br />默认的PrintService。<br />PrintRequestAttributeSet实例。这用来弹出显示的对话框，并在对话框消失之前返回用户所作的任何更改。<br />要解释这些如何运作，可使用下列简单的代码段来显示打印对话：<br /><br /></p>
		<pre class="overflow" title="pre code">PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);<br />PrintService svc = PrintServiceLookup.lookupDefaultPrintService();<br />PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();<br />PrintService selection = ServiceUI.printDialog(<br />   null, 100, 100, services, svc, null, attrs); </pre>
		<p>
				<br />
				<br />运行时，代码产生如图例3中所示的对话框<br /><br /><img onmouseover="javascript:ImgShowTip(this);" style="DISPLAY: inline" onclick="javascript:ImgClick(this);" height="500" alt="image" src="http://matrix.org.cn/resource/upload/content/2005_08_25_203348_ospjfDNFNI.jpg" width="500" onload="javascript:ImgLoad(this);" border="0" /><br />Figure 3. The printer dialog        <br /><br />随着代码的说明，从printDialog()方法返回的值是一个PrintService实例，识别用户所选的打印机，或在用户取消打印机对话时识别为空。此外，PrintRequestAttributeSet已更新到可通过对话框来反映用户作出的更改，比如要打印的份数。<br />通过使用printDialog()方法，可让用户选择其输出要发往的打印机，提供用户对于专业应用程序的期望功能。<br /><br /><hidden name="N/A"><b><span style="FONT-SIZE: 16px">创建打印任务</span></b><br />这是打印中的一个简单步骤；因为一旦获得PrintService的一个参考，你需要做的就是调用createPrintJob()方法，如：<br /></hidden></p>
		<pre class="overflow" title="pre code">PrintService service;<br />.<br />.<br />.<br />DocPrintJob job = service.createPrintJob(); </pre>
		<p>
				<br />代码中显示，从createPrintJob()返回的值是一个DocPrintJob实例，可让您控制并监控打印操作的状态。要启动打印，您会调用DocPrintJob对象的print()方法，但是，这之前，您需要定义待打印的文档或选用PrintRequestAttributeSe。您已经知道如何构造并弹出AttributeSet，这个步骤不再重复，接下来，您将了解定义待打印的文档。<br /><br /><b><span style="FONT-SIZE: 16px">定义要打印的文档</span></b><br />接下来这一步是定义要打印的文档，用一个在javax.print包里的Doc的接口实例来创建。每一个Doc的实例有两个必须定义的属性和一个可选择的属性：<br />        Object 代表要打印的内容<br />        DocFlavor的一个实例描述数据类型<br />        可选的DocAttributeSet 包含打印时的属性<br /><br />复习Doc接口的文档可以看出javax.print包里包含了一个叫SimpleDoc 的接口的继承，它的构造函数包含了上面三个参数。要知道如何构建SimpleDoc 的实例，我们假设你要打印两份存在http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif的gif文件拷贝。<br />我们要做的就是构建一个SimpleDoc实例来描述这个文档创建了一个URL来指向图片，并且引用了DocFlavor，并把这两个传给SimpleDoc构造函数：<br /><br /></p>
		<pre class="overflow" title="pre code">URL url = new URL(<br />   "http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif");<br />DocFlavor flavor = DocFlavor.URL.GIF;<br />SimpleDoc doc = new SimpleDoc(url, flavor, null); </pre>
		<p>
				<br />
				<br />
				<b>
						<span style="FONT-SIZE: 16px">启动打印</span>
				</b>
				<br />打印的最后一个步骤就是调用DocPrintJob的 print()方法，传递待打印数据的Doc对象，或选用PrintRequestAttributeSet实例。为简单起见，假设默认打印机支持你所需要的flavor和属性，在此情况下要使用下列代码将上一个例子提及的gif文件打印两份：<br /></p>
		<pre class="overflow" title="pre code">PrintService service = PrintServiceLookup.lookupDefaultPrintService();<br />DocPrintJob job = service.createPrintJob();<br />URL url = new URL(<br />   "http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif ");<br />DocFlavor flavor = DocFlavor.URL.GIF;<br />Doc doc = new SimpleDoc(url, flavor, null);<br />PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();<br />attrs.add(new Copies(2));<br />job.print(doc, attrs)</pre>
		<p>; <br />注意，一些情况下，打印不同步执行，这可能会在实际打印完成之前返回对print()的调用。<br /><br />关于作者<br />Brett Spell是一个Frito-Lay的高级程序开发者并写了Pro Java Programming 的初始版本。 <br />资源<br />这是Pro Java Programming, Second Edition, Brett Spell (Apress, June 2005; ISBN: 1590594746):第十章“打印”的一部分 http://www.apress.com/book/bookDisplay.html?bID=421 <br />要得到更多关于java API的文章，点击以下链接<br />http://www.javaworld.com/channel_content/jw-apis-index.shtml <br /></p>
<img src ="http://www.blogjava.net/esdsoftware/aggbug/44837.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/esdsoftware/" target="_blank">吴名居</a> 2006-05-07 08:27 <a href="http://www.blogjava.net/esdsoftware/articles/44837.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JASS学习</title><link>http://www.blogjava.net/esdsoftware/articles/44836.html</link><dc:creator>吴名居</dc:creator><author>吴名居</author><pubDate>Sun, 07 May 2006 00:24:00 GMT</pubDate><guid>http://www.blogjava.net/esdsoftware/articles/44836.html</guid><wfw:comment>http://www.blogjava.net/esdsoftware/comments/44836.html</wfw:comment><comments>http://www.blogjava.net/esdsoftware/articles/44836.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/esdsoftware/comments/commentRss/44836.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/esdsoftware/services/trackbacks/44836.html</trackback:ping><description><![CDATA[
		<h4>(来源：转载自yesky网站，<a href="http://www.yesky.com/">http://www.yesky.com/</a>) <br /><br />　　<b><font color="#ac000">摘要：</font></b><br /><br />　　Java Authentication Authorization Service（JAAS，Java验证和授权API）提供了灵活和可伸缩的机制来保证客户端或服务器端的Java程序。Java早期的安全框架强调的是通过验证代码的来源和作者，保护用户避免受到下载下来的代码的攻击。JAAS强调的是通过验证谁在运行代码以及他／她的权限来保护系统面受用户的攻击。它让你能够将一些标准的安全机制，例如Solaris NIS（网络信息服务）、Windows NT、LDAP（轻量目录存取协议），Kerberos等通过一种通用的，可配置的方式集成到系统中。本文首先向你介绍JAAS验证中的一些核心部分，然后通过例子向你展示如何开发登录模块。<br /><br />　　你是否曾经需要为一个应用程序实现登录模块呢？如果你是一个比较有经验的程序员，相信你这样的工作做过很多次，而且每次都不完全一样。你有可能把你的登录模块建立在Oracle数据库的基础上，也有可能使用的是NT的用户验证，或者使用的是LDAP目录。如果有一种方法可以在不改变应用程序级的代码的基础上支持上面提到的所有这一些安全机制，对于程序员来说一定是一件幸运的事。<br /><br />　　现在你可以使用JAAS实现上面的目标。JAAS是一个比较新的的Java API。在J2SE 1.3中，它是一个扩展包；在J2SE 1.4中变成了一个核心包。在本文中，我们将介绍JAAS的一些核心概念，然后通过例子说明如何将JAAS应用到实际的程序中。本文的例子是根据我们一个基于Web的Java应用程序进行改编的，在这个例子中，我们使用了关系数据库保存用户的登录信息。由于使用了JAAS，我们实现了一个健壮而灵活的登录和身份验证模块。<br /><br />　　<b><font color="#ac000">Java验证和授权：概论</font></b><br /><br />　　在JAAS出现以前，Java的安全模型是为了满足跨平台的网络应用程序的需要而设计的。在Java早期版本中，Java通常是作为远程代码被使用，例如Applet，。因此最初的安全模型把注意力放在通过验证代码的来源来保护用户上。早期的Java安全机制中包含的概念，如SercurityManager，沙箱概念，代码签名，策略文件，多是为了保护用户。<br /><br />　　JAAS的出现反映了Java的演变。传统的服务器／客户端程序需要实现登录和存取控制，JAAS通过对运行程序的用户的进行验证，从而达到保护系统的目的。虽然JAAS同时具有验证和授权的能力，在这篇文章中，我们主要介绍验证功能。<br /><br />　　通过在应用程序和底层的验证和授权机制之间加入一个抽象层，JAAS可以简化涉及到Java Security包的程序开发。抽象层独立于平台的特性使开发人员可以使用各种不同的安全机制，而且不用修改应用程序级的代码。和其他Java Security API相似，JAAS通过一个可扩展的框架：服务提供者接口（Service Provider Interface，SPI）来保证程序独立于安全机制。服务提供者接口是由一组抽象类和接口组成的。图一中给出了JAAS程序的整体框架图。应用程序级的代码主要处理LoginContext。在LoginContext下面是一组动态配置的LoginModules。LoginModule使用正确的安全机制进行验证。<br /><br />　　图一给出了JAAS的概览。应用程序层的代码只需要和LoginContext打交道。在LoginContext之下是一组动态配置的LoginModule对象，这些对象使用相关的安全基础结构进行验证操作。<br /><br /><table width="100" align="center"><tbody><tr align="middle"><td><img height="396" src="http://www.huihoo.com/java/j2ee/jt-2003-1-14-image001.gif" width="573" /><br />图一 JAAS概览 </td></tr></tbody></table><br />　　JAAS提供了一些LoginModule的参考实现代码，比如JndiLoginModule。开发人员也可以自己实现LoginModule接口，就象在我们例子中的RdbmsLonginModule。同时我们还会告诉你如何使用一个简单的配置文件来安装应用程序。<br /><br />　　为了满足可插接性，JAAS是可堆叠的。在单一登录的情况下，一组安全模块可以堆叠在一起，然后被其他的安全机制按照堆叠的顺序被调用。<br /><br />　　JAAS的实现者根据现在一些流行的安全结构模式和框架将JASS模型化。例如可堆叠的特性同Unix下的可堆叠验证模块（PAM，Pluggable Authentication Module）框架就非常相似。从事务的角度看，JAAS类似于双步提交（Two-Phase Commit，2PC）协议的行为。JAAS中安全配置的概念（包括策略文件（Police File）和许可（Permission））来自于J2SE 1.2。JAAS还从其他成熟的安全框架中借鉴了许多思想。<br /><br /><br /><br />　　<b><font color="#ac000">客户端和服务器端的JAAS</font></b><br /><br />　　开发人员可以将JAAS应用到客户端和服务器端。在客户端使用JAAS很简单。在服务器端使用JAAS时情况要复杂一些。目前在应用服务器市场中的JAAS产品还不是很一致，使用JAAS的J2EE应用服务器有一些细微的差别。例如JBossSx使用自己的结构，将JAAS集成到了一个更大的安全框架中；而虽然WebLogic 6.x也使用了JAAS，安全框架却完全不一样。<br /><br />　　现在你能够理解为什么我们需要从客户端和服务器端的角度来看JAAS了。我们将在后面列出两种情况下的例子。为了使服务器端的例子程序更加简单，我们使用了Resin应用服务器。<br /><br />　　<b><font color="#ac000">核心JAAS类</font></b><br /><br />　　在使用JAAS之前，你首先需要安装JAAS。在J2SE 1.4中已经包括了JAAS，但是在J2SE 1.3中没有。如果你希望使用J2SE 1.3，你可以从SUN的官方站点上下载JAAS。当正确安装了JAAS后，你会在安装目录的lib目录下找到jaas.jar。你需要将该路径加入Classpath中。（注：如果你安装了应用服务器，其中就已经包括了JAAS，请阅读应用服务器的帮助文档以获得更详细的信息）。在Java安全属性文件java.security中，你可以改变一些与JAAS相关的系统属性。该文件保存在＜jre_home＞/lib/security目录中。<br /><br />　　在应用程序中使用JAAS验证通常会涉及到以下几个步骤：<br /><br />　　1. 创建一个LoginContext的实例。<br /><br />　　2. 为了能够获得和处理验证信息，将一个CallBackHandler对象作为参数传送给LoginContext。<br /><br />　　3. 通过调用LoginContext的login（）方法来进行验证。<br /><br />　　4. 通过使用login（）方法返回的Subject对象实现一些特殊的功能（假设登录成功）。<br /><br />　　下面是一个简单的例子：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>LoginContext lc = new LoginContext("MyExample");<br />try {<br />lc.login();<br />} catch (LoginException) {<br />// Authentication failed.<br />}<br /><br />// Authentication successful, we can now continue.<br />// We can use the returned Subject if we like.<br />Subject sub = lc.getSubject();<br />Subject.doAs(sub, new MyPrivilegedAction());</td></tr></tbody></table><br />　　在运行这段代码时，后台进行了以下的工作。<br /><br />　　1. 当初始化时，LoginContext对象首先在JAAS配置文件中找到MyExample项，然后更具该项的内容决定该加载哪个LoginModule对象（参见图二）。<br /><br />　　2. 在登录时，LoginContext对象调用每个LoginModule对象的login（）方法。<br /><br />　　3. 每个login（）方法进行验证操作或获得一个CallbackHandle对象。<br /><br />　　4. CallbackHandle对象通过使用一个或多个CallBack方法同用户进行交互，获得用户输入。<br /><br />　　5. 向一个新的Subject对象中填入验证信息。<br /><br />　　我们将对代码作进一步的解释。但是在这之前，让我们先看代码中涉及到的核心JAAS类和接口。这些类可以被分为三种类型：<br /><br />　　普通类型 Subject，Principal，凭证<br /><br />　　验证 LoginContext，LoginModule，CallBackHandler，Callback<br /><br />　　授权 Policy，AuthPermission，PrivateCredentialPermission<br /><br />　　上面列举的类和接口大多数都在javax.security.auth包中。在J2SE 1.4中，还有一些接口的实现类在com.sun.security.auth包中。<br /><br />　　<b>普通类型：Subject，Principal，凭证</b><br /><br />　　Subject类代表了一个验证实体，它可以是用户、管理员、Web服务，设备或者其他的过程。该类包含了三中类型的安全信息：<br /><br />　　 身份（Identities）：由一个或多个Principal对象表示<br /><br />　　 公共凭证（Public credentials）：例如名称或公共秘钥<br /><br />　　 私有凭证（Private credentials）：例如口令或私有密钥<br /><br />　　Principal对象代表了Subject对象的身份。它们实现了java.security.Principal和java.io.Serializable接口。在Subject类中，最重要的方法是getName（）。该方法返回一个身份名称。在Subject对象中包含了多个Principal对象，因此它可以拥有多个名称。由于登录名称、身份证号和Email地址都可以作为用户的身份标识，可见拥有多个身份名称的情况在实际应用中是非常普遍的情况。<br /><br />　　在上面提到的凭证并不是一个特定的类或借口，它可以是任何对象。凭证中可以包含任何特定安全系统需要的验证信息，例如标签（ticket），密钥或口令。Subject对象中维护着一组特定的私有和公有的凭证，这些凭证可以通过getPrivateCredentials（）和getPublicCredentials（）方法获得。这些方法通常在应用程序层中的安全子系统被调用。<br /><br />　　<b>验证：LoginContext</b><br /><br />　　在应用程序层中，你可以使用LoginContext对象来验证Subject对象。LoginContext对象同时体现了JAAS的动态可插入性（Dynamic Pluggability），因为当你创建一个LoginContext的实例时，你需要指定一个配置。LoginContext通常从一个文本文件中加载配置信息，这些配置信息告诉LoginContext对象在登录时使用哪一个LoginModule对象。<br /><br />　　下面列出了在LoginContext中经常使用的三个方法： <br /><br />　　login () 进行登录操作。该方法激活了配置中制定的所有LoginModule对象。如果成功，它将创建一个经过了验证的Subject对象；否则抛出LoginException异常。<br /><br />　　getSubject () 返回经过验证的Subject对象<br /><br />　　logout () 注销Subject对象，删除与之相关的Principal对象和凭证<br /><br />　　验证：LoginModule<br /><br />　　LoginModule是调用特定验证机制的接口。J2EE 1.4中包含了下面几种LoginModule的实现类：<br /><br />　　JndiLoginModule 用于验证在JNDI中配置的目录服务<br /><br />　　Krb5LoginModule 使用Kerberos协议进行验证<br /><br />　　NTLoginModul 使用当前用户在NT中的用户信息进行验证<br /><br />　　UnixLoginModule 使用当前用户在Unix中的用户信息进行验证<br /><br />　　同上面这些模块绑定在一起的还有对应的Principal接口的实现类，例如NTDomainPrincipal和UnixPrincipal。这些类在com.sun.security.auth包中。<br /><br />　　LoginModule接口中包含了五个方法：<br /><br />　　initialize () 当创建一LoginModule实例时会被构造函数调用<br /><br />　　login () 进行验证<br /><br />　　commit () 当LgoninContext对象接受所有LoginModule对象传回的结果后将调用该方法。该方法将Principal对象和凭证赋给Subject对象。<br />　　<br />　　abort () 当任何一个LoginModule对象验证失败时都会调用该方法。此时没有任何Principal对象或凭证关联到Subject对象上。<br /><br />　　logout () 删除与Subject对象关联的Principal对象和凭证。<br /><br />　　在应用程序的代码中，程序员通常不会直接调用上面列出的方法，而是通过LigonContext间接调用这些方法。<br /><br />　　验证：CallbackHandler和Callback<br /><br />　　CallbackHandler和Callback对象可以使LoginModule对象从系统和用户那里收集必要的验证信息，同时独立于实际的收集信息时发生的交互过程。<br />　　<br />　　JAAS在javax.sevurity.auth.callback包中包含了七个Callback的实现类和两个CallbackHandler的实现类：ChoiceCallback、ConfirmationCallback、LogcaleCallback、NameCallback、PasswordCallback、TextInputCallback、TextOutputCallback、DialogCallbackHandler和TextCallBackHandler。Callback接口只会在客户端会被使用到。我将在后面介绍如何编写你自己的CallbackHandler类。<br /><br />　　<b><font color="#ac000">配置文件</font></b><br /><br />　　上面我已经提到，JAAS的可扩展性来源于它能够进行动态配置，而配置信息通常是保存在文本。这些文本文件有很多个配置块构成，我们通常把这些配置块称作申请（Application）。每个申请对应了一个或多个特定的LoginModule对象。<br /><br />　　当你的代码构造一个LoginContext对象时，你需要把配置文件中申请的名称传递给它。LoginContext将会根据申请中的信息决定激活哪些LoginModule对象，按照什么顺序激活以及使用什么规则激活。<br /><br />　　配置文件的结构如下所示:<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>Application {<br />ModuleClass Flag ModuleOptions;<br />ModuleClass Flag ModuleOptions;<br />...<br />};<br />Application {<br />ModuleClass Flag ModuleOptions;<br />...<br />};<br />...</td></tr></tbody></table><br />　　下面是一个名称为Sample的申请<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>Sample {<br />com.sun.security.auth.module.NTLoginModule Rquired debug=true;<br />}</td></tr></tbody></table><br />　　上面这个简单的申请指定了LoginContext对象应该使用NTLoginModule进行验证。类的名称在ModuleClass中被指定。Flag控制当申请中包含了多个LoginModule时进行登录时的行为：Required、Sufficient、Requisite和Optional。最常用的是Required，使用它意味着对应的LoginModule对象必须被调用，并且必须需要通过所有的验证。由于Flag本身的复杂性，本文在这里不作深究。<br /><br />　　ModuleOption允许有多个参数。例如你可以设定调试参数为True（debug=true），这样诊断输出将被送到System.out中。<br /><br />　　配置文件可以被任意命名，并且可以被放在任何位置。JAAS框架通过使用java.securty.auth.long.config属性来确定配置文件的位置。例如当你的应用程序是JaasTest，配置文件是当前目录下的jaas.config，你需要在命令行中输入：<br /><br />java -Djava.security.auth.login.config=jass.config JavaTest<br /><br />　　图二描述了配置文件中各元素之间的关系<br /><br /><table width="100" align="center"><tbody><tr align="middle"><td><img height="300" src="http://www.huihoo.com/java/j2ee/jt-2003-1-14-image002.gif" width="548" /><br />图二 JAAS的配置文件 </td></tr></tbody></table><br />　　<b><font color="#ac000">通过命令行方式进行登录验证</font></b><br /><br />　　为了说明JAAS到底能干什么，我在这里编写了两个例子。一个是简单的由命令行输入调用的程序，另一个是服务器端的JSP程序。这两个程序都通过用户名／密码的方式进行登录，然后使用关系数据库对其进行验证。<br /><br />　　为了通过数据库进行验证，我们需要：<br /><br />　　1. 实现RdbmsLoginModul类，该类可以对输入的信息进行验证。<br /><br />　　2. 编辑一个配置文件，告诉LoginContext如何使用RdbmsLoginModule。<br /><br />　　3. 实现ConsoleCallbackHandler类，通过该类可以获取用户的输入。<br /><br />　　4. 编写应用程序代码。<br /><br />　　在RdbmsLoginModul类中，我们必须实现LgoinModule接口中的五个方法。首先是initialize（）方法：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>public void initialize(Subject subject, CallbackHandler <br />callbackHandler,<br /><br />Map sharedState, Map options)<br />{<br />this.subject = subject;<br />this.callbackHandler = callbackHandler;<br />this.sharedState = sharedState;<br />this.options = options;<br /><br />url = (String)options.get("url");<br />driverClass = (String)options.get("driver");<br />debug = "true".equalsIgnoreCase((String)options.get("debug"));<br />}</td></tr></tbody></table><br />　　LoginContext在调用login（）方法时会调用initialize（）方法。RdbmsLoginModule的第一个任务就是在类中保存输入参数的引用。在验证成功后将向Subject对象中送入Principal对象和凭证。<br /><br />　　CallbackHandler对象将会在login（）方法中被使用到。sharedState可以使数据在不同的LoginModule对象之间共享，但是在这个例子中我们不会使用它。最后是名为options的Map对象。options向LgoinModule对象传递在配置文件ModuleOption域中定义的参数的值。配置文件如下所示：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>Example {<br />RdbmsLoginModule required<br />driver="org.gjt.mm.mysql.Driver"<br />url="jdbc:mysql://localhost/jaasdb?user=root"<br />debug="true";<br />};</td></tr></tbody></table><br />　　在配置文件中，RdbmsLoginModule包含了五个参数，其中driver、url、user和password是必需的，而debug是可选阐述。driver、url、user和password参数告诉我们如何获得JDBC连接。当然你还可以在ModuleOption域中加入数据库中的表或列的信息。使用这些参数的目的是为了能够对数据库进行操作。在LoginModule类的initialize（）方法中我们保存了每个参数的值。<br /><br />　　我们前面提到一个LoginContext对应的配置文件告诉它应该使用文件中的哪一个申请。这个信息是通过LgoinContext的构造函数传递的。下面是初始化客户端的代码，在代码中创建了一个LoginContex对象并调用了login（）方法。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>ConsoleCallbackHandler cbh = new ConsoleCallbackHandler();<br />LoginContext lc = new LoginContext("Example", cbh);<br />lc.login();</td></tr></tbody></table><br />　　当LgoinContext.login（）方法被调用时，它调用所有加载了的LoginModule对象的login（）方法。在我们的这个例子中是RdbmsLoginModule中的login（）方法。<br /><br />　　RdbmsLoginModule中的login（）方法进行了下面的操作：<br /><br />　　1. 创建两个Callback对象。这些对象从用户输入中获取用户名/密码。程序中使用了JAAS中的两个Callback类:NameCallback和PasswordCallback（这两个类包含在javax.security.auth.callback包中）。<br /><br />　　2. 通过将callbacks作为参数传递给CallbackHandler的handle（）方法来激活Callback。<br /><br />　　3. 通过Callback对象获得用户名／密码。<br /><br />　　4. 在rdbmsValidate（）方法中通过JDBC在数据库中验证获取的用户名／密码。<br /><br />　　下面是RdbmsLoginModule中的login（）方法的代码<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>public boolean login() throws LoginException {<br />if (callbackHandler == null)<br />throw new LoginException("no handler");<br /><br />NameCallback nameCb = new NameCallback("user: ");<br />PasswordCallback passCb = new PasswordCallback("password: ", true);<br />callbacks = new Callback[] { nameCb, passCb };<br />callbackHandler.handle(callbacks);<br /><br />String username = nameCb.getName();<br />String password = new String(passCb.getPassword());<br />success = rdbmsValidate(username, password);<br /><br />return(true);<br />}</td></tr></tbody></table><br />　　在ConsoleCallbackHandler类的handle（）方法中你可以看到Callback对象是如何同用户进行交互的：<br /><br />　　<b><font color="#ac000">使用JSP和关系数据库进行登录验证</font></b><br /><br />　　现在我们希望将通过命令行调用的程序一直到Web应用程序中。由于Web应用程序与一般的应用程序的交互方式有区别不同，我们将不能使用JAAS提供的标准Callback和CallbackHandler类。因为我们不能在Web程序中打开一个命令窗口让用户输入信息。也许你会想到我们也可以使用基于HTTP的验证，这样我们可以从浏览器弹出的用户名／密码窗口中获得用户输入。但是这样做也有一些问题，它要求建立起双向的HTTP连接（在login（）方法很难实现双向连接）。因此在我们的例子中我们会使用利用表单进行登录，从表单中获取用户输入的信息，然后通过RdbmsLoginModule类验证它。<br /><br />　　由于我们没有在应用层同LoginModule直接打交道，而是通过LgoinContext来调用其中的方法的，我们如何将获得用户名和密码放入LoginModule对象中呢？我们可以使用其它的方法来绕过这个问题，例如我们可以在创建LoginContext对象前先初始化一个Subject对象，在Subject对象的凭证中保存用户名和密码。然后我们可以将该Subject对象传递给LoginContext的构造函数。这种方法虽然从技术上来说没有什么问题，但是它在应用程序层增加了很多与安全机制相关的代码。而且通常是在验证后向Subject送入凭证，而不是之前。<br />前面我们提到可以实现一个CallbackHandler类，然后将它的实例传递给LoginContext对象。在这里我们可以采用类似的方法来处理用户名和密码。我们实现了一个新的类PassiveCallbackHandler。下面是在JSP中使用该类的代码：<br /><br /><script language="Javascript"><![CDATA[ocument.write("<img src='http://counter.yesky.com/servlet/counter.counter?CID=72348977504190464&AID=-1&refer="+escape(document.referrer)+"&cur="+escape(document.URL)+"' border='0' alt='' width='0' height='0'>");]]&gt;</script><img height="0" alt="" src="http://counter.yesky.com/servlet/counter.counter?CID=72348977504190464&amp;AID=-1&amp;refer=&amp;cur=http%3A//www.huihoo.com/java/j2ee/jaas.html" width="0" border="0" /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>String user = request.getParameter("user");<br />String pass = request.getParameter("pass");<br />PassiveCallbackHandler cbh = new PassiveCallbackHandler(user, pass);<br />LoginContext lc = new LoginContext("Example", cbh); </td></tr></tbody></table><br />　　PassiveCallbackHandler中构造函数的参数包含了用户名和密码。因此它可以在Callbick对象中设定正确的值。下面是PassiveCallbackHandler类的handle（）方法的代码：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>public void handle(Callback[] callbacks)<br />throws java.io.IOException, UnsupportedCallbackException {<br />for (int i = 0; i ＜ callbacks.length; i++) {<br />if (callbacks[i] instanceof NameCallback) {<br />NameCallback nameCb = (NameCallback)callbacks[i]; <br /><br />nameCb.setName(user);<br />} else if(callbacks[i] instanceof PasswordCallback) {<br />PasswordCallback passCb = (PasswordCallback)callbacks[i];<br />passCb.setPassword(pass.toCharArray());<br />} else {<br />throw(new UnsupportedCallbackException(callbacks[i],<br />"Callback class not supported"));<br />}<br />}<br />}<br />}</td></tr></tbody></table><br />　　从上面的代码中可以发现实际上我们只是从ConsoleCallbackHandler中去除了那些提示用户输入的代码。<br /><br />　　在运行这个JSP例子的时候，我们需要设定系统属性，这样LgoinContext对象才知道如何查找名称为"Example"的配置。我们使用的是Resin服务器。在resin.conf中，我们增加了一个＜caucho.com＞节点：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>＜system-property <br />java.security.auth.login.config="/resin/conf/jaas.config"/＞ </td></tr></tbody></table><br />　　<b><font color="#ac000">小结</font></b><br /><br />　　JAAS通过提供动态的、可扩展的模型来进行用户验证和控制权限，从而使应用程序有更加健壮的安全机制。同时它还能够让你能够很轻松地创建自己的登录机制。JAAS可以同时在在客户端和服务器端应用程序上工作。虽然在服务器端的JAAS到目前还不是很稳定，但是随着技术的发展，相信会很好地解决这个问题
<h4>(来源：转载自yesky网站，<a href="http://www.yesky.com/">http://www.yesky.com/</a>) <br /><br />　　<b><font color="#ac000">摘要：</font></b><br /><br />　　Java Authentication Authorization Service（JAAS，Java验证和授权API）提供了灵活和可伸缩的机制来保证客户端或服务器端的Java程序。Java早期的安全框架强调的是通过验证代码的来源和作者，保护用户避免受到下载下来的代码的攻击。JAAS强调的是通过验证谁在运行代码以及他／她的权限来保护系统面受用户的攻击。它让你能够将一些标准的安全机制，例如Solaris NIS（网络信息服务）、Windows NT、LDAP（轻量目录存取协议），Kerberos等通过一种通用的，可配置的方式集成到系统中。本文首先向你介绍JAAS验证中的一些核心部分，然后通过例子向你展示如何开发登录模块。<br /><br />　　你是否曾经需要为一个应用程序实现登录模块呢？如果你是一个比较有经验的程序员，相信你这样的工作做过很多次，而且每次都不完全一样。你有可能把你的登录模块建立在Oracle数据库的基础上，也有可能使用的是NT的用户验证，或者使用的是LDAP目录。如果有一种方法可以在不改变应用程序级的代码的基础上支持上面提到的所有这一些安全机制，对于程序员来说一定是一件幸运的事。<br /><br />　　现在你可以使用JAAS实现上面的目标。JAAS是一个比较新的的Java API。在J2SE 1.3中，它是一个扩展包；在J2SE 1.4中变成了一个核心包。在本文中，我们将介绍JAAS的一些核心概念，然后通过例子说明如何将JAAS应用到实际的程序中。本文的例子是根据我们一个基于Web的Java应用程序进行改编的，在这个例子中，我们使用了关系数据库保存用户的登录信息。由于使用了JAAS，我们实现了一个健壮而灵活的登录和身份验证模块。<br /><br />　　<b><font color="#ac000">Java验证和授权：概论</font></b><br /><br />　　在JAAS出现以前，Java的安全模型是为了满足跨平台的网络应用程序的需要而设计的。在Java早期版本中，Java通常是作为远程代码被使用，例如Applet，。因此最初的安全模型把注意力放在通过验证代码的来源来保护用户上。早期的Java安全机制中包含的概念，如SercurityManager，沙箱概念，代码签名，策略文件，多是为了保护用户。<br /><br />　　JAAS的出现反映了Java的演变。传统的服务器／客户端程序需要实现登录和存取控制，JAAS通过对运行程序的用户的进行验证，从而达到保护系统的目的。虽然JAAS同时具有验证和授权的能力，在这篇文章中，我们主要介绍验证功能。<br /><br />　　通过在应用程序和底层的验证和授权机制之间加入一个抽象层，JAAS可以简化涉及到Java Security包的程序开发。抽象层独立于平台的特性使开发人员可以使用各种不同的安全机制，而且不用修改应用程序级的代码。和其他Java Security API相似，JAAS通过一个可扩展的框架：服务提供者接口（Service Provider Interface，SPI）来保证程序独立于安全机制。服务提供者接口是由一组抽象类和接口组成的。图一中给出了JAAS程序的整体框架图。应用程序级的代码主要处理LoginContext。在LoginContext下面是一组动态配置的LoginModules。LoginModule使用正确的安全机制进行验证。<br /><br />　　图一给出了JAAS的概览。应用程序层的代码只需要和LoginContext打交道。在LoginContext之下是一组动态配置的LoginModule对象，这些对象使用相关的安全基础结构进行验证操作。<br /><br /><table width="100" align="center"><tbody><tr align="middle"><td><img height="396" src="http://www.huihoo.com/java/j2ee/jt-2003-1-14-image001.gif" width="573" /><br />图一 JAAS概览 </td></tr></tbody></table><br />　　JAAS提供了一些LoginModule的参考实现代码，比如JndiLoginModule。开发人员也可以自己实现LoginModule接口，就象在我们例子中的RdbmsLonginModule。同时我们还会告诉你如何使用一个简单的配置文件来安装应用程序。<br /><br />　　为了满足可插接性，JAAS是可堆叠的。在单一登录的情况下，一组安全模块可以堆叠在一起，然后被其他的安全机制按照堆叠的顺序被调用。<br /><br />　　JAAS的实现者根据现在一些流行的安全结构模式和框架将JASS模型化。例如可堆叠的特性同Unix下的可堆叠验证模块（PAM，Pluggable Authentication Module）框架就非常相似。从事务的角度看，JAAS类似于双步提交（Two-Phase Commit，2PC）协议的行为。JAAS中安全配置的概念（包括策略文件（Police File）和许可（Permission））来自于J2SE 1.2。JAAS还从其他成熟的安全框架中借鉴了许多思想。<br /><br /><br /><br />　　<b><font color="#ac000">客户端和服务器端的JAAS</font></b><br /><br />　　开发人员可以将JAAS应用到客户端和服务器端。在客户端使用JAAS很简单。在服务器端使用JAAS时情况要复杂一些。目前在应用服务器市场中的JAAS产品还不是很一致，使用JAAS的J2EE应用服务器有一些细微的差别。例如JBossSx使用自己的结构，将JAAS集成到了一个更大的安全框架中；而虽然WebLogic 6.x也使用了JAAS，安全框架却完全不一样。<br /><br />　　现在你能够理解为什么我们需要从客户端和服务器端的角度来看JAAS了。我们将在后面列出两种情况下的例子。为了使服务器端的例子程序更加简单，我们使用了Resin应用服务器。<br /><br />　　<b><font color="#ac000">核心JAAS类</font></b><br /><br />　　在使用JAAS之前，你首先需要安装JAAS。在J2SE 1.4中已经包括了JAAS，但是在J2SE 1.3中没有。如果你希望使用J2SE 1.3，你可以从SUN的官方站点上下载JAAS。当正确安装了JAAS后，你会在安装目录的lib目录下找到jaas.jar。你需要将该路径加入Classpath中。（注：如果你安装了应用服务器，其中就已经包括了JAAS，请阅读应用服务器的帮助文档以获得更详细的信息）。在Java安全属性文件java.security中，你可以改变一些与JAAS相关的系统属性。该文件保存在＜jre_home＞/lib/security目录中。<br /><br />　　在应用程序中使用JAAS验证通常会涉及到以下几个步骤：<br /><br />　　1. 创建一个LoginContext的实例。<br /><br />　　2. 为了能够获得和处理验证信息，将一个CallBackHandler对象作为参数传送给LoginContext。<br /><br />　　3. 通过调用LoginContext的login（）方法来进行验证。<br /><br />　　4. 通过使用login（）方法返回的Subject对象实现一些特殊的功能（假设登录成功）。<br /><br />　　下面是一个简单的例子：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>LoginContext lc = new LoginContext("MyExample");<br />try {<br />lc.login();<br />} catch (LoginException) {<br />// Authentication failed.<br />}<br /><br />// Authentication successful, we can now continue.<br />// We can use the returned Subject if we like.<br />Subject sub = lc.getSubject();<br />Subject.doAs(sub, new MyPrivilegedAction());</td></tr></tbody></table><br />　　在运行这段代码时，后台进行了以下的工作。<br /><br />　　1. 当初始化时，LoginContext对象首先在JAAS配置文件中找到MyExample项，然后更具该项的内容决定该加载哪个LoginModule对象（参见图二）。<br /><br />　　2. 在登录时，LoginContext对象调用每个LoginModule对象的login（）方法。<br /><br />　　3. 每个login（）方法进行验证操作或获得一个CallbackHandle对象。<br /><br />　　4. CallbackHandle对象通过使用一个或多个CallBack方法同用户进行交互，获得用户输入。<br /><br />　　5. 向一个新的Subject对象中填入验证信息。<br /><br />　　我们将对代码作进一步的解释。但是在这之前，让我们先看代码中涉及到的核心JAAS类和接口。这些类可以被分为三种类型：<br /><br />　　普通类型 Subject，Principal，凭证<br /><br />　　验证 LoginContext，LoginModule，CallBackHandler，Callback<br /><br />　　授权 Policy，AuthPermission，PrivateCredentialPermission<br /><br />　　上面列举的类和接口大多数都在javax.security.auth包中。在J2SE 1.4中，还有一些接口的实现类在com.sun.security.auth包中。<br /><br />　　<b>普通类型：Subject，Principal，凭证</b><br /><br />　　Subject类代表了一个验证实体，它可以是用户、管理员、Web服务，设备或者其他的过程。该类包含了三中类型的安全信息：<br /><br />　　 身份（Identities）：由一个或多个Principal对象表示<br /><br />　　 公共凭证（Public credentials）：例如名称或公共秘钥<br /><br />　　 私有凭证（Private credentials）：例如口令或私有密钥<br /><br />　　Principal对象代表了Subject对象的身份。它们实现了java.security.Principal和java.io.Serializable接口。在Subject类中，最重要的方法是getName（）。该方法返回一个身份名称。在Subject对象中包含了多个Principal对象，因此它可以拥有多个名称。由于登录名称、身份证号和Email地址都可以作为用户的身份标识，可见拥有多个身份名称的情况在实际应用中是非常普遍的情况。<br /><br />　　在上面提到的凭证并不是一个特定的类或借口，它可以是任何对象。凭证中可以包含任何特定安全系统需要的验证信息，例如标签（ticket），密钥或口令。Subject对象中维护着一组特定的私有和公有的凭证，这些凭证可以通过getPrivateCredentials（）和getPublicCredentials（）方法获得。这些方法通常在应用程序层中的安全子系统被调用。<br /><br />　　<b>验证：LoginContext</b><br /><br />　　在应用程序层中，你可以使用LoginContext对象来验证Subject对象。LoginContext对象同时体现了JAAS的动态可插入性（Dynamic Pluggability），因为当你创建一个LoginContext的实例时，你需要指定一个配置。LoginContext通常从一个文本文件中加载配置信息，这些配置信息告诉LoginContext对象在登录时使用哪一个LoginModule对象。<br /><br />　　下面列出了在LoginContext中经常使用的三个方法： <br /><br />　　login () 进行登录操作。该方法激活了配置中制定的所有LoginModule对象。如果成功，它将创建一个经过了验证的Subject对象；否则抛出LoginException异常。<br /><br />　　getSubject () 返回经过验证的Subject对象<br /><br />　　logout () 注销Subject对象，删除与之相关的Principal对象和凭证<br /><br />　　验证：LoginModule<br /><br />　　LoginModule是调用特定验证机制的接口。J2EE 1.4中包含了下面几种LoginModule的实现类：<br /><br />　　JndiLoginModule 用于验证在JNDI中配置的目录服务<br /><br />　　Krb5LoginModule 使用Kerberos协议进行验证<br /><br />　　NTLoginModul 使用当前用户在NT中的用户信息进行验证<br /><br />　　UnixLoginModule 使用当前用户在Unix中的用户信息进行验证<br /><br />　　同上面这些模块绑定在一起的还有对应的Principal接口的实现类，例如NTDomainPrincipal和UnixPrincipal。这些类在com.sun.security.auth包中。<br /><br />　　LoginModule接口中包含了五个方法：<br /><br />　　initialize () 当创建一LoginModule实例时会被构造函数调用<br /><br />　　login () 进行验证<br /><br />　　commit () 当LgoninContext对象接受所有LoginModule对象传回的结果后将调用该方法。该方法将Principal对象和凭证赋给Subject对象。<br />　　<br />　　abort () 当任何一个LoginModule对象验证失败时都会调用该方法。此时没有任何Principal对象或凭证关联到Subject对象上。<br /><br />　　logout () 删除与Subject对象关联的Principal对象和凭证。<br /><br />　　在应用程序的代码中，程序员通常不会直接调用上面列出的方法，而是通过LigonContext间接调用这些方法。<br /><br />　　验证：CallbackHandler和Callback<br /><br />　　CallbackHandler和Callback对象可以使LoginModule对象从系统和用户那里收集必要的验证信息，同时独立于实际的收集信息时发生的交互过程。<br />　　<br />　　JAAS在javax.sevurity.auth.callback包中包含了七个Callback的实现类和两个CallbackHandler的实现类：ChoiceCallback、ConfirmationCallback、LogcaleCallback、NameCallback、PasswordCallback、TextInputCallback、TextOutputCallback、DialogCallbackHandler和TextCallBackHandler。Callback接口只会在客户端会被使用到。我将在后面介绍如何编写你自己的CallbackHandler类。<br /><br />　　<b><font color="#ac000">配置文件</font></b><br /><br />　　上面我已经提到，JAAS的可扩展性来源于它能够进行动态配置，而配置信息通常是保存在文本。这些文本文件有很多个配置块构成，我们通常把这些配置块称作申请（Application）。每个申请对应了一个或多个特定的LoginModule对象。<br /><br />　　当你的代码构造一个LoginContext对象时，你需要把配置文件中申请的名称传递给它。LoginContext将会根据申请中的信息决定激活哪些LoginModule对象，按照什么顺序激活以及使用什么规则激活。<br /><br />　　配置文件的结构如下所示:<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>Application {<br />ModuleClass Flag ModuleOptions;<br />ModuleClass Flag ModuleOptions;<br />...<br />};<br />Application {<br />ModuleClass Flag ModuleOptions;<br />...<br />};<br />...</td></tr></tbody></table><br />　　下面是一个名称为Sample的申请<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>Sample {<br />com.sun.security.auth.module.NTLoginModule Rquired debug=true;<br />}</td></tr></tbody></table><br />　　上面这个简单的申请指定了LoginContext对象应该使用NTLoginModule进行验证。类的名称在ModuleClass中被指定。Flag控制当申请中包含了多个LoginModule时进行登录时的行为：Required、Sufficient、Requisite和Optional。最常用的是Required，使用它意味着对应的LoginModule对象必须被调用，并且必须需要通过所有的验证。由于Flag本身的复杂性，本文在这里不作深究。<br /><br />　　ModuleOption允许有多个参数。例如你可以设定调试参数为True（debug=true），这样诊断输出将被送到System.out中。<br /><br />　　配置文件可以被任意命名，并且可以被放在任何位置。JAAS框架通过使用java.securty.auth.long.config属性来确定配置文件的位置。例如当你的应用程序是JaasTest，配置文件是当前目录下的jaas.config，你需要在命令行中输入：<br /><br />java -Djava.security.auth.login.config=jass.config JavaTest<br /><br />　　图二描述了配置文件中各元素之间的关系<br /><br /><table width="100" align="center"><tbody><tr align="middle"><td><img height="300" src="http://www.huihoo.com/java/j2ee/jt-2003-1-14-image002.gif" width="548" /><br />图二 JAAS的配置文件 </td></tr></tbody></table><br />　　<b><font color="#ac000">通过命令行方式进行登录验证</font></b><br /><br />　　为了说明JAAS到底能干什么，我在这里编写了两个例子。一个是简单的由命令行输入调用的程序，另一个是服务器端的JSP程序。这两个程序都通过用户名／密码的方式进行登录，然后使用关系数据库对其进行验证。<br /><br />　　为了通过数据库进行验证，我们需要：<br /><br />　　1. 实现RdbmsLoginModul类，该类可以对输入的信息进行验证。<br /><br />　　2. 编辑一个配置文件，告诉LoginContext如何使用RdbmsLoginModule。<br /><br />　　3. 实现ConsoleCallbackHandler类，通过该类可以获取用户的输入。<br /><br />　　4. 编写应用程序代码。<br /><br />　　在RdbmsLoginModul类中，我们必须实现LgoinModule接口中的五个方法。首先是initialize（）方法：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>public void initialize(Subject subject, CallbackHandler <br />callbackHandler,<br /><br />Map sharedState, Map options)<br />{<br />this.subject = subject;<br />this.callbackHandler = callbackHandler;<br />this.sharedState = sharedState;<br />this.options = options;<br /><br />url = (String)options.get("url");<br />driverClass = (String)options.get("driver");<br />debug = "true".equalsIgnoreCase((String)options.get("debug"));<br />}</td></tr></tbody></table><br />　　LoginContext在调用login（）方法时会调用initialize（）方法。RdbmsLoginModule的第一个任务就是在类中保存输入参数的引用。在验证成功后将向Subject对象中送入Principal对象和凭证。<br /><br />　　CallbackHandler对象将会在login（）方法中被使用到。sharedState可以使数据在不同的LoginModule对象之间共享，但是在这个例子中我们不会使用它。最后是名为options的Map对象。options向LgoinModule对象传递在配置文件ModuleOption域中定义的参数的值。配置文件如下所示：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>Example {<br />RdbmsLoginModule required<br />driver="org.gjt.mm.mysql.Driver"<br />url="jdbc:mysql://localhost/jaasdb?user=root"<br />debug="true";<br />};</td></tr></tbody></table><br />　　在配置文件中，RdbmsLoginModule包含了五个参数，其中driver、url、user和password是必需的，而debug是可选阐述。driver、url、user和password参数告诉我们如何获得JDBC连接。当然你还可以在ModuleOption域中加入数据库中的表或列的信息。使用这些参数的目的是为了能够对数据库进行操作。在LoginModule类的initialize（）方法中我们保存了每个参数的值。<br /><br />　　我们前面提到一个LoginContext对应的配置文件告诉它应该使用文件中的哪一个申请。这个信息是通过LgoinContext的构造函数传递的。下面是初始化客户端的代码，在代码中创建了一个LoginContex对象并调用了login（）方法。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>ConsoleCallbackHandler cbh = new ConsoleCallbackHandler();<br />LoginContext lc = new LoginContext("Example", cbh);<br />lc.login();</td></tr></tbody></table><br />　　当LgoinContext.login（）方法被调用时，它调用所有加载了的LoginModule对象的login（）方法。在我们的这个例子中是RdbmsLoginModule中的login（）方法。<br /><br />　　RdbmsLoginModule中的login（）方法进行了下面的操作：<br /><br />　　1. 创建两个Callback对象。这些对象从用户输入中获取用户名/密码。程序中使用了JAAS中的两个Callback类:NameCallback和PasswordCallback（这两个类包含在javax.security.auth.callback包中）。<br /><br />　　2. 通过将callbacks作为参数传递给CallbackHandler的handle（）方法来激活Callback。<br /><br />　　3. 通过Callback对象获得用户名／密码。<br /><br />　　4. 在rdbmsValidate（）方法中通过JDBC在数据库中验证获取的用户名／密码。<br /><br />　　下面是RdbmsLoginModule中的login（）方法的代码<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>public boolean login() throws LoginException {<br />if (callbackHandler == null)<br />throw new LoginException("no handler");<br /><br />NameCallback nameCb = new NameCallback("user: ");<br />PasswordCallback passCb = new PasswordCallback("password: ", true);<br />callbacks = new Callback[] { nameCb, passCb };<br />callbackHandler.handle(callbacks);<br /><br />String username = nameCb.getName();<br />String password = new String(passCb.getPassword());<br />success = rdbmsValidate(username, password);<br /><br />return(true);<br />}</td></tr></tbody></table><br />　　在ConsoleCallbackHandler类的handle（）方法中你可以看到Callback对象是如何同用户进行交互的：<br /><br />　　<b><font color="#ac000">使用JSP和关系数据库进行登录验证</font></b><br /><br />　　现在我们希望将通过命令行调用的程序一直到Web应用程序中。由于Web应用程序与一般的应用程序的交互方式有区别不同，我们将不能使用JAAS提供的标准Callback和CallbackHandler类。因为我们不能在Web程序中打开一个命令窗口让用户输入信息。也许你会想到我们也可以使用基于HTTP的验证，这样我们可以从浏览器弹出的用户名／密码窗口中获得用户输入。但是这样做也有一些问题，它要求建立起双向的HTTP连接（在login（）方法很难实现双向连接）。因此在我们的例子中我们会使用利用表单进行登录，从表单中获取用户输入的信息，然后通过RdbmsLoginModule类验证它。<br /><br />　　由于我们没有在应用层同LoginModule直接打交道，而是通过LgoinContext来调用其中的方法的，我们如何将获得用户名和密码放入LoginModule对象中呢？我们可以使用其它的方法来绕过这个问题，例如我们可以在创建LoginContext对象前先初始化一个Subject对象，在Subject对象的凭证中保存用户名和密码。然后我们可以将该Subject对象传递给LoginContext的构造函数。这种方法虽然从技术上来说没有什么问题，但是它在应用程序层增加了很多与安全机制相关的代码。而且通常是在验证后向Subject送入凭证，而不是之前。<br />前面我们提到可以实现一个CallbackHandler类，然后将它的实例传递给LoginContext对象。在这里我们可以采用类似的方法来处理用户名和密码。我们实现了一个新的类PassiveCallbackHandler。下面是在JSP中使用该类的代码：<br /><br /><script language="Javascript"><![CDATA[ocument.write("<img src='http://counter.yesky.com/servlet/counter.counter?CID=72348977504190464&AID=-1&refer="+escape(document.referrer)+"&cur="+escape(document.URL)+"' border='0' alt='' width='0' height='0'>");]]&gt;</script><img height="0" alt="" src="http://counter.yesky.com/servlet/counter.counter?CID=72348977504190464&amp;AID=-1&amp;refer=&amp;cur=http%3A//www.huihoo.com/java/j2ee/jaas.html" width="0" border="0" /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>String user = request.getParameter("user");<br />String pass = request.getParameter("pass");<br />PassiveCallbackHandler cbh = new PassiveCallbackHandler(user, pass);<br />LoginContext lc = new LoginContext("Example", cbh); </td></tr></tbody></table><br />　　PassiveCallbackHandler中构造函数的参数包含了用户名和密码。因此它可以在Callbick对象中设定正确的值。下面是PassiveCallbackHandler类的handle（）方法的代码：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>public void handle(Callback[] callbacks)<br />throws java.io.IOException, UnsupportedCallbackException {<br />for (int i = 0; i ＜ callbacks.length; i++) {<br />if (callbacks[i] instanceof NameCallback) {<br />NameCallback nameCb = (NameCallback)callbacks[i]; <br /><br />nameCb.setName(user);<br />} else if(callbacks[i] instanceof PasswordCallback) {<br />PasswordCallback passCb = (PasswordCallback)callbacks[i];<br />passCb.setPassword(pass.toCharArray());<br />} else {<br />throw(new UnsupportedCallbackException(callbacks[i],<br />"Callback class not supported"));<br />}<br />}<br />}<br />}</td></tr></tbody></table><br />　　从上面的代码中可以发现实际上我们只是从ConsoleCallbackHandler中去除了那些提示用户输入的代码。<br /><br />　　在运行这个JSP例子的时候，我们需要设定系统属性，这样LgoinContext对象才知道如何查找名称为"Example"的配置。我们使用的是Resin服务器。在resin.conf中，我们增加了一个＜caucho.com＞节点：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>＜system-property <br />java.security.auth.login.config="/resin/conf/jaas.config"/＞ </td></tr></tbody></table><br />　　<b><font color="#ac000">小结</font></b><br /><br />　　JAAS通过提供动态的、可扩展的模型来进行用户验证和控制权限，从而使应用程序有更加健壮的安全机制。同时它还能够让你能够很轻松地创建自己的登录机制。JAAS可以同时在在客户端和服务器端应用程序上工作。虽然在服务器端的JAAS到目前还不是很稳定，但是随着技术的发展，相信会很好地解决这个问题
<h4>(来源：转载自yesky网站，<a href="http://www.yesky.com/">http://www.yesky.com/</a>) <br /><br />　　<b><font color="#ac000">摘要：</font></b><br /><br />　　Java Authentication Authorization Service（JAAS，Java验证和授权API）提供了灵活和可伸缩的机制来保证客户端或服务器端的Java程序。Java早期的安全框架强调的是通过验证代码的来源和作者，保护用户避免受到下载下来的代码的攻击。JAAS强调的是通过验证谁在运行代码以及他／她的权限来保护系统面受用户的攻击。它让你能够将一些标准的安全机制，例如Solaris NIS（网络信息服务）、Windows NT、LDAP（轻量目录存取协议），Kerberos等通过一种通用的，可配置的方式集成到系统中。本文首先向你介绍JAAS验证中的一些核心部分，然后通过例子向你展示如何开发登录模块。<br /><br />　　你是否曾经需要为一个应用程序实现登录模块呢？如果你是一个比较有经验的程序员，相信你这样的工作做过很多次，而且每次都不完全一样。你有可能把你的登录模块建立在Oracle数据库的基础上，也有可能使用的是NT的用户验证，或者使用的是LDAP目录。如果有一种方法可以在不改变应用程序级的代码的基础上支持上面提到的所有这一些安全机制，对于程序员来说一定是一件幸运的事。<br /><br />　　现在你可以使用JAAS实现上面的目标。JAAS是一个比较新的的Java API。在J2SE 1.3中，它是一个扩展包；在J2SE 1.4中变成了一个核心包。在本文中，我们将介绍JAAS的一些核心概念，然后通过例子说明如何将JAAS应用到实际的程序中。本文的例子是根据我们一个基于Web的Java应用程序进行改编的，在这个例子中，我们使用了关系数据库保存用户的登录信息。由于使用了JAAS，我们实现了一个健壮而灵活的登录和身份验证模块。<br /><br />　　<b><font color="#ac000">Java验证和授权：概论</font></b><br /><br />　　在JAAS出现以前，Java的安全模型是为了满足跨平台的网络应用程序的需要而设计的。在Java早期版本中，Java通常是作为远程代码被使用，例如Applet，。因此最初的安全模型把注意力放在通过验证代码的来源来保护用户上。早期的Java安全机制中包含的概念，如SercurityManager，沙箱概念，代码签名，策略文件，多是为了保护用户。<br /><br />　　JAAS的出现反映了Java的演变。传统的服务器／客户端程序需要实现登录和存取控制，JAAS通过对运行程序的用户的进行验证，从而达到保护系统的目的。虽然JAAS同时具有验证和授权的能力，在这篇文章中，我们主要介绍验证功能。<br /><br />　　通过在应用程序和底层的验证和授权机制之间加入一个抽象层，JAAS可以简化涉及到Java Security包的程序开发。抽象层独立于平台的特性使开发人员可以使用各种不同的安全机制，而且不用修改应用程序级的代码。和其他Java Security API相似，JAAS通过一个可扩展的框架：服务提供者接口（Service Provider Interface，SPI）来保证程序独立于安全机制。服务提供者接口是由一组抽象类和接口组成的。图一中给出了JAAS程序的整体框架图。应用程序级的代码主要处理LoginContext。在LoginContext下面是一组动态配置的LoginModules。LoginModule使用正确的安全机制进行验证。<br /><br />　　图一给出了JAAS的概览。应用程序层的代码只需要和LoginContext打交道。在LoginContext之下是一组动态配置的LoginModule对象，这些对象使用相关的安全基础结构进行验证操作。<br /><br /><table width="100" align="center"><tbody><tr align="middle"><td><img height="396" src="http://www.huihoo.com/java/j2ee/jt-2003-1-14-image001.gif" width="573" /><br />图一 JAAS概览 </td></tr></tbody></table><br />　　JAAS提供了一些LoginModule的参考实现代码，比如JndiLoginModule。开发人员也可以自己实现LoginModule接口，就象在我们例子中的RdbmsLonginModule。同时我们还会告诉你如何使用一个简单的配置文件来安装应用程序。<br /><br />　　为了满足可插接性，JAAS是可堆叠的。在单一登录的情况下，一组安全模块可以堆叠在一起，然后被其他的安全机制按照堆叠的顺序被调用。<br /><br />　　JAAS的实现者根据现在一些流行的安全结构模式和框架将JASS模型化。例如可堆叠的特性同Unix下的可堆叠验证模块（PAM，Pluggable Authentication Module）框架就非常相似。从事务的角度看，JAAS类似于双步提交（Two-Phase Commit，2PC）协议的行为。JAAS中安全配置的概念（包括策略文件（Police File）和许可（Permission））来自于J2SE 1.2。JAAS还从其他成熟的安全框架中借鉴了许多思想。<br /><br /><br /><br />　　<b><font color="#ac000">客户端和服务器端的JAAS</font></b><br /><br />　　开发人员可以将JAAS应用到客户端和服务器端。在客户端使用JAAS很简单。在服务器端使用JAAS时情况要复杂一些。目前在应用服务器市场中的JAAS产品还不是很一致，使用JAAS的J2EE应用服务器有一些细微的差别。例如JBossSx使用自己的结构，将JAAS集成到了一个更大的安全框架中；而虽然WebLogic 6.x也使用了JAAS，安全框架却完全不一样。<br /><br />　　现在你能够理解为什么我们需要从客户端和服务器端的角度来看JAAS了。我们将在后面列出两种情况下的例子。为了使服务器端的例子程序更加简单，我们使用了Resin应用服务器。<br /><br />　　<b><font color="#ac000">核心JAAS类</font></b><br /><br />　　在使用JAAS之前，你首先需要安装JAAS。在J2SE 1.4中已经包括了JAAS，但是在J2SE 1.3中没有。如果你希望使用J2SE 1.3，你可以从SUN的官方站点上下载JAAS。当正确安装了JAAS后，你会在安装目录的lib目录下找到jaas.jar。你需要将该路径加入Classpath中。（注：如果你安装了应用服务器，其中就已经包括了JAAS，请阅读应用服务器的帮助文档以获得更详细的信息）。在Java安全属性文件java.security中，你可以改变一些与JAAS相关的系统属性。该文件保存在＜jre_home＞/lib/security目录中。<br /><br />　　在应用程序中使用JAAS验证通常会涉及到以下几个步骤：<br /><br />　　1. 创建一个LoginContext的实例。<br /><br />　　2. 为了能够获得和处理验证信息，将一个CallBackHandler对象作为参数传送给LoginContext。<br /><br />　　3. 通过调用LoginContext的login（）方法来进行验证。<br /><br />　　4. 通过使用login（）方法返回的Subject对象实现一些特殊的功能（假设登录成功）。<br /><br />　　下面是一个简单的例子：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>LoginContext lc = new LoginContext("MyExample");<br />try {<br />lc.login();<br />} catch (LoginException) {<br />// Authentication failed.<br />}<br /><br />// Authentication successful, we can now continue.<br />// We can use the returned Subject if we like.<br />Subject sub = lc.getSubject();<br />Subject.doAs(sub, new MyPrivilegedAction());</td></tr></tbody></table><br />　　在运行这段代码时，后台进行了以下的工作。<br /><br />　　1. 当初始化时，LoginContext对象首先在JAAS配置文件中找到MyExample项，然后更具该项的内容决定该加载哪个LoginModule对象（参见图二）。<br /><br />　　2. 在登录时，LoginContext对象调用每个LoginModule对象的login（）方法。<br /><br />　　3. 每个login（）方法进行验证操作或获得一个CallbackHandle对象。<br /><br />　　4. CallbackHandle对象通过使用一个或多个CallBack方法同用户进行交互，获得用户输入。<br /><br />　　5. 向一个新的Subject对象中填入验证信息。<br /><br />　　我们将对代码作进一步的解释。但是在这之前，让我们先看代码中涉及到的核心JAAS类和接口。这些类可以被分为三种类型：<br /><br />　　普通类型 Subject，Principal，凭证<br /><br />　　验证 LoginContext，LoginModule，CallBackHandler，Callback<br /><br />　　授权 Policy，AuthPermission，PrivateCredentialPermission<br /><br />　　上面列举的类和接口大多数都在javax.security.auth包中。在J2SE 1.4中，还有一些接口的实现类在com.sun.security.auth包中。<br /><br />　　<b>普通类型：Subject，Principal，凭证</b><br /><br />　　Subject类代表了一个验证实体，它可以是用户、管理员、Web服务，设备或者其他的过程。该类包含了三中类型的安全信息：<br /><br />　　 身份（Identities）：由一个或多个Principal对象表示<br /><br />　　 公共凭证（Public credentials）：例如名称或公共秘钥<br /><br />　　 私有凭证（Private credentials）：例如口令或私有密钥<br /><br />　　Principal对象代表了Subject对象的身份。它们实现了java.security.Principal和java.io.Serializable接口。在Subject类中，最重要的方法是getName（）。该方法返回一个身份名称。在Subject对象中包含了多个Principal对象，因此它可以拥有多个名称。由于登录名称、身份证号和Email地址都可以作为用户的身份标识，可见拥有多个身份名称的情况在实际应用中是非常普遍的情况。<br /><br />　　在上面提到的凭证并不是一个特定的类或借口，它可以是任何对象。凭证中可以包含任何特定安全系统需要的验证信息，例如标签（ticket），密钥或口令。Subject对象中维护着一组特定的私有和公有的凭证，这些凭证可以通过getPrivateCredentials（）和getPublicCredentials（）方法获得。这些方法通常在应用程序层中的安全子系统被调用。<br /><br />　　<b>验证：LoginContext</b><br /><br />　　在应用程序层中，你可以使用LoginContext对象来验证Subject对象。LoginContext对象同时体现了JAAS的动态可插入性（Dynamic Pluggability），因为当你创建一个LoginContext的实例时，你需要指定一个配置。LoginContext通常从一个文本文件中加载配置信息，这些配置信息告诉LoginContext对象在登录时使用哪一个LoginModule对象。<br /><br />　　下面列出了在LoginContext中经常使用的三个方法： <br /><br />　　login () 进行登录操作。该方法激活了配置中制定的所有LoginModule对象。如果成功，它将创建一个经过了验证的Subject对象；否则抛出LoginException异常。<br /><br />　　getSubject () 返回经过验证的Subject对象<br /><br />　　logout () 注销Subject对象，删除与之相关的Principal对象和凭证<br /><br />　　验证：LoginModule<br /><br />　　LoginModule是调用特定验证机制的接口。J2EE 1.4中包含了下面几种LoginModule的实现类：<br /><br />　　JndiLoginModule 用于验证在JNDI中配置的目录服务<br /><br />　　Krb5LoginModule 使用Kerberos协议进行验证<br /><br />　　NTLoginModul 使用当前用户在NT中的用户信息进行验证<br /><br />　　UnixLoginModule 使用当前用户在Unix中的用户信息进行验证<br /><br />　　同上面这些模块绑定在一起的还有对应的Principal接口的实现类，例如NTDomainPrincipal和UnixPrincipal。这些类在com.sun.security.auth包中。<br /><br />　　LoginModule接口中包含了五个方法：<br /><br />　　initialize () 当创建一LoginModule实例时会被构造函数调用<br /><br />　　login () 进行验证<br /><br />　　commit () 当LgoninContext对象接受所有LoginModule对象传回的结果后将调用该方法。该方法将Principal对象和凭证赋给Subject对象。<br />　　<br />　　abort () 当任何一个LoginModule对象验证失败时都会调用该方法。此时没有任何Principal对象或凭证关联到Subject对象上。<br /><br />　　logout () 删除与Subject对象关联的Principal对象和凭证。<br /><br />　　在应用程序的代码中，程序员通常不会直接调用上面列出的方法，而是通过LigonContext间接调用这些方法。<br /><br />　　验证：CallbackHandler和Callback<br /><br />　　CallbackHandler和Callback对象可以使LoginModule对象从系统和用户那里收集必要的验证信息，同时独立于实际的收集信息时发生的交互过程。<br />　　<br />　　JAAS在javax.sevurity.auth.callback包中包含了七个Callback的实现类和两个CallbackHandler的实现类：ChoiceCallback、ConfirmationCallback、LogcaleCallback、NameCallback、PasswordCallback、TextInputCallback、TextOutputCallback、DialogCallbackHandler和TextCallBackHandler。Callback接口只会在客户端会被使用到。我将在后面介绍如何编写你自己的CallbackHandler类。<br /><br />　　<b><font color="#ac000">配置文件</font></b><br /><br />　　上面我已经提到，JAAS的可扩展性来源于它能够进行动态配置，而配置信息通常是保存在文本。这些文本文件有很多个配置块构成，我们通常把这些配置块称作申请（Application）。每个申请对应了一个或多个特定的LoginModule对象。<br /><br />　　当你的代码构造一个LoginContext对象时，你需要把配置文件中申请的名称传递给它。LoginContext将会根据申请中的信息决定激活哪些LoginModule对象，按照什么顺序激活以及使用什么规则激活。<br /><br />　　配置文件的结构如下所示:<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>Application {<br />ModuleClass Flag ModuleOptions;<br />ModuleClass Flag ModuleOptions;<br />...<br />};<br />Application {<br />ModuleClass Flag ModuleOptions;<br />...<br />};<br />...</td></tr></tbody></table><br />　　下面是一个名称为Sample的申请<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>Sample {<br />com.sun.security.auth.module.NTLoginModule Rquired debug=true;<br />}</td></tr></tbody></table><br />　　上面这个简单的申请指定了LoginContext对象应该使用NTLoginModule进行验证。类的名称在ModuleClass中被指定。Flag控制当申请中包含了多个LoginModule时进行登录时的行为：Required、Sufficient、Requisite和Optional。最常用的是Required，使用它意味着对应的LoginModule对象必须被调用，并且必须需要通过所有的验证。由于Flag本身的复杂性，本文在这里不作深究。<br /><br />　　ModuleOption允许有多个参数。例如你可以设定调试参数为True（debug=true），这样诊断输出将被送到System.out中。<br /><br />　　配置文件可以被任意命名，并且可以被放在任何位置。JAAS框架通过使用java.securty.auth.long.config属性来确定配置文件的位置。例如当你的应用程序是JaasTest，配置文件是当前目录下的jaas.config，你需要在命令行中输入：<br /><br />java -Djava.security.auth.login.config=jass.config JavaTest<br /><br />　　图二描述了配置文件中各元素之间的关系<br /><br /><table width="100" align="center"><tbody><tr align="middle"><td><img height="300" src="http://www.huihoo.com/java/j2ee/jt-2003-1-14-image002.gif" width="548" /><br />图二 JAAS的配置文件 </td></tr></tbody></table><br />　　<b><font color="#ac000">通过命令行方式进行登录验证</font></b><br /><br />　　为了说明JAAS到底能干什么，我在这里编写了两个例子。一个是简单的由命令行输入调用的程序，另一个是服务器端的JSP程序。这两个程序都通过用户名／密码的方式进行登录，然后使用关系数据库对其进行验证。<br /><br />　　为了通过数据库进行验证，我们需要：<br /><br />　　1. 实现RdbmsLoginModul类，该类可以对输入的信息进行验证。<br /><br />　　2. 编辑一个配置文件，告诉LoginContext如何使用RdbmsLoginModule。<br /><br />　　3. 实现ConsoleCallbackHandler类，通过该类可以获取用户的输入。<br /><br />　　4. 编写应用程序代码。<br /><br />　　在RdbmsLoginModul类中，我们必须实现LgoinModule接口中的五个方法。首先是initialize（）方法：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>public void initialize(Subject subject, CallbackHandler <br />callbackHandler,<br /><br />Map sharedState, Map options)<br />{<br />this.subject = subject;<br />this.callbackHandler = callbackHandler;<br />this.sharedState = sharedState;<br />this.options = options;<br /><br />url = (String)options.get("url");<br />driverClass = (String)options.get("driver");<br />debug = "true".equalsIgnoreCase((String)options.get("debug"));<br />}</td></tr></tbody></table><br />　　LoginContext在调用login（）方法时会调用initialize（）方法。RdbmsLoginModule的第一个任务就是在类中保存输入参数的引用。在验证成功后将向Subject对象中送入Principal对象和凭证。<br /><br />　　CallbackHandler对象将会在login（）方法中被使用到。sharedState可以使数据在不同的LoginModule对象之间共享，但是在这个例子中我们不会使用它。最后是名为options的Map对象。options向LgoinModule对象传递在配置文件ModuleOption域中定义的参数的值。配置文件如下所示：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>Example {<br />RdbmsLoginModule required<br />driver="org.gjt.mm.mysql.Driver"<br />url="jdbc:mysql://localhost/jaasdb?user=root"<br />debug="true";<br />};</td></tr></tbody></table><br />　　在配置文件中，RdbmsLoginModule包含了五个参数，其中driver、url、user和password是必需的，而debug是可选阐述。driver、url、user和password参数告诉我们如何获得JDBC连接。当然你还可以在ModuleOption域中加入数据库中的表或列的信息。使用这些参数的目的是为了能够对数据库进行操作。在LoginModule类的initialize（）方法中我们保存了每个参数的值。<br /><br />　　我们前面提到一个LoginContext对应的配置文件告诉它应该使用文件中的哪一个申请。这个信息是通过LgoinContext的构造函数传递的。下面是初始化客户端的代码，在代码中创建了一个LoginContex对象并调用了login（）方法。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>ConsoleCallbackHandler cbh = new ConsoleCallbackHandler();<br />LoginContext lc = new LoginContext("Example", cbh);<br />lc.login();</td></tr></tbody></table><br />　　当LgoinContext.login（）方法被调用时，它调用所有加载了的LoginModule对象的login（）方法。在我们的这个例子中是RdbmsLoginModule中的login（）方法。<br /><br />　　RdbmsLoginModule中的login（）方法进行了下面的操作：<br /><br />　　1. 创建两个Callback对象。这些对象从用户输入中获取用户名/密码。程序中使用了JAAS中的两个Callback类:NameCallback和PasswordCallback（这两个类包含在javax.security.auth.callback包中）。<br /><br />　　2. 通过将callbacks作为参数传递给CallbackHandler的handle（）方法来激活Callback。<br /><br />　　3. 通过Callback对象获得用户名／密码。<br /><br />　　4. 在rdbmsValidate（）方法中通过JDBC在数据库中验证获取的用户名／密码。<br /><br />　　下面是RdbmsLoginModule中的login（）方法的代码<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>public boolean login() throws LoginException {<br />if (callbackHandler == null)<br />throw new LoginException("no handler");<br /><br />NameCallback nameCb = new NameCallback("user: ");<br />PasswordCallback passCb = new PasswordCallback("password: ", true);<br />callbacks = new Callback[] { nameCb, passCb };<br />callbackHandler.handle(callbacks);<br /><br />String username = nameCb.getName();<br />String password = new String(passCb.getPassword());<br />success = rdbmsValidate(username, password);<br /><br />return(true);<br />}</td></tr></tbody></table><br />　　在ConsoleCallbackHandler类的handle（）方法中你可以看到Callback对象是如何同用户进行交互的：<br /><br />　　<b><font color="#ac000">使用JSP和关系数据库进行登录验证</font></b><br /><br />　　现在我们希望将通过命令行调用的程序一直到Web应用程序中。由于Web应用程序与一般的应用程序的交互方式有区别不同，我们将不能使用JAAS提供的标准Callback和CallbackHandler类。因为我们不能在Web程序中打开一个命令窗口让用户输入信息。也许你会想到我们也可以使用基于HTTP的验证，这样我们可以从浏览器弹出的用户名／密码窗口中获得用户输入。但是这样做也有一些问题，它要求建立起双向的HTTP连接（在login（）方法很难实现双向连接）。因此在我们的例子中我们会使用利用表单进行登录，从表单中获取用户输入的信息，然后通过RdbmsLoginModule类验证它。<br /><br />　　由于我们没有在应用层同LoginModule直接打交道，而是通过LgoinContext来调用其中的方法的，我们如何将获得用户名和密码放入LoginModule对象中呢？我们可以使用其它的方法来绕过这个问题，例如我们可以在创建LoginContext对象前先初始化一个Subject对象，在Subject对象的凭证中保存用户名和密码。然后我们可以将该Subject对象传递给LoginContext的构造函数。这种方法虽然从技术上来说没有什么问题，但是它在应用程序层增加了很多与安全机制相关的代码。而且通常是在验证后向Subject送入凭证，而不是之前。<br />前面我们提到可以实现一个CallbackHandler类，然后将它的实例传递给LoginContext对象。在这里我们可以采用类似的方法来处理用户名和密码。我们实现了一个新的类PassiveCallbackHandler。下面是在JSP中使用该类的代码：<br /><br /><script language="Javascript"><![CDATA[ocument.write("<img src='http://counter.yesky.com/servlet/counter.counter?CID=72348977504190464&AID=-1&refer="+escape(document.referrer)+"&cur="+escape(document.URL)+"' border='0' alt='' width='0' height='0'>");]]&gt;</script><img height="0" alt="" src="http://counter.yesky.com/servlet/counter.counter?CID=72348977504190464&amp;AID=-1&amp;refer=&amp;cur=http%3A//www.huihoo.com/java/j2ee/jaas.html" width="0" border="0" /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>String user = request.getParameter("user");<br />String pass = request.getParameter("pass");<br />PassiveCallbackHandler cbh = new PassiveCallbackHandler(user, pass);<br />LoginContext lc = new LoginContext("Example", cbh); </td></tr></tbody></table><br />　　PassiveCallbackHandler中构造函数的参数包含了用户名和密码。因此它可以在Callbick对象中设定正确的值。下面是PassiveCallbackHandler类的handle（）方法的代码：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>public void handle(Callback[] callbacks)<br />throws java.io.IOException, UnsupportedCallbackException {<br />for (int i = 0; i ＜ callbacks.length; i++) {<br />if (callbacks[i] instanceof NameCallback) {<br />NameCallback nameCb = (NameCallback)callbacks[i]; <br /><br />nameCb.setName(user);<br />} else if(callbacks[i] instanceof PasswordCallback) {<br />PasswordCallback passCb = (PasswordCallback)callbacks[i];<br />passCb.setPassword(pass.toCharArray());<br />} else {<br />throw(new UnsupportedCallbackException(callbacks[i],<br />"Callback class not supported"));<br />}<br />}<br />}<br />}</td></tr></tbody></table><br />　　从上面的代码中可以发现实际上我们只是从ConsoleCallbackHandler中去除了那些提示用户输入的代码。<br /><br />　　在运行这个JSP例子的时候，我们需要设定系统属性，这样LgoinContext对象才知道如何查找名称为"Example"的配置。我们使用的是Resin服务器。在resin.conf中，我们增加了一个＜caucho.com＞节点：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>＜system-property <br />java.security.auth.login.config="/resin/conf/jaas.config"/＞ </td></tr></tbody></table><br />　　<b><font color="#ac000">小结</font></b><br /><br />　　JAAS通过提供动态的、可扩展的模型来进行用户验证和控制权限，从而使应用程序有更加健壮的安全机制。同时它还能够让你能够很轻松地创建自己的登录机制。JAAS可以同时在在客户端和服务器端应用程序上工作。虽然在服务器端的JAAS到目前还不是很稳定，但是随着技术的发展，相信会很好地解决这个问题</h4></h4></h4>
<img src ="http://www.blogjava.net/esdsoftware/aggbug/44836.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/esdsoftware/" target="_blank">吴名居</a> 2006-05-07 08:24 <a href="http://www.blogjava.net/esdsoftware/articles/44836.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>