[测试]使用Mantis跟踪bug

file

在PPP项目中我们组使用PVCS Tracker来跟踪bug,感觉项目组的确是需要这样一套系统的,PVCS虽然功能比较强,但首先不是免费的,另外也比较大,对于示范中心这样的小项目来说有些不够灵活。因此我安装了Mantis,一个十分小巧的bug跟踪工具。

Mantis是php写的开源软件(Bugzilla也是开源的,written in perl,但在windows下安装麻烦,所以暂时不考虑)。安装Mantis的步骤很简单,以下总结在Win2000/XP下的安装过程:

1、如果机器上有IIS,确保已经启动;如果希望使用Apache,从 httpd.apache.org 下载apache的windows安装程序,我用的是1.3版本,直接运行这个程序就安装完成了。

2、从 www.php.net 下载php的windows安装程序,我用的是4.3.8版本,也是直接运行下载来的程序。我用IIS时安装程序会自动对IIS进行设置,但在另一台没有IIS而使用Apache的时候,即使在安装过程中选择了正确的服务器类型,也会提示自动设置出错。不过手动设置也很简单,只要在apache安装目录下的conf目录里的httpd.conf里增加这样一段:

ScriptAlias /php/ "c:/php/"
AddType application/x-httpd-php .php
Action application/x-httpd-php "/php/php.exe"

这样就可以了。不过按照PHP的安装说明,这种方式是很危险的,我对PHP不熟,说不出到底危险在哪,可能是容易给Web服务器带来一些安全漏洞吧。

3、从 www.mantisbt.org 下载mantis的0.8.3版本(原来写成1.8.3是笔误,谢谢wfifi指出。mantis目前已有1.0.0rc版本),它很小只有几百K。如果是IIS,把mantis解压缩到Inetpub下(我一开始解到c:下总是不行,可能是权限问题),然后增加一个虚拟路径指向mantis目录;如果是Apache,把mantis解压缩到apache的安装目录下,并在httpd.conf里把主目录改为指向这个目录,或者增加一个Alias,但权限要设够才能正常运行。为了方便,可以在Web服务器里增加index.php为缺省文件名。

4、从 www.mysql.net 下载mysql,我用的是4.0版本。在mysql里为mantis建立一个帐户,然后建立一个名为bugtracker的数据库,这是mantis配置文件里的缺省名字,在mantis安装路径下的sql目录里有一个db_generate.sql文件,这里面是建表的语句,执行它。

5、把mantis安装路径下的config_inc.php.sample改名为config_inc.php,打开并修改里面的内容。主要是和数据库连接的信息,例如数据库名、用户名等等。我在最后加了这样两句:

$g_default_language = 'chinese_simplified';
$g_enable_email_notification = OFF;

这样缺省界面就是简体中文的,并且不发送邮件通知。要发送邮件还要对PHP进行另外的配置,我还没有试过,好象挺麻烦的,所以干脆禁掉。另外mantis提供的简体中文语言包里好象很多“删除”都写成了“.h除”,我对lang目录中的strings_chinese_simplified.txt文件做了一个替换(.h除->删除)就好了。

6、最后,重启一下Web服务器,就可以访问了,地址是 http://localhost/mantis 这样的。按照mantis的建议,应该新建至少一个administrator级别的用户,然后把admin目录删除,并删除administrator这个帐号。

我们的项目不复杂,而且成员少,所以mantis的安装能用就行,对安全和报表、邮件的配置都没有关心,以后需要用到的时候再研究吧。

Update(2012/11/12): 今天再次看了一下Mantis网站,这个bug跟踪系统又有很多改进,而且增加了手机客户端,下次有机会还要继续使用。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/18/34380.html

[Struts]处理表单中值为空的日期类型字段

在示范中心项目中,我们把ActionForm中日期类型的字段指定为String类型,而在对应的JavaBean中指定为java.sql.Date类型。当用户提交表单的时候,在Action里使用BeanUtils.copyProperties()方法从ActionForm构造JavaBean对象(详见利用BeanUtils在对象间复制属性)。这个方法在大部分时候都很好,但有一个问题,就是当用户没有填写日期类型字段时(而该字段并非必填),validator不会提出警告,而在copyProperties()时会报类型转换异常,原因是这时ActionForm中的该字段的值是空字符串(""),负责字符串向Date转换的SqlDateConverter类调用Date.valueOf("")方法,显然""是无法转换为日期的,所以会抛出异常。

通过查看代码和资料,我发现这个问题的解决方法其实非常简单。只要把带缺省值参数的SqlDateConverter重新注册一下,覆盖原有的注册信息就可以了,这个注册语句一般是写在系统初试化的地方,对于Struts应用程序,当然做在PlugIn里最方便。代码如下:

package com.acme;

import javax.servlet.ServletException;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.converters.SqlDateConverter;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.config.ModuleConfig;

public class ConverterPlugIn implements PlugIn{

    public void init(ActionServlet servlet, ModuleConfig config) throws ServletException {
        ConvertUtils.register(new SqlDateConverter(null),java.sql.Date.class);
    }

    public void destroy() {
        ConvertUtils.deregister();
    }
}

注意SqlDateConverter的构造方法是带有参数null的,这表示遇到不能解析的字符串就返回空值。而deregister()方法的作用是恢复ConvertUtils的缺省注册表。为了使这个PlugIn起作用,要在struts-config.xml里增加一句话:

<plug-in className="etc.ConverterPlugIn" />

日期字段往往会给我们的开发带来麻烦,其实在Struts应用程序里,只要把这些转换类搞熟了,总可以找到很方便的办法。常见的问题还有如何指定日期输入格式,怎样处理java.util.Date的转换,等等,在这个链接里有解决这些问题的方法,道理都是一样的。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/17/34106.html

[Hibernate]xDoclet生成hbm的一个bug

做示范中心项目时遇到的,类Teacher实现接口BusinessObject,在接口里用@hibernate.class,在类里用@hibernate.joined-subclass-key column="oid"和@hibernate.joined-subclass,执行ant任务时只生成了BusinessObject.hbm.xml,而且在里面没有关于Teacher的定义。为此折腾了好一阵,后在在网上找到一个贴子说的是同一个问题,还提供了一个patch,不过还没试好不好使,内容如下。(快该回家了,晚上继续写)

diff -u -1 -b -p -r1.20 HibernateTagsHandler.java
--- HibernateTagsHandler.java   14 Jun 2003 13:58:10 -0000      1.20
+++ HibernateTagsHandler.java   3 Nov 2003 00:58:27 -0000
@@ -285,3 +285,7 @@ public class HibernateTagsHandler
                 }
-                else if (clazz.getSuperclass() != null && clazz.getSuperclass().getQualifiedName().equals(typeName)) {
+                else if ((clazz.getSuperclass() != null &&
+                    clazz.getSuperclass().getQualifiedName().equals(typeName))
+                    ||
+                    (getCurrentClass().isInterface() &&
+                    clazz.isImplementingInterface(typeName))) {
                     log.debug("is a subclass");

现在决定不用这个patch的方法了,改源码得重新build,而且以后就不能用通用包了。暂时拿抽象类代替接口吧,差不多。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/13/33157.html

[Eclipse]国际化你的应用程序(上)

file

记得几年前汉化软件一般是用二进制编辑工具在编译后的文件中查找和替换英文单词,这个过程需要使用很多技巧,非常的麻烦。而现在,在开发时对应用程序进行国际化处理已经越来越成为一个必不可少的步骤了。例如这次我参与的项目是给台湾客户做的,他们要求英文和繁体中文两个版本,幸好我们使用的开发工具是Eclipse,利用它的国际化功能可以很方便的将写在代码里的字符串提出到独立的资源文件中,这里用一个简单的例子说明一下这个过程。

在Eclipse里建立一个名为nls-test的工程(也可以国际化已有工程),新建一个类NLSTest,内容如下:

public class NLSTest {
   public NLSTest() {
      String str1="Hello world!";
      System.out.println(str1);
   }
   public static void main(String[] args) {
       new NLSTest();
   }
}

现在,这个类的输出是在代码里写死的,如果要改变就必须修改代码然后重新编译。下面我们利用Eclipse解决这个问题。在导航器(Package Explorer)里右键单击这个文件,选择Source -> Externalize Strings,就会打开一个对话框,在这里列出了该类中尚未国际化的字符串,见下图。

file
图1 国际化向导第一步

单击每个字符串前面的小方块可以选择对该串 1、进行国际化处理 2、永不进行国际化处理 3、这次不处理。我们选择要对这个字符串进行国际化处理,并把它的Key修改为比较好理解的名称hello,注意在对话框最上方可以指定一个通用的前缀,这个值会加在每个Key前面作为最终写在资源文件(扩展名是.properties)里的Key名称。好,按下一步继续。

在这里要指定properties文件的名称,如果需要的话要指定一个用于从properties文件中取资源的类,一般是XXXMessages的格式,再按下一步,会显示一个所作更改的确认列表,确认后按Finish按钮完成向导。

可以看到Eclipse为我们生成了NLSTestMessages.java和NLSTest.properties,打开后者会看到里面只有这么一句:

NLSTest.hello=Hello world!

而前者NLSTestMessages的作用是根据参数Key从后者取对应的字符串值,看一看现在的NLSTest.java就知道了,它的内容现在是这样的(只列出构造方法,main方法同上):

public NLSTest() {
    String str1=NLSTestMessages.getString("NLSTest.hello"); //$NON-NLS-1$
    System.out.println(str1);
}

后面的注释是Eclipse自己用的,标有这个注释的字符串在国际化将被忽略。注释中的数字1表示要忽略的是该行中第一个字符串,如果一行语句里有多个字符串被忽略,将会有多个这样的注释,但数字会各不相同,像这样:

//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 

好了,现在这个类的国际化处理就算完成了。要想让这个类可以根据用户所在地区输出不同语言的结果,可以在NLSTest.properties同一目录下创建名为NLSTest_XX.properties的文件,其中XX表示国家名称,例如中国是zh_CN或zh_TW,法国是FR等等。新创建的文件里也要有和原来文件相同的名值对,但值是不同语言的,NLSTestMessages类会根据用户机器的地区设置值自动从不同的资源文件里取值,这样就达到了国际化的目的。要在自己的机器上测试运行结果,可以在Eclipse的运行设置里面加上这样的参数:-nl zh_TW,这样就不用费力气设置区域了。(2012/5/4更新:此方法可能有误,在Eclipse3.6里是启动程序时在VM arguments里加上-Duser.language=XXX即可以所需要的语言环境启动程序)

国际化的原理很简单,Eclipse提供的这个功能使国际化变得更容易了。不过关于国际化还有一些细节问题,包括对含参数资源的处理,字符编码处理等等,下篇将对它们进行讨论。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/08/31262.html

[Struts]让Dreamweaver显示Struts标签的插件

Dreamweaver(简称DW)的设计视图里不能显示struts标签,只能手动改代码。为此我找了好久,终于还是在DW网站上找到了,只有8K大,虽然没有漂亮的图标,但显示的信息还是很够用的。现在总算可以用DW编辑含有struts标签的jsp文件了!

file
图1 在DW里显示struts标签

这个文件我已经放在FTP上了,请点这里下载。如果连不上,请用这个地址。下载以后直接双击就安装,然后重开DW就行了。不需要了可以在Extension Manager里卸载。

Update: 感谢dudu帮我开通了文件上传功能,本地下载

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/07/31028.html

[翻译]JavaBean组件与JSP技术结合

Web架构师Brett McLaughlin向我们展示了怎样利用JavaBean和JSP技术在Web页面中保存和传递数据,以及如何设计可以得到更大的灵活性。

到目前为止,在JSP最佳实践系列里,我们已经讨论了相当一部分基础知识。在前面的两个章节里,你应该学会了如何使用JSP的include机制将网站以外的内容包含在页面或应用程序里。共有两种不同类型的include:静态的include命令和动态的jsp:include标记。

同时,我们还没有涉及到在父页面(在我们的例子中是网站的首页)和被包含内容之间的通信问题。而实际上这种情况是非常普遍的。当你开始建立一个真正的网站或者web应用程序时,通常你会需要这样的通讯机制,举个例子,你的网站可能会在首页产生一些标题一类的小段文字,这些文字要出现在页眉或页脚页面中。在这一部分里,你将学到如何在页面间传递数据,以及如何在被包含的页面中使用这些数据。

注:这部分的全部例子都基于JavaServer Pages技术,为了能运行它们,你需要建立一个JSP兼容的web容器,在你本地计算机或某个用于测试的服务器上都可以。同时,你还要有一个文本编辑器或者集成环境来编写你的JSP页面。

用于保存数据的JavaBean

让我们假想这样的一个网站,在这个网站里每个页面有一小句“口号”(例如“书:装满知识的容器”或者“唱片:值得一听”)和一个标题。父页面(有时也被称作主页面)决定了每一页的口号,但真正产生html输出这个口号的是页眉页面,它是被包含的。要实现这个目的,父页面必须能够把口号传递给页眉页面,页眉页面得到这个口号,将它以页面标题的形式输出。

首先我们需要某种对象来保存被传递的数据,恰好(这并非偶然)JavaBean组件就是这样一个合适的选择,它与JSP技术配合得天衣无缝,简单的使用取值方法和赋值方法就可以控制你要的数据。稍有java编程经验的读者可能已经想到,get()就是一个取值方法,因为它读取数据;而set()是一个赋值方法,因为它改变数据。
列表1展示了我们所需要的一个JavaBean的代码,PageHeaderinfo包含了网站页眉信息。

<![CDATA[
package com.newInstance.site.beans;
import java.io.Serializable;
public class PageHeaderInfo implements Serializable {
     /** The title of the page */
     private String pageTitle;
     /** The slogan of the page */
     private String pageSlogan;
     public String getPageTitle() {
       return pageTitle;
     }
     public void setPageTitle(String pageTitle) {
       this.pageTitle = pageTitle;
     }
     public String getPageSlogan() {
       return pageSlogan;
     }
     public void setPageSlogan(String pageSlogan) {
       this.pageSlogan = pageSlogan;
     }
}
]]>

作为第一个练习,将这个文件保存为PageHeaderInfo.java并编译它。接下来,把得到的class文件PageHeaderInfo.class放在你的web应用程序的WEB-INF/classes目录下。请确保目录包含了包的结构,例如:

$<TOMCAT-ROOT>/webapps/$<WEB-APP-NAME>/WEB-INF/classes/com/newInstance/  site/beans/PageHeaderInfo.class

你可以使用类似这样的路径安排web应用程序中用到的类。按以上步骤做到这里,下面就可以向PageHeaderInfo里存入数据然后在不同的JSP页面中获取了。

传递已保存的数据

在我们的网站里,页眉页面包含向不同页面传递不同口号的代码。查看前面的章节就会发现,页眉页面(header.jsp)是通过jsp:include标记被包含的文件。列表2展示了网站首页如何通过标记把数据传递给header.jsp文件。

<![CDATA[
<%@ page language="java" contentType="text/html" %>
<html>
<head>
     <title>newInstance.com</title>
     <meta http-equiv="Content-Type" 
       content="text/html; charset=iso-8859-1" />
     <link href="/styles/default.css" rel="stylesheet" type="text/css" />
</head>

<body>
<jsp:include page="header.jsp" flush="true">
     <jsp:param name="pageTitle" value="newInstance.com"/>
     <jsp:param name="pageSlogan" 
       value="Java and XML :: Turning theory into practice" />
</jsp:include>
<%@ include file="/navigation.jsp" %>
<jsp:include page="bookshelf.jsp" flush="true" />

<jsp:include page="/mt-blogs/index.jsp" flush="true" />

<%@ include file="/footer.jsp" %>
</body>
</html>
]]>

可以看出,标题是被传递过去作为口号的。

你可能已经注意到了,在你建立页面的时候,不一定需要JavaBean组件实际存在。我总是先写好JavaBean,有一个很好的理由:JSP参数名必须与JavaBean属性名匹配,先完成JavaBean可以保证你在编写JSP页面时使用合适的参数名称。

获得数据

当你完成了JSP参数和JavaBean属性的编码,一旦数据被传递给header.jsp,这个页面就可以开始获取数据了。列表3展示了header.jsp页面。它的大部分内容是html,请注意里面的JSP脚本,在你研究过这些代码后我会在后面向你解释。

<![CDATA[
<!-- Begin header section -->
<%@ page language="java" contentType="text/html" %>
<jsp:useBean id="pageHeaderInfo"
class="com.newInstance.site.beans.PageHeaderInfo">
     <jsp:setProperty name="pageHeaderInfo" property="*" />
</jsp:useBean>

<table width="100%" border="0" cellspacing="0" cellpadding="0">
     <tr>
       <td width="91" height="50" align="right" valign="top"
           bgcolor="#330066"><font color="#FFFFFF"><img
           src="/images/header-lions.gif" 
           width="90" height="60"></font></td>
       <td colspan="3" align="left" valign="top"
           bgcolor="#000000"><table width="100%" height="60" border="0"
           cellpadding="0" cellspacing="0">
           <tr>
             <td width="261" rowspan="2"><img
               src="/images/header-title.gif" width="261" height="60"></td>
             <td class="pagetitle" width="249" height="55" align="right"
               valign="bottom"><jsp:getProperty name="pageHeaderInfo"
               property="pageSlogan"/></td>
             <td width="10" height="55"> </td>
           </tr>
           <tr>
             <td height="5"><img src="/images/spacer.gif" width="1"
               height="5"></td>
             <td height="5"><img src="/images/spacer.gif" width="1"
               height="5"></td>
           </tr>
         </table></td>
       <td width="141" bgcolor="#000000">
         <font color="#FFFFFF"> </font>
       </td>
     </tr>
<!-- End header section -->
]]>

第一行代码标识了该页面为一个JSP页面,然后通过jsp:useBean标记声明需要访问PageHeaderInfo这个JavaBean,id属性为这个bean指定了一个名称,通过该名称可以在JSP页面中使用bean;class属性指定了JavaBean类的全名。相邻的jsp:setProperty标记说明了JavaBean(通过id属性标识)的所有属性都以请求数据赋值,也就是说,为这个bean里的每个属性(例如pagetitle和pageslogan)寻找名称对应的请求参数来赋值。这些请求参数可以来自客户端的浏览器,也可以来自包含这一页的其他JSP页面。在这种情况下,唯一的请求数据是由父页面创建的。对于我们的网站,首页(index.jsp)发送pagetitle和pageslogan,其值分别为“newinstance.com”和“Java and XML: Turning theory into practice”。

一旦bean的属性被赋值后,页面就可以使用这些数据了。对于header.jsp,在后面的代码里可以看到,通过jsp:getProperty标记使用了这些数据。Jsp:getProperty标记通过name参数标识从哪个对象取数据,通过property参数标识取对象的哪一个属性。取得的数据值自动插入到页面的输出,也就是输出到父页面里,从而得到一个无缝的、动态的页面口号,在JSP页面间传递数据就是这么简单!你可以为一个JSP页面增加任意多的bean,每一个bean都可以有任意多的属性,足以应付任何复杂的请求数据。

处理需求变化

改变是软件开发者最大的烦恼,你写好了你的bean,在JSP页面里也写好了使用它们的代码,这时web应用程序的需求似乎不可避免的会发生变化。如果这个改变要求更多的属性(大多数情况都是如此),你不得不改写你的JavaBean源代码,重新编译它,还要确定JSP页面能够正确访问新生成的bean类。有些情况下,你可以不必对bean进行处理,如果保存或获取属性页面不再使用了(也就是说,即使那个页面还在站点的目录里,但你不再使用该页面),这时你可以不用去管原来的代码即可。实际上,我就遇到过这种情况!

我的个人网站的页眉页面header.jsp用于生成html的头部,以前有一段时间里,这一页向我的其他页面头部里的title标记里插入一个标题,后来我做了修改,在header.jsp里不再使用页面标题了。但我并没有从PageHeaderInfo里把pageTitle移除;实际上,我甚至在大部分JSP页面里连jsp:param标记都没有移除,这个标记的作用本来是为页面设置标题的。我认为花这些工夫不值得,因为我确信保留这些数据不会带来任何坏的影响(也许某一天我还会重新用到呢!)。因此,如果你遇到同样的情况,不用浪费时间了--有处理这些琐事的时间不如用来为你的web应用程序增加些新的、有趣的、实用的功能。

下一次

当你熟练掌握了在JSP页面间传递数据的方法后,试着自己写一些有用的JavaBean并且看看能不能把它们用在你的站点里。通过和这些bean的接触,还有jsp:useBeanjsp:param以及jsp:get/setProperty的使用,你应该能够做出一些很酷的功能了!在下次的最佳实践里,我将向你展示使用JSP向站点增加外部内容的方法,JSTL标记和我们熟悉的include标记差不多,它使得JSP更灵活和更高效。在这之前,请用功准备,到时再见!

英文原文

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/07/30950.html

师兄对研究生课程的评价

北大计算机系研究生该选哪些课程,这些意见值得参考。

必修课

  • 算法:一定要认真记笔记,而且要把笔记内容想透,老师会变着法子从笔记里出题。不要逃课,课本最后的内容也可能考。论文最好早点写,一般考试都会延长半个小时左右,基础知识要弄扎实,不要怕枯燥。两次作业,一次分析设计,一次是NPC问题。
  • OO:枯燥,上课会点名,有个图要早点做(11月),考试也会延长时间。题型比较固定,可以找往届题参考。老师自创的一些观点考试很可能会考到。有一个大程序。
  • 高体:头两个月可能会比较吃力,但题型一般也就是那么几种。程旭老师讲课很有激情(像推销员),前几节课的内容是回顾初级体系结构课程内容。要抓住几个重点(参考往届题目),背的内容居多。

选修课

  • 数据库原理与技术:笔记很多很多,要写一个分析报告,关于目前流行的数据库产品介绍等等。总体感觉不是很值得选。
  • 并行计算:一般是开卷考,主要是理论。袁老师很注重课堂表现。
  • 网络与分布式:新毕业的博士生讲,教材是英文八九百页,很有挑战性的课程。内容非常多,从头讲到尾,有不少新鲜应用,虽然很累但能学到东西。
  • 人工智能:没听说过有人及格。
  • PETRI网:平时分多,不太好混,因为平时表现比较重要。袁老师课讲得好,但PETRI网本身不是很实用。
  • 高级软工:什么都讲,但主要是概念或特点方面的内容,不太涉及具体编程。Java、EJB方面的内容比较多,要做一个程序,如果用Java比较简单。考试给分一般比较高。
  • 信息安全:没必要上,感觉老师不是很负责任(所谓的烂课)。老师总换,有程序和论文各一。
  • 数据库新技术:就是讲数据挖掘。
  • 空间信息系统与辅助设计:讲法比较新颖,每节课有作业。
  • 形式语言与自动机:老师水平不高,但讲得很仔细。
  • 图象处理:考试给分不低,且会划一个比较小的范围,上课轻松。共有5次程序。
  • 并行程序设计:去年作业很多很累,基本上是讲用MPI写并行程序,应该有C++基础。
  • 软件设计工具:多实践多用,考试不累,ROSE、UML培训性质。
  • 先进技术专题:COM技术,潘爱民老师知道得多,但讲起来不一定能听懂(也许一句也听不懂),可以学到一些编程哲学。
  • 图形多媒体(信息中心):比较容易,给分高,老师讲得不好,但内容比较有用。

据说信息中心的课期末不考试只写论文的居多。

我选过的课

补充几个我曾选过的课:

  • 数据库原理与技术和Web Services平时负担都很大。
  • 软件项目管理可以经常逃课,老师不管。
  • 软件设计工具(UML)虽然我平时很认真做作业,而且从不逃课,自认为学得不错,得分却是所有课里最低的。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/07/30945.html

Web服务系列(三) XML技术

file

对于XML这个名字,我们已经再熟悉不过了。它可以说是既简单又复杂,因为XML本身具有简单明确的规则,但随着越来越多基于XML技术的新应用的出现,它又演化出各种复杂的语言。希望这篇帖子能为你解决以下问题:XML是什么、我们为什么需要它以及怎样使用它。

XML,全称是可扩展标记语言(eXtensible Markup Language),它是一种标记语言。标记语言的特点是具有三个要素:标记、元素和属性。先看一段简单的XML例子,如下所示:

<?xml version="1.0"?>
<team>
  <member leader="true">
    <name>张浩</name>
    <age>25</age>
  </member>
  <member leader="false">
    <name>孙亮</name>
    <age>25</age>
  </member>
</team>

可以看出XML文档具有树状的结构,应该说这种结构是很适合描述数据的,所以会有不少人把XML作为持久化数据的方式。上面的XML文档中,用尖括号括起来的是标记,例如<team><member>等等,带斜线/的是结束标记,不带的是开始标记,如果是空标记可以写作<team />的形式;开始标记和结束标记与它们之间的内容合称元素;属性是一个名称-值对,例如leader="true"表示leader是member元素的属性。

从XML的格式很容易想到HTML,HTML也是一种标记语言,还有WML、XHTML等等都是标记语言,XML与它们的不同之处在于:XML是创造这些标记语言的元语言。如果拿面向对象语言来类比的话,那就是HTML、WML、XHTML等等都继承了XML,是XML的子类。因此,凡是XML具备的特性,它们也都具备。例如,一个文档只能有一个根元素,标记可以嵌套但不能交叉,属性必须用双引号括起来,开始标记和结束标记必须配套等等。如果一个XML文档不遵守这些规则,则称它是无效的。另外,还可以通过DTD或Schema对XML格式增加额外的约束,例如可以要求<team>元素下至少有一个<member>元素,<age>元素为可选的等等,如果XML文档是有效的同时还满足这些额外要求,则称这个文档是格式良好的。

DTD和Schema是验证XML是否格式良好的两种方式。DTD出现得比较早,它不是XML格式的;Schema则是XML格式的,并且功能更强,支持正则表达式。在XML文档头部可以引用这些文件,处理该XML文档时就会对它进行验证,如果验证失败则不会做进一步处理。为节约篇幅,DTD和Schema的格式这里就不细说了。一般来说,如果在程序中需要定义自己的XML格式,最好先定义DTD或Schema,我们平常使用的大部分XML文档如web.xml、struts-config.xml都有自己的DTD或Schema用来保证格式。

还要说一下名称空间的问题。名称空间是标记的前缀,XML文档在实际应用中可能会被合并,这个前缀保证了合并后的文档中不会出现冲突的标记。为了保证这个唯一性,名称空间一般使用URL的格式,例如:

<myNS:team xmlns:myNS="http://www.mysite.com">
    ...
</myNS:team>

其中myNS是我们随便起的名字,后面的xmlns:myNS属性指定了这个名字代表的名称空间,应该注意真正有意义的是team这个名字。一个完整的标记应该是名称空间:标记名这样的形式,带有名称空间的XML文档读起来会有点乱,所以要认清哪些是重要的,哪些是暂时可以忽略的。

作为一种描述数据的方式,只要发挥想象力,XML可以有无限多种用途,你订阅过RSS吗,那也是其中之一。在Web服务中,我们用XML在服务提供者和使用者之间传递请求和响应数据(SOAP是其中一种格式)、用XML描述服务(例如WSDL),还用XML将服务组装成完整的流程(比如使用BPEL4WS),这些格式规范将在后面的帖子中一一介绍。对了,是XML的可扩展性成就了它们。

要在程序中使用XML,也许是从XML格式的配置文件中读取信息,或是向其他系统提供XML格式的数据,或者其他方式,最直接的方法是使用XML解释器,目前比较常见的DOM、SAX、JDOM和JAXP,其中JAXP作为Java扩展是一个统一的接口,前三者是它的实现方式。关于这些解释器的比较,有很多文章可以参考,这里就不赘述了,我用过DOM和JDOM,比较喜欢后者,因为代码量会小一些。

虽然直接使用XML解释器处理XML格式信息并在服务提供者和使用者间传递也是Web服务,但那样太麻烦了,我们将不得不处理各种琐碎问题(例如数据类型映射),同时产生大量代码。因此,有必要使用专门处理Web服务中各种专用XML格式的解释器,Apache Axis(前身是Apache SOAP)就是其中一种,它可以解释SOAP信息,比起直接用前面所说的XML解释器方便很多。

关于XML还有太多内容,例如XSL用来表现XML、XSLT用来在不同格式XML间转换,XPATH用来在XML文档中找到合适的元素,等等。怎么样,帖子开头提到的问题解决了吗?如果没有也没关系,IBM开发人员网站上有一个XML专区,在那里你一定会大开眼界,如果你是新手,先看看这个教程,可比我写得好多了,呵呵...下一贴开始讲SOAP。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/06/30885.html

Web服务系列(二) Web服务的结构

我发现写日志可以帮助自己整理思路,有些技术在一段时间不用后,如果以日志的形式写出来,对于巩固记忆是十分有效的。比如这个Web服务系列,为了尽量避免错误,我会重新查阅资料,在这个过程中又能学到新的知识。不过,Web服务涉及的面太广了,而且新技术出现那么快,即使全部时间都用来研究它也不可能面面俱到,就像一本很厚很厚的书,经常翻翻反而会有意想不到的收获。

上一篇帖子里已经说过Web服务是做什么用的了,在这一篇里要说说Web服务的结构,也就是Web服务的协议栈。协议是各角色间用来沟通的基础,之所以称为栈,是由于这些协议是一层层垒起来的,下面一层是上面一层的基础。就像OSI的七层网络协议的关系。

现在要向你介绍一些概念了,它们是:XML、SOAP、WSDL、UDDI和BPEL4WS。对于XML相信大家都不会陌生,它是可扩展标记语言(eXtensible Markup Language)的缩写,是Web服务各种协议的基础;SOAP是简单对象访问协议(Simple Object Access Protocal)的缩写,它主要用于在服务提供者和使用者之间传送各种请求和应答数据;WSDL代表Web服务定义语言(Web Services Description Language),服务提供者使用这种语言发布自己的Web服务,供潜在的服务使用者使用;UDDI的意思是统一描述发现和集成(Universal Description, Discovery and Integration),UDDI项目由UDDI社区维护,服务提供者可以将自己的服务注册到UDDI服务器中,服务使用者可以在服务器中浏览和查询所需服务;BPEL4WS的意思是用于Web服务的业务流程执行语言(Business Process Execution Language for Web Services),它可以将多个Web服务组装成完整的业务流程,体现了Web服务的真正强大之处(组装)。

只这么简单一说,你可能对其中有些概念并不明白,不过没有关系,在以后的帖子里会对每个概念展开来说明。还要说明一点,列出的这些概念是实际构造和使用Web服务时使用最为广泛的技术,但Web服务并不一定必须使用这些,除XML外,其他技术都有替代品,只是并不那么流行而已。下面我们就来看看Web服务的协议栈是个什么样子的吧,如图所示。

file
图1 Web服务协议栈

最底层是服务传输层,在图中可以看到Web服务可以使用多种(OSI应用层)网络协议进行消息传递,HTTP是使用最为广泛的,因为HTTP的请求应答模式十分符合RPC类型调用,SMTP主要用于异步方式的调用,例如订阅信息等等。

服务消息层的协议定义了消息的格式,在这一层里几乎全部是以SOAP为协议的,至少我还没见过使用其他协议的例子。SOAP的基础是XML,也就是说,SOAP消息一定都是XML格式的。

服务描述层的协议用于对如何使用这个Web服务进行描述,描述信息一般包括使用到的数据类型、消息格式、方法名称和参数(在WSDL里的称呼有所不同)等等。WSDL也是以XML为基础的。

服务发布和发现层协议是供注册中心这个角色使用的,UDDI是目前使用最广泛的注册中心,图中其他几种方式也有应用。

服务组装层用于组装Web服务成为新的服务,这些被组装起来的服务一般体现了一定的业务流程。其好处是各服务间耦合很小,改变起来十分容易。在这一层里,目前有不少协议正在竞争,BPEL4WS可以说具有一定的优势吧。

待开发的协议与我们比较小,暂时不说了。图中右边三个纵向协议贯穿整个Web服务生命周期,它们是服务管理、服务质量和服务安全。因为将来很多的Web服务是要收费才可以使用的,和钱挂钩的东西就必须能够管理、保证质量和安全才行。一直以来,它们都是Web服务研究的难点(因为涉及到太多方面的利益),目前在功能方面Web服务已经做好了准备,如果能够攻破这些非功能性的难题,我想Web服务距离大规模应用就不远了。

图1是比较常见的一种协议栈图,实际上由于Web服务的使用方式多种多样,协议栈图也未必相同。例如w3.org上的是这样,它把XML也技术表现在图上,体现了其在Web服务中的基础地位。

总结一下Web服务的关键技术:XML、SOAP、WSDL、UDDI和BPEL4WS。

如果觉得这一篇有点抽象,那很正常,因为出现了新的概念。另外,我自己对Web服务的理解也是来源与书本,项目经验不足,缺少对这个行业的宏观认识,所以在写出来的时候都要斟酌一二,拿不准的尽量不写。没关系,下面几篇讲的是具体技术,可以醒醒了:)

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/05/30402.html

Web服务系列(一) 简介

file

Web服务系列计划由大约十篇帖子组成,目的是介绍各种概念,以及开发Web服务的工具。在这第一篇里我会简单介绍一下Web服务的概念,并演示一个Web服务的具体应用场景。文章的篇幅不会太长,因为那样会看得很累,反正我是个“没有耐心”的人,呵呵。

Web服务(Web Services)在很多人眼里还是个十分神秘的概念,究其根源,我想主要是由于Web服务被宣传得很多,但实际应用却鲜见,给人一种很复杂和难以理解的感觉。另外,Web服务是基于XML的,不少人对XML本身也缺乏理解,虽然他们可能每天都在写XML格式的配置文件。

提到Web服务的起源就一定要先说一说SOA(面向服务的体系结构),和很多具有划时代意义的软件技术一样,SOA的出现根本上也是为了解决软件危机问题。做过项目的人都有过这种感受,随着项目推进,模块之间关系越来越紧密,任何一个小的修改都可能引起整个系统的不稳定,而客户需求偏偏总是在改变,结果是项目以差不多失败的结果告终。

从(分布式)软件发展的趋势来看,C/S->B/S->SOA,模块之间的耦合度是由紧密到松散的,松散的耦合有利于修改。我们常说的各种设计模式,其中大部分不也是为了降低类之间的耦合度吗。

这里我引用一下IBM网站上对SOA的定义:

面向服务的体系结构(service-oriented architecture)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种这样的系统中的服务可以以一种统一和通用的方式进行交互。(全文

说得通俗一点就是,系统中分为三种角色:服务提供者服务使用者注册中心,提供者发布服务到注册中心,使用者通过注册中心发现所需服务,然后与该服务的提供者绑定,并调用服务。

那么Web服务和SOA是什么关系呢,可以这样说,Web服务是SOA的一种实现,有点像Tomcat和JSP/Servlet规范的关系。SOA是一个比较虚的概念,例如它只提出定义一些接口和协议,那么这些东西具体应该怎样定义呢,Web服务就将它们具体化了:Web服务使用的协议都是基于XML的;SOA只说应该有三种角色,而Web服务里这三种角色都有具体的实现方式。看到这里你应该会问,那么SOA还有哪些实现呢?CORBA、DCOM和J2EE都可以算是,但我认为它们不能算很纯粹,至少它们并不都具有中立的协议。

现在用一个具体的例子来说明一下Web服务。假设我们的系统中需要一项功能是查询当地的天气情况(世界时间、货币汇率等等,都一样),显然我们不会自己做一个从气象部门数据库中查找数据的程序,这需要很多手续也没有必要,更要命的是,这样做会增加我们与气象部门的耦合度。试想某一天气象部门的数据库结构改变了,我们将不得不修改自己的代码,如果他们忘记通知我们这一改变,想象一下客户会看到什么?

为了利用Web服务,我们从某一注册中心查找和天气有关的服务,在结果中也许我们会选择收费较低,或者收费稍高但更稳定和准确的服务。从注册中心我们能够得到所选服务的完整描述,其中包含了各种数据类型和调用方式,利用这些信息,可以使用工具生成这些必要的类,以及客户端Stub,利用这个Stub就可以调用远程的Web服务了。在我们的例子中,调用后服务提供者会返回一个含有结果的消息,在我们的系统中可以从这个消息里得到所要的结果,并显示给客户。这样就形成一个完整的Web服务调用。这种调用方式被称为静态调用,因为在Stub里服务提供者的地址(被称为调用端点endpoint)是写定的,还有另外一种方式被称为动态调用,以后会讲到。

那么Web服务和以前的RPC(远程过程调用)有什么分别呢?RPC通常要求调用者和被调用者是同构的,即使用同样的语言编写,而Web服务没有这个要求(诀窍在于使用了XML封装消息),这就大大增加了灵活程度;另外,Web服务的调用除这种类似RPC的方式外,还可以是基于消息的方式,服务使用者可以只接收消息,或是只发送消息,在一些应用中这种方式十分有用。

好了,把这次讲的内容总结一下就是:Web服务是SOA的实现,Web服务不是RPC

下次将稍微详细点说说Web服务的结构和协议栈,第三篇文章开始会陆续讲些与Web服务关系非常紧密的几个概念。本人水平有限,如果存在错误欢迎指出,转载请注明出处。最后推荐一个学习Web服务的好地方,IBM开发者SOA与Web服务专区,适合各种水平的读者阅读,同时有很多最新应用,绝对值得一去。在http://www.xmethods.net/有很多Web服务的演示,有兴趣也可以看看。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/04/30012.html