优雅天平

享受喧嚣 安于平静
posts(18) comments(167) trackbacks(0)
  • BlogJava
  • 联系
  • RSS 2.0 Feed 聚合
  • 管理

常用链接

  • 我的随笔
  • 我的评论
  • 我的参与
  • 最新评论

留言簿

  • 给我留言
  • 查看公开留言
  • 查看私人留言

随笔档案(18)

  • 2006年4月 (1)
  • 2006年3月 (1)
  • 2005年12月 (3)
  • 2005年10月 (2)
  • 2005年9月 (1)
  • 2005年7月 (2)
  • 2005年6月 (3)
  • 2005年3月 (2)
  • 2005年2月 (3)

文章分类(62)

  •  eclipse(2)
  •  Guitar(11)
  •  J2EE相关(5)
  •  JAVA基础(7)
  •  Solaris相关(1)
  •  WAP相关(1)
  •  web services(12)
  •  web页面基础(4)
  •  开源相关(16)
  •  数据库/仓库(2)
  •  模式相关(1)

文章档案(61)

  • 2006年2月 (1)
  • 2006年1月 (3)
  • 2005年12月 (32)
  • 2005年11月 (8)
  • 2005年9月 (6)
  • 2005年8月 (1)
  • 2005年6月 (6)
  • 2005年5月 (1)
  • 2005年3月 (1)
  • 2005年2月 (2)

相册

  • 大事记
  • 我的照片

blog

  • 小江西
  • 铁手剑谱的blog-strust
  • 陶永胜书法艺术中心

站点搜藏

  • Matrix-与Java共舞
  • OpenSource
  • 中国java开发网

搜索

  •  

积分与排名

  • 积分 - 142169
  • 排名 - 438

最新评论

  • 1. re: JFreeChart完全攻略示例各种方法和属性[未登录]
  • sdfg
  • --111
  • 2. re: JFreeChart完全攻略示例各种方法和属性
  • 分数线烦不烦很舒服发个号发给合适的给他发所提供合适的土壤规范不能是纳税人他呢过生日能接受洒脱人呢是vae回复身体然后呢托管人粉红丝带让他反感 受托人复活币善待他人回事
  • --倒萨倒
  • 3. re: Jcreator注册码[未登录]
  • 有谁有jcreator 5.0 pro的注册码呀?跪求
  • --liu
  • 4. re: 开启log4j的记录源代码行号的配置,会对系统性能产生影响!
  • 怎么说呢?理解的有点片面。。。jdk1.4之前的话的确对性能影响比较大,自从1.4开始引入了StackTraceElement之后,对性能影响不是那么大。。。
  • --黄大仙
  • 5. re: 请问MYECLIPSE在哪里输入注册码呀?[未登录]
  • 对我这个初学者有帮助,嘿嘿。
  • --Flyer

阅读排行榜

  • 1. Jcreator注册码(22630)
  • 2. java日期相减问题(20239)
  • 3. Myeclipse注册码(13937)
  • 4. jbuilder 2006 破解文件(13684)
  • 5. 修改 Oracle9i 8080端口问题(2884)

评论排行榜

  • 1. jbuilder 2006 破解文件(58)
  • 2. Jcreator注册码(50)
  • 3. Myeclipse注册码(18)
  • 4. JBuilder2005光标问题更好的解决方法(6)
  • 5. B/S结构程序等待查询结果超时问题(5)

View Post

JAXM开发Web服务的构架和模式(good2)

级别: 初级

陈亚强, 高级软件工程师

2003 年 6 月 01 日

本文是本系列的第二篇,前一篇我们介绍了JAXM的开发技术,在这篇里,我将结合前一篇的案例来讨论JAXM Web服务的构架和设计模式。

本文是本系列的第二篇, 前一篇我们介绍了JAXM的开发技术,在这篇里,我将结合前一篇的案例来讨论JAXM Web服务的构架和设计模式。

阅读本文前您需要以下的知识和工具:

JavaTM Web Services Developer Pack 1.1,并且会使用初步使用;

至少会使用一种EJB容器来开发、部署EJB,并且会在客户端调用EJB组件;

对J2EE平台有比较全面的了解;

对UML比较熟悉。

本文的参考资料见 参考资料

本文的全部代码在这里 下载

系统构架



消息传送方式


JAXM使用SOAP消息在消息客户端和服务端传送消息,消息有两种类型,一种是SOAPConnection,另一种是ProviderConnection。前者是一种点对点的消息发送模型,后者需要通过MessageProvider来把消息传送到目标。它们的消息传送路径如图1所示。




图1 单向和双向的消息传送方式

不使用MessageProvider,可以带来一些便利,比如:

客户端可以是一般的J2SE程序(本文讲述的案例就是如此);

不需要额外的配置。

但是,不使用MessageProvider也有以下的限制:

只能发送request-response类型的消息;

客户端只能是客户端的角色。

本文的案例采用了点对点的消息传送方式,调用环境如图2所示。




图2 JAXM调用环境





整体构架


系统体系结构如图3所示。




图3 系统体系结构

上图的分层模型和J2EE应用程序的分层模型基本一致,不同的是客户端和JAXM Servlet数据的通信是封装成SOAP,但是,它也是HTTP的调用。




用例


本案例共有三个用例,它们分别是按名字查找书,按类别查找书,查找所有的书。如图4所示。




图4 用例图





数据模型


在数据库里,图书信息用图5的格式保存。




图5 数据库表

为了传输数据的方便,减少远程调用的次数,特别设计了BookVO来代表图书的信息,BookVO是一个值对象,如图6所示。




图6 BookVO值对象类图

值对象设计模式在J2EE模式中是非常有名且大量使用的设计模式,相信读者会很熟悉。我们知道,JAXM Servlet和EJB组件之间传递数据是通过对象来传递的,这个对象就是包含有BookVO实例的java.util.Collection。但是JAXM和客户端是通过SOAP消息来传递的(当然,也可以使用序列化的对象作为附件发送),为了传输图书信息,我们就要定义对应的DTD(或者schema)。针对以上的模型,定义的DTD如例程1所示。

例程1 传输图书信息的格式(book.dtd)



<!ELEMENT books(book*)>
<!ELEMENT book ( name, publisher, price, author+, category, description ) >
<!ELEMENT name (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ELEMENT price (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT category (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ATTLIST book  id  CDATA #REQUIRED>





业务逻辑


业务层是EJB组件,这里使用了两个EJB组件,一个是BookServiceFacadeEJB,它是一个有状态会话Bean,另一个是BookEntityEJB,它是一个实体Bean,代表了BookEntityTable的持久数据。

BookEntityEJB组件类图如图7所示。




图7 BookEntityEJB组件的类图

其中,isbn是BookEntityEJB组件的主键,BookEntityHome定义了几个find方法,它们是:

findAllBook(),查找所有的书,返回的是由BookEntity组成的Collection;

findByCategory(String category),按类别查找书,返回的是由BookEntity组成的Collection;

findByName(String name),按书名查找书,返回的是一个BookEntity的远程引用。

BookServiceFacadeEJB是会话门面,JAXM Servlet通过它来和BookEntityEJB交互。它的类图如下:




图8 BookServiceFacadeEJB组件的类图

同样,它也定义了几个find方法,但是和BookEntityHome接口不同的是,它返回的是包含了BookVO值对象的Collection,而不是包含了BookEntity远程引用的Collection。具体的实现细节请参考源代码。

EJB组件之间的依赖关系如图9所示。




图9 EJB组件之间的依赖关系





JAXM服务端


在JAXM 服务端设计了三个服务JAXM Servlet,分别对应了图4中的三个用例,它们是:

ListAllBook:查找所有的书;

BookDetail:按书名查找某本特定的图书;

ListByCategory:按类别查找。

它们的建模关系如图9所示。




图9 服务端JAXM Servlet的类图

每个JAXM Servlet都有一个SOAPMessage onMessage(SOAPMessage message)方法,这个方法在它们接收到SOAP消息时调用,可以说是客户端调用服务端的入口。





客户端


最终客户端是一个叫BookClientGUI的图形界面程序,它并不直接和SOAP消息打交道,它是通过JAXMDelegate类来和服务端JAXM Servlet进行交互的,这里我们简单列出它的类图,在接下来的设计模式里将详细介绍。




图10 客户端类图

在上图中,请注意到BookBusiness接口,它是BookClientGUI和JAXMDelegate通信的桥梁,这里也体现了面向接口编程的思想。





设计模式


JAXM进行Web服务开发还不是特别普遍,故对它的设计模式的探讨还比较少。但是它也有它自己的特点,基本上来说,它的设计模式和J2EE平台其它组件设计模式是一致的。我们在使用J2EE设计模式时,基本上有以下几点的考虑:

  • 减少远程调用的次数(使用值对象、值列表组装器、值对象组装器模式)
  • 降低组件之间、层(Tier)之间的耦合(使用会话门面、业务代表模式)
  • 减少服务查找的复杂度(使用服务定位器模式)
  • 数据的一致访问(使用数据访问对象模式)
  • 进行异步通信(使用服务激发器模式)

JAXM也是J2EE平台的一种技术,它当然可以使用J2EE核心模式中的任何一种,但是它有自己的特点,比如客户端和服务端是通过SOAP消息进行通信,这个和J2EE平台的其它组件之间通信是不同的。在JAXM编程中,为了实现数据(这里是SOAP消息)的一致返回,我们可以使用XML业务代表的模式。

JAXM进行编程时,数据传递的特点如图11所示。




图11 JAXM数据传输的特点

从上图可以看出,客户端最终要使用的数据是java对象或者Java的基本数据类型,而客户端和服务端的通信是通过SOAP消息格式来传输的;同样,在服务端,它要调用业务逻辑,也必须使用java对象或者是java基本数据类型。这样就存在数据的传输和数据的使用的矛盾,为了解决这个矛盾,降低层(Tier)之间的耦合度,使数据易于处理,我们可以使用一个数据转换器来转换数据。当客户端要发送数据时,它使用数据转换器把请求数据转换成SOAP消息格式;在服务端,它调用了业务逻辑后,为了使数据能在internet上传输,它要使用数据转换器把调用结果封装成SOAP消息。接下来我们来看怎么处理这个问题。





客户端模式--JAXM业务代表


在客户端,通过使用JAXM业务代表,可以降低最终客户和SOAP消息的耦合度。系统的结构如下。




图12 JAXM业务代表

JAXM业务代表使用数据转换器来转换数据,业务代表直接和Web服务进行交互,它屏蔽了Web服务请求的复杂过程,为客户端提供易于使用的接口。

此案例中,具体实现的类图如下:




图13 客户端类图

图中的JAXMDelegate为JAXM业务代表,它实现了BookBusiness接口,它是此模式的核心,它实现的方法是客户端可以直接调用的方法。

BookBusiness接口定义了和最终客户端(BookClientGUI)交互的方法,BookBusiness接口如例程2所示。

例程2 BookBusiness接口



package com.hellking.webservice;

import java.util.Collection;
public interface BookBusiness 
{   
   /**
    * @return Collection,查询所有的书
    */
   public Collection getAllBooks();   
   /**
    * @param name
    * @return BookVO
    *查询某本特定的书
    */
   public BookVO getTheBookDetail(String name);
   
   /**
    * @return Collection
    *按类别查询图书 
*/
   public Collection getBookByCategory(String category);
}

SOAPToBeanEngine是数据转换器,它负责把具体的SOAP消息转换成客户端可以使用的数据。SOAPToBeanEngine实现了DTOEngine接口,我们看DTOEngine接口的具体代码,如例程3所示。

例程3 DTOEngine接口



package com.hellking.webservice;

import java.util.Collection;
import javax.xml.soap.SOAPMessage;
public interface DTOEngine
{
 public void build();//把SOAP Message转换成Bean(对象)的具体代码。
 public Collection getResult();//返回转换结果。
 public void init(SOAPMessage msg);//初始化,msg为要转换的信息。 
}

以上三个方法是每个把SOAP消息转换成Java对象的数据转换器(如SOAPToBeanEngine)都必须实现的方法。实际上,这里的SOAPToBeanEngine只能转换BookVO相关的信息,如果要把此模式的框架设计得更加完美,还需进一步抽象,比如抽象到只要传入相关的值对象类(BookVO.class)和SOAP Message就能转换成对应的Bean结果集。

当客户端(BookClientGUI)发出一请求时,它调用JAXMDelegate对应的方法,JAXMDelegate根据请求构造对应的SOAP消息,然后把消息发送到服务端(如ListByCategory Servlet),服务端根据客户的请求做出对应的处理,并把处理结果返回到JAXMDelegate,JAXMDelegate使用SOAPToBeanEngine把返回的SOAP Message转化成Java对象(如值Bean),最后返回给客户端(BookClientGUI),BookClientGUI再把获得的数据进行处理后显示。

假如客户端要按类别查询图书信息,我们来看下一个顺序图,如图14所示。




图14 按类别查询图书客户端顺序图

JAXMDelegate是此模式的核心,我们来看一下它的代码,如例程4所示。

例程4 JAXMDelegate的部分代码



package com.hellking.webservice;
import java.net.*;
import java.io.*;
import java.util.*;

import javax.xml.soap.*;

public class JAXMDelegate implements BookBusiness
{
 SOAPConnection con =null;//到服务端的连接
 EndpointLocator locator=new EndpointLocator();//服务定位器
 Collection allbook;//cache
 DTOEngine dto;//数据转换对象
 
 public JAXMDelegate()
 {
  allbook=new ArrayList();
  dto=new SOAPToBeanEngine();
  try
  {
   SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
           con = scf.createConnection();//生成一个用于SOAP调用的连接
        }
        catch(Exception e)
        {
         e.printStackTrace();
        }
    }
    //构造SOAP消息
    public SOAPMessage createMessage(String target,String name,String category) 
    {
     try
     {
       MessageFactory mf = MessageFactory.newInstance();
          SOAPMessage msg = mf.createMessage(); 
       SOAPPart sp = msg.getSOAPPart();   
   SOAPEnvelope envelope = sp.getEnvelope();   
   //SOAPHeader hdr = envelope.createSOAPHeader();
   //AttachmentPart attachment = message.createAttachmentPart();
   SOAPBody body = envelope.getBody();
    Name bodyName=envelope.createName(
"books",target,"http://hellking.webservice.com");
   SOAPBodyElement gpp=body.addBodyElement(bodyName);
   //如果category不空,那么构建一个按类别查找的SOAP消息
   if(category!=null)
   {
    gpp.addChildElement("category").addTextNode(category);
   }
   //如果name不空,那么构建一个按图书名字查找的SOAP消息
   if(name!=null)
   {
    gpp.addChildElement("name").addTextNode(name);
   } 
   msg.saveChanges();
   //msg.writeTo(new FileOutputStream("e://d.msg"));
   return msg;
  }
  catch(Exception e)
  {
   e.printStackTrace();
   return null;//一般要进行错误处理,这里省略
  }
  
 }
 //按类别查找书,业务代表方法
 public Collection getBookByCategory(String category)
 {
  try
  {
   SOAPMessage msg=createMessage("GetBookByCategory",null,category); 
         String endpoint=locator.getBookByCategory_Endpoint();
       SOAPMessage reply=con.call(msg,new URL(endpoint));
       reply.writeTo(System.out);
       dto.init(reply);//初始化数据转换器
       return dto.getResult();//返回数据转换结果
      
     }
     catch(Exception ex)
     {
      ex.printStackTrace();      
      return null;  //一般要进行错误处理,这里省略。    
     }
  }
 //查询所有的图书,业务代表方法  
    public Collection getAllBooks()
    {
     /**
*allbook为JAXMDelegate的cache,由于Web服务调用代价比较高,
*故使用它来减少不必要的远程调用。如果allbook为空,那么调用对应的Web服务
*来获得数据,并且把调用结果保存在allbook中,如果不为空,那么直接返回allbook
*中的数据。
*/
     if(allbook.size==0)
     {      
      try
      {
       SOAPMessage msg=createMessage("GetAllBooks",null,null);
       String endpoint=locator.getAllBooks_Endpoint();
       SOAPMessage reply=con.call(msg,new URL(endpoint));
       reply.writeTo(new FileOutputStream("e://out.msg"));
       dto.init(reply); //初始化数据转换器
       Collection re=dto.getResult();  //获得转换结果        
       allbook=re;
       return re; //返回数据转换结果
      }
      catch(Exception e)
      {
       e.printStackTrace();

       return null; //一般要进行错误处理,这里省略
      }   
   }
   else
   return allbook;     
    }
    //按图书名字查找某本图书,业务代表方法
    public BookVO getTheBookDetail(String name)
    {
     try
     {
      SOAPMessage msg=createMessage("GetBookDetail",name,null);
      String endpoint=locator.getTheBookDetail_Endpoint();
      SOAPMessage reply=con.call(msg,new URL(endpoint));
      reply.writeTo(System.out);
      dto.init(reply); //初始化数据转换器
      Collection ret=dto.getResult();      
      if(ret.size()==1)
      {
       return (BookVO)ret.iterator().next();
      }
      else
       return null;                   
     }
     catch(Exception e)
     {
      e.printStackTrace();
      return null; //一般要进行错误处理,这里省略
     }    
  } 
}





服务端模式


服务端的模式和客户端的模式基本一样,只是处理过程相反。服务端从客户端接收到SOAP消息后,然后读取参数,调用对应的业务方法,然后使用SOAPToBeanEngine来把调用的结果转换成SOAP消息返回。

如图15所示是相应的数据转换模型。




图15所示是相应的数据转换模型

在服务端,数据转换器负责把对象转换成SOAP消息,这里和客户端是相反的。服务端类图如下。




图16 服务端类图

在图16中,OTDEngine接口定义了把Bean转换成SOAP消息的方法,如例程5所示。

例程5 OTDEngine接口定义的方法



package com.hellking.webservice;

import javax.xml.soap.*;
import java.util.Collection;
public interface OTDEngine
{
 public void build();//构造SOAP消息
 public SOAPMessage getResult();//返回结果
 public void init(Collection c,String type);//初始化 
}

OTDEngine定义了把Bean转换成SOAP消息需要的方法:build()、init()、getResult()。

XMLBusinessDelegate是此模式的核心,它调用业务逻辑,并且使用BeanToSOAPEngine来转换结果。我们来看它的部分代码,如例程6所示。

例程6 XMLBusinessDelegate部分代码



package com.hellking.webservice;
import javax.naming.*;
import com.hellking.webservice.ejb.*;
import java.util.*;
import java.rmi.*;
import javax.xml.messaging.*;
import javax.xml.soap.*;
public class XMLBusinessDelegate
{
 InitialContext init=null;
 BookServiceFacadeHome facadeHome;
 OTDEngine otd;
 public XMLBusinessDelegate()throws NamingException
 {
   init=this.getInitialContext();
   otd=new BeanToSOAPEngine();        
 } 
 public static InitialContext getInitialContext() 
      throws javax.naming.NamingException 
   {
       Properties p = new Properties();
        //…  p.put(XXX,XXX)
       return new javax.naming.InitialContext(p);
   }
   //查找所有的图书
   public SOAPMessage listAllBook()
   {  
  try
  {
    //查找业务组件à调用业务逻辑à构造SOAP消息à返回消息。 
       Object objref = init.lookup("ejb/bookservicefacade");  
           facadeHome = (BookServiceFacadeHome)
javax.rmi.PortableRemoteObject.narrow(objref, BookServiceFacadeHome.class); 
   Collection result=facadeHome.create().getAllBook();
   System.out.println(result.size());
   otd.init(result,"GetAllBooks");//初始化BeanToSOAPEngine
   SOAPMessage ret=otd.getResult();//获得结果
   return ret;         
    }
    catch(Exception e)
    {
      e.printStackTrace();
      return null;
    }
   }
   //按Category查找图书
   public SOAPMessage listByCategory(String category)
   {
     try
  {
  //查找业务组件à调用业务逻辑à构造SOAP消息à返回消息。 
      Object objref = init.lookup("ejb/bookservicefacade");
  facadeHome = (BookServiceFacadeHome)
javax.rmi.PortableRemoteObject.narrow(objref, BookServiceFacadeHome.class); 
   Collection result=facadeHome.create().findByCategory(category);
   otd.init(result,"GetBookByCategory");//初始化BeanToSOAPEngine
   SOAPMessage ret=otd.getResult();//获得结果
   return ret;    //返回结果     
    }
    catch(Exception e)
    {
      e.printStackTrace();
      return null;
    }
   }   
    //查询某本特定的图书。
   public SOAPMessage getBookDetail(String name)
   {
     try
  {
  //查找业务组件à调用业务逻辑à构造SOAP消息à返回消息。 
    Object objref = init.lookup("ejb/bookservicefacade");  
        facadeHome = (BookServiceFacadeHome)
javax.rmi.PortableRemoteObject.narrow(objref, BookServiceFacadeHome.class); 
   Collection result=facadeHome.create().getBookDetail(name);
   otd.init(result,"GetBookDetail");
   SOAPMessage ret=otd.getResult();
   return ret;         
    }
    catch(Exception e)
    {
      e.printStackTrace();
      return null;
    }
   }  
…
}

假如客户端传来要按类别查询图书信息,ListByCategory Servlet将调用XMLBusinessDelegate 的listByCategory(String category)方法,XMLBusinessDelegate查找BookServiceFacadeHome接口,生成BookServiceFacade应用,调用getBookDetail(name);方法,然后初始化OTDEngine,最后调用getResult()方法来返回结果。顺序图如图17所示。




图17 按类别查找图书的服务端顺序图





总结

本篇结合具体的案例介绍了JAXM Web服务开发的体系结构和设计模式。数据转换在设计中占有很大的分量,总的来说,从客户端发出的数据要经过以下途径:

java数据类型àSOAP请求消息àjava数据类型à业务逻辑返回的java数据类型àSOAP相应消息àjava数据类型

业务代表模型在以上数据转换和业务处理起着重要的作用。

posted on 2005-12-16 16:56 Victor 阅读(448) 评论(0)  编辑  收藏 所属分类: web services

新用户注册  刷新评论列表  

只有注册用户登录后才能发表评论。


网站导航:
博客园   IT新闻   Chat2DB   C++博客   博问   管理
相关文章:
  • 使用JAXB处理XML文档--先睹为快
  • JAXM开发Web服务的构架和模式(good2)
  • 用JAXM开发Web服务(good)
  • 用JAXM发送和接收SOAP消息
  • SOAP Header:扩展SOAP能力的途径
  • 使用SOAP开发java web服务--Axis开发方案
  • 基于JAX-RPC的快速Web服务开发方案
  • 深入探索SOAP1.1--使用SAAJ1.2.1
  • 实现安全的AXIS Web服务,第2部分(转)
  • 实现安全的AXIS Web服务,第1部分(转)
 
 
Powered by:
BlogJava
Copyright © Victor