65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET/Java 互操作性:使用服务接口和 DTO 架构模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (8投票s)

2011 年 2 月 7 日

CPOL

5分钟阅读

viewsIcon

52041

downloadIcon

234

本文将向您展示如何使用服务接口和数据传输对象 (DTO) 架构模式,在 .NET 和 Java 之间构建一个简单的跨平台互操作解决方案

引言

运行在 Java/Linux 平台和 .NET/Windows 上的自定义开发分布式系统之间的互操作性至今仍是一个挑战。基于 RPC 的解决方案,如“COM-CORBA 桥接”,可以解决这个问题,但会引入脆弱耦合的重大缺陷。一些基于 Web 服务、数据库集成和远程桥接的专有和开源解决方案也试图解决这个问题。然而,这些解决方案通常包含深层且(在某些情况下是昂贵的)软件堆栈,引入了偶然的复杂性、大量资源占用、陡峭的学习曲线和性能下降。

在本文中,我将提出一个跨平台互操作的架构解决方案。该解决方案在架构上重要的方面是显露、定义和管理跨两个平台的数据结构和交换。该架构基于服务接口数据传输对象 (DTO) 模式。

参考实现演示了如何使用此架构来集成两个业务系统:“履约中心”运行在 Java/Linux 层,“CustomerOrderProcessing”运行在 .NET/Windows 上。

背景

服务接口

服务接口实现了消费者和提供者之间的契约。该契约允许它们即使在不同系统上也能交换信息。服务接口负责执行此通信所需的所有实现细节。

数据传输对象

数据传输对象是一种分发模式,您可以在其中创建可序列化对象以在两个系统之间传输状态。DTO 没有行为,其序列化/反序列化由汇编器对象处理。

XSD

XML Schema 或 XSD 提供构造,以平台/语言无关的格式表达数据类型。例如,定义客户地址信息的 XSD 数据类型将如下所示

<xs:complexType name="Address"> 
 <xs:sequence> 
  <xs:element name="name" type="xs:string" /> 
  <xs:element name="zip" type="xs:integer" /> 
  <xs:element name="city" type="xs:string" /> 
  <xs:element name="country" type="xs:string" /> 
 </xs:sequence> 
</xs:complexType> 

XXsd2Code

XXsd2Code 是一个开源代码生成器,可从 XSD 生成可序列化的 Java、C#、C++ 和 C++/CLI 类。

问题

“履约中心”应用程序是仓库管理系统的一部分,用 Java 编写并运行在 RHEL 上。“CustomerOrderProcessing”是 POS 系统的一部分,用 .NET 编写并运行在 Microsoft Windows 2008 服务器上。

用例

CustomerOrderProcessing 系统处理的任何新订单都需要发送到“履约中心”。一旦履约系统发货,它就会反过来通知“CustomerOrderProcessing”。

SerInterfaceXXsd2Code/system-overall.gif

解决方案大纲

  • 基于 DTO 模式,创建一个表示两个系统之间数据交换结构的模式。这就是接口契约。
  • 将此契约建模为 XSD。
  • 使用 XXsd2Code 在 C# 和 Java 中生成平台特定契约代码。
  • 使用 TCP/IP 作为传输方式为“履约中心”应用程序创建服务接口
  • 为“CustomerOrderProcessing”创建一个连接到此服务接口的服务网关。

结构(类图) design.pdf

SerInterfaceXXsd2Code/design-class-diagram-1.gif

行为(序列图) design.pdf

SerInterfaceXXsd2Code/design-sequence-diagram-1.gif

解决方案实现

服务合同

  1. 定义契约

    使用任何 XSD 编辑器,创建以下数据契约。我使用 Microsoft VS 2005 创建了这个 XSD。

    SerInterfaceXXsd2Code/xsd.GIF

  2. 代码生成 C# 类

    假设 XXsd2Code.exe.xsd 文件位于同一文件夹中,执行以下命令

    XXsd2Code.exe .\/.\/C# 

    这将为 xsd 契约生成 C# 绑定代码

    namespace CustomerOrderAndFulfillment
    {
    { 
    namespace Contract
    [Serializable]
    public class FulfillmentRecord : ICloneable
    {
    public string FulfillmentRecordID;
    public bool IsBackOrder; 
    public FuzzyCondition StorageTemperature;
    ...
    }
    [Serializable]
    public class CustomerOrder : ICloneable
    {
    public string OrderNumber;
    public CreditRating Rating;
    public Address AddressInfo;
    public List<OrderItem> Orders;
    public CreditCardDetails CcInfo;
    ... 
    }
    }
  3. 代码生成 Java 类

    执行以下命令:

    XXsd2Code.exe .\/.\/Java 

    这将为 xsd 契约生成 Java 绑定代码

    package CustomerOrderAndFulfillment.Contract; 
    public class FulfillmentRecord implements Cloneable
    {
    public String FulfillmentRecordID;
    public boolean IsBackOrder;
    public FuzzyCondition StorageTemperature; 
    ...
    }
    package CustomerOrderAndFulfillment.Contract; 
    public class CustomerOrder implements Cloneable
    {
    public String OrderNumber;
    public CreditRating Rating;
    public Address AddressInfo;
    public java.util.List<OrderItem> Orders;
    public CreditCardDetails CcInfo;
    ...
    }

传输

  1. .NET TCP/IP 客户端

    使用 TcpClientNetworkStream 编写一个简单的 TCP/IP 客户端

    TcpClient client = new TcpClient(server,port);
    NetworkStream stream = client.GetStream(); 
    //Send Message
    Byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
    stream.Write(data, 0, data.Length)
    //Receive response – blocking call
    byte[] buffer = new byte[1024];
    stream.Read(buffer, 0, buffer.Length);
    return buffer;
  2. Java TCP/IP 服务器

    使用 ServerSocketBufferedReaderDataOutputStream 在端口 1111 上编写一个简单的 TCP/IP 守护程序。

    ServerSocket welcomeSocket = new ServerSocket(1111);
    Socket connectionSocket = welcomeSocket.accept();
    BufferedReader inFromClient = new BufferedReader(new 
    InputStreamReader(connectionSocket.getInputStream()));
    DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());
    //get request message
    String request = inFromClient.readLine();
    //Send response
    outToClient.writeBytes(response);

数据传输对象

  1. 编写一个汇编器类,使用 XMLSerializer 序列化和反序列化 .NET 服务接口契约类。

    Serialize 方法将 CustomerOrder 对象转换为字节流,以便将其发送到 FulfillmentCenter 服务器。

    static string SerializeCustomerOrder(CustomerOrder order)
    {
    XmlSerializer serializer = new XmlSerializer(typeof(CustomerOrder));
    using (MemoryStream memoryStream = new MemoryStream())
    {
    serializer.Serialize(memoryStream, order);
    return Encoding.UTF8.GetString(memoryStream.ToArray());
    }
    }

    DeSerialize 将来自 FulfillmentCenter 服务器的响应转换为强类型 FulfillmentRecord 对象

    static FulfillmentRecord DeSerializeFulfillmentRecord(byte[] stream)
    {
    XmlSerializer serializer = new XmlSerializer(typeof(FulfillmentRecord));
    using (MemoryStream memoryStream = new MemoryStream(stream))
    {
    return serializer.Deserialize(memoryStream) as FulfillmentRecord;
    }
    }
  2. 编写一个汇编器类,使用 xstream 序列化和反序列化 Java 服务接口契约类。

    DeSerialize 将来自 CustomerOrderProcessing 的请求转换为强类型 CustomerOrder 对象。

    static CustomerOrder DeSerializeCustomerOrder(String request)
    {
    XStream serializer = new XStream();
    RegisterXstreamConverters(serializer);
    return (CustomerOrder) serializer.fromXML(request);
    }

    SerializeFulfillmentRecord 对象转换为字节流,以便将其发送回 CustomerOrderProcessing 系统。

    static String SerializeFulfillmentRecord(FulfillmentRecord 
    fulfillmentRecord)
    {
    XStream serializer = new XStream();
    RegisterXstreamConverters(serializer);
    return serializer.toXML(fulfillmentRecord); 
    }

    XMLSerializerxstream XML 兼容性。

    为确保 xstream 生成与 XMLSerializer 兼容的 XML,请使用以下代码

    xs.aliasType("CustomerOrder", CustomerOrder.class);
    xs.aliasType("OrderItem", OrderItem.class);
    xs.aliasType("FulfillmentRecord", FulfillmentRecord.class); 
    xs.aliasType("Address", Address.class); 

XStream 枚举序列化

为了强制 xstream 正确序列化 enum,编写一个 Enum 转换器

public class EnumSingleValueConverter< T extends Enum<T> > extends 
EnumConverter implements SingleValueConverter 
{
    private Class<T> enumType;
    public static <V extends Enum<V>> SingleValueConverter create(Class<V> 
    enumClass) { return new EnumSingleValueConverter<V>(enumClass); }
    private EnumSingleValueConverter(Class<T> newEnumType) 
    { this.enumType = newEnumType; }
    public boolean canConvert(Class type) { return type == enumType; }
    public Object fromString(String str) {
    Object r = null;
    Method m = enumType.getMethod("fromValue", String.class);
    r = m.invoke(null, str); 
    }
    public String toString(Object obj) { return obj.toString(); } 
} 

使用参考实现

下载并解压参考实现,然后按照以下步骤操作

  1. 运行 GenerateCrossPlatformDataBindings.bat
  2. 打开 .\CustomerOrderProcessing-DotNetClient\CustomerOrderProcessing.sln 并编译它。
  3. 使用 Eclipse 或 Netbeans 打开 .\FulfillmentCenterSimple-JavaTcpIpServer
  4. 导出可运行的 jar 文件并将其命名为 FulfillmentCenterApplication.jar
  5. 使用此命令运行 jar 文件
    java -jar FulfillmentCenterApplication.jar 
  6. 现在使用此命令运行 CustomerOrderProcessing
    CustomerOrderProcessing.exe 127.0.0.1 1111 

注意:如果您修改了 xsd(接口契约),请确保再次运行 GenerateCrossPlatformDataBindings.bat

集成测试

我使用 Eclipse Ganymede 创建了一个可运行的 jar 文件。为了测试我的“履约中心”服务器应用程序,我使用在 Windows 7 主机上作为 VirtualBox 客户机运行的 Ubuntu 10.10。确保为 VM 添加一个桥接网络适配器,以便 Windows .NET 应用程序可以访问在 Ubuntu 客户机上运行的 Java 服务器。现在运行 Java/Linux “履约中心”服务器应用程序。

SerInterfaceXXsd2Code/server-app.gif

现在运行“CustomerOrderProcessing”.NET 应用程序。确保您指向 Linux 虚拟机的 IPAddress

SerInterfaceXXsd2Code/client-dot-net.gif

结论

本文描述的架构解决方案使您能够

  • 使用服务接口模式将互操作性/通信管道与业务应用程序解耦。
  • 使用数据传输对象模式最大限度地减少两个系统之间的调用次数。
  • 通过以下方式显露、定义、管理并确保服务契约的编译时强制执行
    • 使用 XSD 创建平台无关的数据类型
    • 然后使用 XXS2Code 代码生成平台特定的数据类型

历史

  • 2011年2月7日:首次发布
© . All rights reserved.