通过 JAVA/CORBA 访问 Domino

Lotus Domino 作为一种全球领先的协作、消息传递和 Web 支持软件,正在迅速地在中国企事业推广。Domino 已经成为帮助每个人更灵活和更高效地工作的强大支持。如何从 DOMINO 数据库中获取数据,使这些数据为其他系统可用,已经成为许多企业迫切需要解决的问题。然而 domino 不同于普通的关系型数据库,由 ibm/lotus 自主研发,有自己的标准和特殊性,是一种另类的数据库类型。开发具有访问 DOMINO 服务器的应用程序的方法有许多种,但是普遍存在的问题是功能有很大的局限性,都要在依赖于 lotous notes 这一庞大而昂贵的客户端软件。随着 java 语言和 CORBA 中间件技术的日趋成熟,新版本的 Domino 也提供了 corba 服务,使这一问题得到彻底的解决。java/corba 访问 domino 的优点在于采用此技术开发的应用程序在不用安装 lotus notes 的情况下获取远程 domino 服务器上的数据,真正做到了瘦客户端,为企业节省了不必要的开支,同时也极大的降低了应用程序部署的难度。由于这一技术刚刚发展两三年,国内基本上没有相应的中文资料,corba 技术也属于比较高级的开发技术,绝大多数开发工程师都没有接触过,因此许多做数据集成的开发人员对 domino 的开发望而生畏。本文旨在为开发人员提供一个全面的 java/corba 访问 domino 的技术解决方案,并通过例程指导开发人员掌握这一新技术。其中也有笔者在开发过程中的一些经验与教训,相信对广大开发人员一定会有相当大的帮助。

访问 DOMINO 的方式

本地调用

DOMINO CLASSES 方式

通过使用 lotus 提供的 domino classes 通过本地安装的 lotus notes 访问远程 domino 服务器。此方法的缺点在于应用程序所在的主机需要安装 lotus notes,这在 UNIX 系列平台上或某些项目实施过程中是不允许的。

JDBC 方式

JDBC 方法是使用标准 Java 数据库技术 Domino 提供 JDBC 驱动程序并且它的行为可以看起来和标准关系数据库一样。但是,尽管某些 SQL 扩展允许访问其有层次结构的数据,但这种方法限制了使用 Domino 所能提供的好处。

你可以去 LOTUS 的官方网站下载 DOMINO 的 JDBC 驱动 :

http://www.lotus.com/products/rnext.nsf/873769A79D9C5B2285256A0800720B96/D14669BE33B75CB585256C4700659FDC?OpenDocument

远程访问

EJB 访问方式

通过 websphere 中的 ejb 方式访问远程 domino 服务器,方便快捷,缺点是需要 websphere 作为应用服务器,对软件平台和硬件都有一定的要求。

JAVA/CORBA 方式

一种彻底的解决方案,基于 IIOP 协议,采用 JAVA/CORBA 方式开发的应用程序的运行环境中无须安装 lotus notes 便可以完全访问控制 domino 中的数据。

本文重点介绍 JAVA/CORBA 方式。


CORBA 简介

CORBA(Common Object Request Broker Architecture, 公共对象请求代理体系结构)是由 OMG(对象管理组织,Object Management Group)提出的应用软件体系结构和对象技术规范,其核心是一套标准的语言、接口和协议,以支持异构分布应用程序间的互操作性及独立于平台和编程语言的对象重用。

CORBA 规范充分利用了现今软件技术发展的最新成果,在基于网络的分布式应用环境下实现应用软件的集成,使得面向对象的软件在分布、异构环境下实现可重用、可移植和互操作。


使用 IBM 提供的 CORBA 库访问 DOMINO

你可以去下面地址下载 IBM 提供的针对于 JAVA/CORBA 的 LOTUS 工具库 :

http://www-10.lotus.com/ldd/toolkits开发人员通使用这个库,可以协助你建立:

访问本地或远程的 domino 数据和服务的 JAVA 应用程序;

访问关系型数据库中数据的 JAVA 应用程序;

通过 CORBA,IIOP,IDL 访问远程 domino 对象的 JAVA/C++ 应用程序。

此工具库中带有丰富的文档范例,你完全可以通过这些范例掌握工具库的使用方法,在此不再赘述。但是,IBM 提供的 CORBA 库 dtjava2.1 中存在一些 BUG,在 Domino 5.0.2 or 5.0.3 中某些功能不能正常工作,使用中还存在一些其它问题,如果你感觉使用起来不够方便的话,此篇文章正是解你燃眉之急,指导你如何创建并使用通过自己的 domino corba 库访问 domino 对象 !


根据 IDL 文件创建自己的 CORBA 库

首先你所需要的是一个描述 domino 对象的 IDL 文件,这个文件在上面提到的工具库的 IDL 目录下,名称为 ncorba.idl 。既然使用 corba, 就要有请求对象代理(ORB,Object Request Broker)的辅助,许多厂商都提供了 orb 工具,比较有名的有 visiBroker,Jcorb,OpenORB 等等。业内公认的比较成熟和完善的当推 borland 的 visiBroker, 但由于它是商业软件,且价格不菲,我们便不考虑使用它做 ORB。在众多的免费版本 ORB 中,采用 OpenORB 作为 CORBA 是个不错的选择 , 你可以去 www.openorb.org 下载 OpenORB 的最新版本。OpenORB 的安装非常简单:解压缩下载的压缩文件到你的硬盘上,在 lib 子目录下,有九个 .jar 为后缀的库文件,主要的两个是 openorb-x.x.x.jar 和 openorb_tools-x.x.x.jar( 其中的 x.x.x 根据是你的下载的 openorb 的版本号 )。第一个 jar 包作为 openorb 的核心,必须安装到每一个运行 corba 应用程序的主机上。它包含 OpenORB 在运行时需要的客户端和服务器端的代码。由于 OpenORB 依赖于 xerces 的 xml 解析器,所以 lib 目录下的 xerces.jar 这个文件也要放到和 openorb-x.x.x.jar 同一个目录下。openorb_tools-x.x.x.jar 必须安装到开发主机上,同时它也被 openorb 的内核所依赖,因此应该也安装到 openorb-x.x.x.jar 同一目录下。这九个 jar 包应该都在你的 corba 应用程序运行的 classpath 中,否则你的 corba 应用程序将不能正确编译通过或执行。

好了,做完以上配置工作,我们就可以使用 OpenORB 的 IDL 编译器生成我们自己的 domino 对象了。进入上一节提到的 ibm 的 toolkits 的 idl 目录,找到 ncorba.idl 这个文件 , 在 windows 的控制台模式下,输入如下指令:

 java org.openorb.compiler.IdlCompiler ncorba.idl 

OpenORB 的 IDL 编译器将在当前目录下创建一个名称为 generated 的目录,将生成的 domino 对象的 java 代码都放在这个目录下。

如果执行以上操作不成功,报错有未找到的类,说明你系统的 classpath 的配置有问题,缺少上面提到的九个 jar 包中的一个。请重新配置你的 classpath, 解决这个问题。

在工作中发现 OpenORB 的编译器功能并不够强大,对 idl 中的一些宏定义不能正确转换成符合 java 语法的代码,你在用 jdk 提供的 javac 编译器编译 OpenORB 生成的 java 代码时会发现这个问题,编译器会抛出许多错误。不要着急,你可以用文本编辑工具打开 ncorba.idl 文件,根据刚才 javac 编译器提到的 java 代码中的错误位置,找到 idl 文件中相对应的宏定义,这些宏定义一般都是简单的加法计算,你手工计算出表达式的值将 idl 文件中的这些宏替换后存盘退出后,重新执行上面的提到的操作,用 org.openorb.compiler.IdlCompiler 编译 corba.idl 文件,此时再生成的 java 代码将是完全正确的。我想 OpenORB 以后新版本的编译器将解决这个问题。

将生成的 java 代码编译打包成一个 DominoCorba.jar 文件,我们自己的 domino java/corba ToolKits 诞生了!这个库的一大好处是全部开源的(好像是废话), 十分有利于我们应用程序的调试跟踪。有了自己的功能强大的库,让我们看看如何利用她开发访问 domino 的 java 应用程序吧。


构建自己的 CORBA 库 , 完全驾驭 DOMINO

开发环境的搭建

首先你的机器上要安装 domino 5 或 domino5 以上版本 , 启动 domino 服务器,确定 domino 的 HTTP 服务和 DIIOP(domino IIOP) 服务是否启动,这两个服务是必需的。

你可以在 domino 的控制台输入下面的指令:

 >load http                    ( 启动 http 服务 ) 
 >load diiop                   ( 启动 diiop 服务 ) 
停止服务是:
 >tell http quit               ( 停止 http 服务 ) 
 >tell diiop quit              ( 停止 diiop 服务 ) 
刷新服务是:
 >tell http refresh            ( 刷新 http 服务 ) 
 >tell diiop refresh           ( 刷新 diiop 服务 ) 

假设在 domino 服务器上创建一个名称为 xg-yfwh.nsf 的库文件,后面的例子就是访问这个库中的数据;

假设你的 domino 的用户名为 dev dev/dev, 密码为 12345678。

启动 Domino Admininstrator, 编辑当前服务器文档,在"安全性"一页中,配置右下角的 "Java/COM 限制"配置项,结果如图:



在"Internet 协议"一页中 , 选 HTTP 子项,配置"允许 HTTP 客户浏览数据"选项为"是",如下图:



重新启动 domino 服务器 , 启动 HTTP 和 DIIOP 服务。

JAVA/CORBA 应用程序的开发

注意:以下的程序开发都将用到前面生成的 DominoCorba.jar 库及 OpenORB 库,请配置你的工程的 classpath 包含这两套库的全部文件。

理解 domino 对象的结构

domino 对象类的结构基于包容模型,包容模型定义了对象的范围。容器对象通常被用来访问它所包含的子对象。例如,你可以用 session 对象或的 Database 或 Database 的集合;而一个 Database 对象可以创建一个或若干个 Document 对象。



关闭一个容器对象意味着其包含的全部子对象也将被关闭。例如,你建立了一个 Database 对象,并适应它创建了一个 Document 对象,如果关闭了 Database 对象,Document 对象也会随之关闭。如果容器对象超时,它将会被自动关闭,其包含的对象也将被自动关闭。因此你应该在容器对象超时或关闭前保存你的任何改变。

建立连接

客户端 java 程序向 domino 服务器发出 CORBA 请求,服务器通过 HTTP 协议返回给客户端 IOR(Interoperable Object Reference),之后客户端通过 IIOP 协议与服务器进行通讯。这一握手过程如下图所示:

下面代码实现这一握手过程:

      String dominoHost = "192.168.3.56";// 主机名或 IP 地址
 String strIOR = null; 
      URL url = new URL("http://" + dominoHost + "/diiop_ior.txt"); 
      InputStream in = url.openStream(); 
      BufferedReader br = new BufferedReader(new InputStreamReader(in)); 
      for (boolean bExit = false; !bExit; ) { 
        String line = br.readLine(); 
        if (line == null) { 
          bExit = true; 
        } 
        else { 
          if (strIOR == null) 
            strIOR = line; 
          else 
            strIOR = strIOR + line; 
          if (strIOR.startsWith("IOR:")) 
            bExit = true; 
        } 
      } 
      br.close(); 
 System.out.println("strIOR - " + strIOR); 

如果 屏幕上能够打印出一长串字符,说明握手成功。

创建 session

不用我说,从上面的结构图中,你也可以看出,要想通过 CORBA 同服务器会话,首先一定要创建一个 session, 这也是最容易出问题的一步。一般来讲,能成功的创建 session,后面的困难就不算什么了。

第一步 告知 ORB 架构,用 openorb 作为对象请求代理 :

 Properties props = new Properties(); 
      props.put("org.omg.CORBA.ORBClass", "org.openorb.CORBA.ORB"); 
      ORB orb = ORB.init(args, props); 

第二步 通过 IOR 得到 IObjectServer 对象

            org.omg.CORBA.Object obj = orb.string_to_object(strIOR); 
      System.out.println("org.omg.CORBA.Object - " + obj); 
      IObjectServer ios = IObjectServerHelper.narrow(obj); 
      System.out.println("IObjectServer - " + ios); 
     

第三步 通过 IObjectServer 获得 ISession

      ProtocolVersion maxVersion = new ProtocolVersion(IBase. 
          DOM_MAJOR_MINIMUM_VERSION, IBase.DOM_MINOR_MINIMUM_VERSION); 
      ProtocolVersion minVersion = new ProtocolVersion(IBase.DOM_MAJOR_VERSION, 
          IBase.DOM_MINOR_VERSION); 
 SessionData sd = ios.createSession(maxVersion, minVersion, 
"dev dev/dev",// 用户名称
                                "12345678");// 口令
 System.out.println("SessionData - " + sd); 

如果能够成功打印出结果,说明你的 Session 创建成功。

获取文档内容

获取文档内容,要涉及到几个 domino 对象,现介绍如下:

  • DbCache:是一个描述数据库为开的情况下,数据库可用性一组信息集合。
  • DCData:结果集对象,类似于 JDBC 中的 ResultSet。
  • Idatabase:数据库对象。
  • Idocument:文档对象。

通过调用 iDatabaseHolder 的 search 方法,我们便可以从数据中很容易的取出符合条件的一组文档对象。对比关系型数据库 , 就相当于 SQL 的查询操作。Search 方法异常强大,这强大也得益于 domino 数据库功能的强大,是关系型数据库不可比拟的。Search 方法包含三个参数:

formula: 字符串型,会使用 domino 的设计人员都不会陌生就是 domino/notes 特有的公式语言,只要你会用这种语言,可以随心所欲的获取各种符合你筛选条件的文档集合,如果你还没有掌握公式语言的写法,建议参考 Notes Designer 中的帮助,里面有非常详细的介绍;

Datetime:domino 的日期时间型函数,此参数指定取出的文档的创建或最后更新的日期要晚于这个日期,为空则无任何限制。在关系型数据库中令人头疼的获取增量更新的问题在 domino 中根本就不是问题;

Maxdocs:获取符合条件的文档的最大个数。

例:获取名称为 xg-yfwh.nsf 的数据库中由名称为 FrmApp 创建的更新日期在 2003 年 10 月 1 日下午三点零二分零二秒修改或创建的前 10 条文档

 //sd 就是前面创建的 SessionData; 
 DbCache dbCache = session.getDatabase(sd.serverName, "xg-yfwh.nsf", false); 
 Idatabase iDatabase = dbCache.db; 
 lotus.domino.corba.DateTime date=session.createDateTime("2003/10/1 15:02:03"); 
 IDateTime idt=session.createDateTimeObject(date); 
 DCData dCdata = iDatabase.search("Form=\"FrmApp\"", idt, 10); 

遍历结果集中的文档

IdatabaseHolder:Database 的操纵器,通过它对 Database 对象读写数据。

IntHolder:整数型对象,功能是将服务器返回的整数转换成本机整数形式。

 IDatabaseHolder idatabaseholder = new IDatabaseHolder(); 
 IntHolder intholder = new IntHolder(); 
 IDocument doc = dCdata.dcObject.getFirstDocMDB(idatabaseholder, intholder); 
 while (doc != null) { 
        ItemData[] id = doc.getData().items; 
        for (int i = 0; i %lt; id.length; i++) { 
          ItemValue iv = id[i].values;// 读取数据
          System.out.print("\t" + iv.StringValue()); 
          System.out.print("[ 类型:" + id[i].type + "]"); 
        } 
        System.out.println(); 
		 // 获取下一条文档
        doc = dCdata.dcObject.getNextDocMDB(doc, idatabaseholder, intholder); 
 } 

关闭 session

关闭 session 是编程的良好习惯。代码很简单:

 session.recycle(); 

前面已经提到,在 session 关闭后,其包容的子对象也将被自动关闭。

以上便是 java 通过 corba 访问 domino 数据库的完整过程,下面将提供完成这一过程的完整代码。


一个完整的例子

 import java.io.BufferedReader; 
 import java.io.InputStreamReader; 
 import java.io.InputStream; 
 import java.net.URL; 
 import java.util.Properties; 
 import org.omg.CORBA.*; 
 import lotus.domino.corba.*; 
 public class LotusClient { 
  public static void main(String args[]) { 
    try { 
      String dominoHost = "192.168.3.56"; 
      lotus.domino.corba.DbCache dbCache; 
      lotus.domino.corba.IDatabase iDatabase; 
      lotus.domino.corba.DbInfo dbInfo; 
      lotus.domino.corba.ViewData viewData; 
      lotus.domino.corba.IView iView; 
      // 通过 http 获取 IOR 
      String strIOR = null; 
      URL url = new URL("http://" + dominoHost + "/diiop_ior.txt"); 
      InputStream in = url.openStream(); 
      BufferedReader br = new BufferedReader(new InputStreamReader(in)); 
      for (boolean bExit = false; !bExit; ) { 
        String line = br.readLine(); 
        if (line == null) { 
          bExit = true; 
        } 
        else { 
          if (strIOR == null) 
            strIOR = line; 
          else 
            strIOR = strIOR + line; 
          if (strIOR.startsWith("IOR:")) 
            bExit = true; 
        } 
      } 
      br.close(); 
      System.out.println("strIOR - " + strIOR); 
      // 以 OpenORB 实现 CORBA 
      Properties props = new Properties(); 
      props.put("org.omg.CORBA.ORBClass", "org.openorb.CORBA.ORB"); 
      ORB orb = ORB.init(args, props); 
      System.out.println("org.omg.CORBA.ORB - " + orb); 
      // 通过 IOR 得到 IObjectServer 对象
      org.omg.CORBA.Object obj = orb.string_to_object(strIOR); 
      System.out.println("org.omg.CORBA.Object - " + obj); 
      IObjectServer ios = IObjectServerHelper.narrow(obj); 
      System.out.println("IObjectServer - " + ios); 
      // 通过 IObjectServer 获得 ISession 
      ProtocolVersion maxVersion = new ProtocolVersion(IBase. 
          DOM_MAJOR_MINIMUM_VERSION, IBase.DOM_MINOR_MINIMUM_VERSION); 
      ProtocolVersion minVersion = new ProtocolVersion(IBase.DOM_MAJOR_VERSION, 
          IBase.DOM_MINOR_VERSION); 
      SessionData sd = ios.createSession(maxVersion, minVersion, "dev dev/dev", 
                                         "12345678"); 
      System.out.println("SessionData - " + sd); 
      ISession session = sd.sesObject; 
      System.out.println("ISession - " + session); 
      // 使用 ISession 
      String str = session.getURL(); 
      System.out.println("url - " + str); 
      dbCache = session.getDatabase(sd.serverName, "xg-yfwh.nsf", false); 
      iDatabase = dbCache.db; 
      DCData dCdata = iDatabase.search("1=1", null, 10); 
      IDatabaseHolder idatabaseholder = new IDatabaseHolder(); 
      IntHolder intholder = new IntHolder(); 
      IDocument doc = dCdata.dcObject.getFirstDocMDB(idatabaseholder, intholder); 
      while (doc != null) { 
        ItemData[] id = doc.getData().items; 
        for (int i = 0; i %lt; id.length; i++) { 
          ItemValue iv = id[i].values; 
          System.out.print("\t" + iv.StringValue()); 
          System.out.print("[ 类型:" + id[i].type + "]"); 
        } 
        System.out.println(); 
        doc = dCdata.dcObject.getNextDocMDB(doc, idatabaseholder, intholder); 
      } 
      System.out.println("query:" + dCdata.query); 
      for (int i = 0; i %lt; dCdata.remoteID.length; i++) { 
        System.out.println("remoteID:" + dCdata.remoteID); 
      } 
      while (doc != null) { 
        ItemData[] id = doc.getData().items; 
        for (int k = 1; k %lt; id.length; k++) { 
          ItemValue iv = id[k].values; 
          System.out.print("\t" + iv.StringValue()); 
          System.out.print("[ 类型:" + id[k].type + "]"); 
        } 
        System.out.println(); 
      } 
      System.out.println("--------------------------------------------"); 
      session.recycle(); 
    } 
    catch (Exception e) { 
      e.printStackTrace(); 
    } 
  } 
 } 


如何从 lotus.domino.NotesException 中获取错误信息

在你编写 domino 的 java/corba 应用程序时,不可避免的要同异常处理打交道,NotesException 当然是每个开发人员都不愿见到的但又不得不经常面对的问题。

如果你在写程序时尽是简单的采用

 try{ 
 … . 
 }catch(Exception e){ 
 e.printStackTrace(); 
 } 

的形式捕捉异常,那么一旦代码抛出异常,你将变得一筹莫展,因为屏幕上不会打印出任何对你提供帮助的信息,这方面的资料更是凤毛翎角, 连 ibm 的官方文档中都没有丰富的错误信息提示。错误捕捉代码应该这样写:

 try{ 
	… . 
 }catch(Exception e){ 
 if(ex instanceof lotus.domino.NotesException){ 
 System.out.println( "通过 CORBA 访问数据库发生错误,错误代码为"+((lotus.domino.NotesException)ex).id); 
 } 
 e. printStackTrace(); 
 } 

这是屏幕上将打印出错误代码,有了错误代码,我们还要知道错误代码的含义,这就要到 IDL 文件中去查了。在 corba.dll 文件中 NotesError 这个接口定义了全部错误代码(自己要做一下简单的加法才能知道那里面错误代码的具体值),错误代码的变量名正是错误信息。这样你便可以对出现的问题了然于胸了 , 对你解决突发性事件很有帮助。


针对在 JBOSS 环境下的解决方案

JBOSS 应用服务器作为性能优越的开源 J2EE 应用服务器产品,已经成为越来越多的中小企业的首选服务器解决方案。当你已经学会上面介绍的开发方法,能够自如的开发自己的 JAVA/CORBA 应用程序时,你一定会考虑把自己的程序做成 JAVABEAN 或 EJB 移植到 JBOSS 中。很不幸,你会发现,上面这个在标准 JAVA 环境下可以运行通过的程序,在 JBOSS 下被调用时不能正确执行。会报文件 OpenORB.XM 找不到这样的错误。按照屏幕提示的路径 org/openorb/config 去 openORB.jar 文件中看一下,发现 OpenORB.XML 这个文件好好的呆在那里,怎么会找不到呢?这是个比较隐蔽的问题造成的。在标准的 java 环境下运行上面的应用程序时,OpenORB 也要找这个资源文件,但是他调用默认的 classloader, 这时是 jdk 的 classloader,可以正常的定位这个资源文件;而在 JBOSS 环境下 , 由于 JBOSS 的 classloader 的机制与 jdk 的 classloader 机制不同,不能正确的找到这个资源文件,因此便产生了上面这个问题。经过尝试,我的解决方案是:

将例子代码中的 ORB orb = ORB.init(args, props) 程序行换成下面两行程序:

 java.net.URL url=this.getClass().getResource("/org/openorb/"); 
 ORB orb = ORB.init(new String[]{ "-ORBopenorb.home="+url},props); 

记住上面两行代码中的第一行代码中的"/org/openorb/"的最后一个字符一定要有"/",否则是错误的。

相信这个这个提示会对你的开发有很大帮助。


可能遇到的错误

1. 在创建 SESSION 时,服务器拒绝访问

解决办法:先确认你的应用程序中的用户名,密码是否正确。开启 Domino Administrator, 打开当前服务器文档 , 切换到"安全性"页 , 在右下角会出现"JAVA/COM 限制"一栏,其中有两个选项:

"运行有限制的 Java/Javascript/COM"和"运行无限制的 Java/Javascript/COM"。编辑这两个选项,将你 JAVA 程序中用到的用户名加入到这两个位置,编辑结束存盘退出,重新启动服务器。再次运行 JAVA 程序,问题排除。

2. 在调用 IdbDirectory 类的 getFirstDatabase() 方法时,抛错:lotus.domino.NotesException 错误代码:4536, 错误信息:服务器访问被拒绝

解决办法:开启 Domino Administrator, 打开当前服务器文档,切换到 Internet 协议一页,将允许 HTTP 客户浏览数据后面的选项置为"是"后,编辑结束存盘退出,重起服务器,再次运行 JAVA 程序,问题排除。

读完此文你会发现通过 java/corba 访问 domino 服务器是如此的简单清晰,在 domino 的新版本中也在不断的增加 domino 对 java 的支持。随着 Domino 服务器在企业中应用的普及,这一技术将为企业创造财富发挥更大的威力 !


参考资料

要获得更多关于 CORBA 的权威资料和最新信息,请到 http://www.omg.org/查阅。在 http://openorb.sourceforge.net/可以获得开源项目 openorb 的全部源代码和开发使用文档。如果您想了解更多通过 java 连接 domino 的企业级应用的开发方法,请参阅 IBM 的红皮书: http://publib-b.boulder.ibm.com/Redbooks.nsf/RedbookAbstracts/sg245425.html?Open

关于作者

>薛谷雨是NORDSAN信息科技开发有限公司高级JAVA研发工程师,正致力于企业级异构数据交换的服务器产品的研发,在J2EE和WEB SERVICE方面有较为丰富的开发经验,你可以通过 xgy@sina.com与他取得联系。

posted @ 2010-10-01 07:16  hannover  阅读(1034)  评论(0编辑  收藏  举报