分享

JMS与Spring.NET间的消息互操作

 集微笔记 2014-04-02

简介

在.NET和Java间存在许多进行互操作的解决方案。其中应用最广泛的是,使用能同时在两种环境下工作的Web服务和WSDL片段以及XML Schema。如你所料,Web服务最适合基于Internet的应用。如果你在开发一个内部网的应用系统,它将被应用在一个单独的部门或机构的局域网中,那么中间件技术就变重要了。尤其是基于消息的中间件(message oriented middleware,MOM),它们早已成为公司在不同系统间进行集成的一个主流选择。本文的场景是一个本地局域网内运行的简单的证券系统,本文通过该场景来介绍.NET客户端与Java中间层间的互操作,其中MOM被作为.NET与Java间通讯的基础。系统实现用到了Spring框架(既有.NET版也有Java版)的JMS支持,来提供连接.NET客户端与Java中间层的通用编程模型。

为何使用消息服务?

在许多情况中,基于消息的中间件都被认为是解决互操作问题的元老。IBM和TIBCO等供应商都已经提供消息中间件产品达20年以上,这些消息中间件都能够工作于各种不同的平台,并存在多种语言的版本。1999年消息服务领域迎来了新的春天,Java消息服务(JMS)规范定义了一系列API的通用集合和消息中间件供应商所需实现的行为。考虑到Java的特点,毫无疑问,JMS的实现可以在绝大部分系统(不是全部,因为还需要.NET API 1) 上运行。本文涉及的例子用到了TIBCO的JMS实现,它的实现方式提供了对Java、.NET、.NET Compact Framework和C++客户端API的支持。顺便说明一下,即使你选择的供应商不提供.NET客户端,仍旧有一些方法可以使你通过.NET来访问JMS。方法之一是使用一个互操作性产品,如JNBridge2 或者Codemesh3

互操作性为使用消息服务提供了一个不容忽视的理由,在许多领域中消息服务都颇具吸引力,即使它这不完全属实,消息服务也会成为系统架构的一个选择。简而言之,MOM擅长于提供流程间的异步通讯、发布-订阅(一对多) 消息来传递语义和保证高可靠性。如果你的应用能够从这些特点中获益的话,你将总能找到一个合适的消息解决方案。如果你的公司已经因为某些理由使用了消息服务,但还局限于Java或C++应用的话,将.NET客户端作为一个逻辑扩展纳入到现有的架构中去吧。撇开冲动的原因,创建基于JMS的应用的过程存在它自己的学习曲线和大量的最佳实践。本文通过在.NET和Java上使用Spring框架,来展示如何最快的开始创建JMS应用。本文还为.NET和Java间的消息通讯中出现的问题提供指导。


Spring.NET介绍

大部分人对用于构建Java应用系统的Spring框架并不陌生,与此同时,许多人未必听说它的.NET版本,Spring.NET4。Spring.NET 是Spring框架的.NET版,它完全用C#编写,为.NET提供了基于Spring的设计并将它付诸于应用系统开发的实践。它的核心功能与Java版相同,例如反转控制、面向方面编程、Web框架、RPC Exporter、声明式事务管理和一个ADO.NET框架。简而言之,如果你已经是一个Java版Spring的使用者,那你就能自如地使用 Spring.NET。

Java版的Spring在底层与许多第三方库绑定,与它不同,Spring.NET将对第三方库支持独立为可下载的模块。这些模块之一提供了基于TIBCO JMS实现的JMS支持。如果你希望运行本文中的示例,你需要从TIBCO的网站5下载评估版的TIBCO EMS(Enterprise Message Service)。

可能产生的疑问有“为什么Spring.NET只支持TIBCO的JMS而不支持<供应商名称>的?”其它供应商并非因为原则性的原因而不支持JMS实现。真实的理由是,因为每个供应商需要在.NET中去实现的并非真正的JMS API。基于这一点,每个供应商不再开发Java JMS API的.NET版本。开源项目.NET消息服务API(NMS)的目标是提供这种通用的API,它极可能未来在Spring.NET 6中的扮演JMS的角色。因为我非常熟悉TIBCO的JMS产品,出于方便性的考虑,我将它用于我的Spring.NET示例中。

基于Spring框架的消息服务

Spring JMS支持的目标是,提升使用JMS时的抽象性和支持消息服务的最佳实践。通过提供易于使用的消息类,Spring达成了这些目标。这其中,消息类能够使通用操作变得简单,并能通过使用MessageConverters来传递消息,从而创建“plain old objects”(POJO或POCO)编程模型。MessageConverters有责任完成JMS消息与“plain old objects”之间的转换,它在本质上与XML/对象间映射器无异,但它支持JMS转换。将JMS产品与应用最外层分离的过程中,使用消息转换器将使你受益,从而使你的应用尽可能地摆脱对技术的依赖。当你打算切换中间件时,如果在业务流程中应用JMS MapMessage的话,所需做的重构工作就要少很多。

像JDBC一样,JMS是一个底层API,它需要你为JMS的绝大部分基础任务来创建并管理中间对象。对发送消息而言,Spring的JmsTemplate管理你的行为所产生的中间对象,并使得通用的JMS操作保持一致。在接收端,Spring MessageListenerContainer的实现允许你简单地创建基本结构,来支持异步并发的消息消耗。JmsTemplate和MessageListenerContainer都与MessageConverter相关联,转换于JMS和POJO/POCO的世界。

JmsTemplateMessageListenerContainersMessageConverters是Spring JMS支持中的核心部分。本文大量地应用了它们,并从细节上解释了它们,但没有为它们提供定义的参考。更多细节请参照Spring参考文档和其它Spring资源。

Spring JMS中的Hello World

在深入Spring JMS和互操作性的细节之前,来看看以下示例,.NET利用JmsTemplate将“Hello World”的TextMessage发送到命名为“test.queue”的JMS队列。

ConnectionFactory factory = new ConnectionFactory("tcp://localhost:7222");
JmsTemplate template = new JmsTemplate(factory);
template.ConvertAndSend("test.queue", "Hello world!");

如果你很熟悉JMS API,那么你立刻会发现,与直接使用JMS相比,使用JmsTemplate有多么的简单。这主要是因为你不再需要去写代码了,JmsTemplate已经提供了JMS连接、会话和MessageProducter的所有模板资源管理。它还为我们提供了一些额外的便利,例如针对Java中未检验情况的已检验转换异常,处理基于JMS点到点对象的字符串,委任MessageConverter来将对象转换为JMS消息。如果你打算不进行转换就发送消息,JmsTemplate提供了一个简单的Send方法。

当调用ConvertAndSend方法时,JmsTemplate使用默认的MessageConverter实现(SimpleMessageConverter)来将字符串“Hello World”转化为JMS TextMessage。SimpleMessageConverter还支持byte数组与ByteMessage,以及hash表与JMS MapMessage间的转换。要提供你自己定制的MessageConverter实现,这取决于复杂Spring JMS应用系统的创建过程。通常情况是创建一个针对对象的转换器,这些对象是你已经在系统代码中使用到的,并且你只是想将它们marshall或者unmarshall成为JMS(译者注:marshall/unmarshall,指将对象序列化为消息对象以及逆向过程。)。

JMS互操作性简述

Spring是同时支持.NET和Java的通用框架,与它相关的围绕互操作性的问题可归纳为,实现MessageConverter和在. NET和Java间交换的“plain old objects”的兼容。为了方便,我们将这些“plain old objects”称为“业务对象”(business objects)。它们可能实际上都是标准的域对象(domain objects),数据转换对象(data transfer objects)或一个用于展示的域对象(domain objects)的UI优化版。在本应用中,业务对象实际上成为了两层之间的数据契约。字段和属性被作为数据契约的一部分,依赖于转换器的实现。需要注意的是,具体的数据类型是没有被明确共享的。涉及的数据类型都需要彼此适应,尽可能使它们所关联的消息转换器正常工作。

尽管本应用肯定不如在web services中使用契约来的规范,但存在一些技术,它们用来在很大程度上管理被交换的数据并减少小错误。基于这个考虑,一个能起到帮助的非技术问题是,在局域网或部门应用中,常常是同一个小组(甚至个人)既开发.NET客户端又开发Java中间层。沟通和持续集成测试能够被替代为对数据契约的大范围使用,这种方式正在被不同的开发小组所采用。如果你乐于使用正式的数据契约来定义层与层之间的交互,那么,正在申请的JMS提案就不太可能让你满意。这就是说,这样松耦合但更不标准的具有互操作性的应用,在我的印象中,已经被成功应用于多个项目中了。

接下来的章节将拿一个逐渐成熟的应用举例,该应用使用了MessageConverter,并在.NET和Java之间同步地保持业务对象。示例应用在一开始就利用Spring中的SimpleMessageConverter来转换哈希表的数据。然后我们创建了一个简单业务对象的.NET和Java实现,以及一对相应的自定义MessageConverter。最终,我们将使用一些技术,通过使用源代码翻译器和通用的MessageConverter(不随意转换对象类型),来减少创建converter与业务对象所产生的冗余效果。每个应用的优缺点都被讨论到了。

股票交易示例

本文中的例子是简化了的股票交易系统。我们的应用有三大主要功能——实时市场数据信息的分发,新交易的创建,以及证券交易的获取。

我们从市场数据的分发入手,我们创建JMS基本结构,利用Java中间层向.NET客户端发送信息。在Java中间层中,JmsTemplate被创建来发送消息。在客户端,一个SimpleMessageListenerContainer用于接收消息。这两个类都默认使用了一个SimpleMessageConverter的实例。因为市场数据显然是键值对的集合(例如PRICE=28.5, TICKER="CSCO"),完全有理由使用默认的转换器,并在.NET与Java之间利用简单的哈希表来进行数据交换。

JmsTemplate与SimpleMessageListenerContainer的配置中都需要一个JMS的连接工厂和JMS的目的对象名称及类型。SimpleMessageListenerContainer还需要一个引用,引用指向JMS MessageListener进行消息处理的回调方法的实现。Spring提供了一个MessageListenerAdapter类,它实现了JMS MessageListener接口,并使用一个MessageConverter来将接收的JMS消息转换为一个对象。MessageListenerAdapter随后用这个对象来调用某个方法,用户提供的具有相应方法签名的处理类实现了该方法。

前述的流程是对例子的最好解释。如果MessageListenerAdapter被配置来使用SimpleMessageConverter,那么传入的JMS MapMessage将被转换为一个.NET IDictionary,并且处理类上方法签名为“void handle(IDictionary data)”的方法将被激活。如果转换器产生了一个交易类型的对象,随后处理类必须包含一个名为handle(Trade trade)的方法。下面的顺序图展示了事件的流程。

上述框架展示了,在Java世界中,“消息驱动POJO”或“消息驱动对象”通常是如何被理解的。因为无论“消息驱动POJO”或“消息驱动对象”都是一个“plain old object”,而不是一个执行消息回调的JMS MessageListener。此应用的一个优点是,你可以轻松地创建集成形式的测试用例,这些测试将通过直接调用处理器方法的方式演练这个应用的流程。另一个优点是,在消息应用中,你经常可以发现MessageListenerAdapter扮演了if/else或switch/case块替代品的角色。SimpleMessageListenerContainer还有许多其它的特点,例如自动发送基于处理器返回值的回应消息,成为自定义流程的子集等。更多细节请查看Spring参考手册文档。

这些对象的Spring配置如下:

中间层发布者:

 id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
name="connectionFactory"> bean="connectionFactory"/>
name="pubSubDomain" value="true"/>
name="defaultDestinationName" value="APP.STOCK"/>

客户端消费者

 id="jmsContainer"
type="Spring.Messaging.Tibco.Ems.Listener.SimpleMessageListenerContainer, Spring.Messaging.Tibco.Ems">
name="ConnectionFactory" ref="connectionFactory"/>
name="PubSubDomain" value="true"/>
name="DestinationName" value="APP.STOCK"/>
name="ConcurrentConsumers" value="1"/>
name="MessageListener" ref="messageListenerAdapter"/>


id="messageListenerAdapter"
type="Spring.Messaging.Tibco.Ems.Listener.Adapter.MessageListenerAdapter, Spring.Messaging.Tibco.Ems">
name="DelegateObject">
type="Spring.JmsInterop.Handlers.SimpleHandler, Spring.JmsInterop"/>

name="DefaultListenerMethod" value="HandleObject"/>

需要注意的是,Spring.NET在 XML中使用’object’标签来代替’bean’标签定义对象。从.NET的角度来看,处理器对象的 “ DelegateObject” 这个名字是不幸的。不过此属性与.NET中delegate的涵义完全无关。

所有发送到客户端的数据都在名为APP.STOCK的JMS主题中,而所有从客户端发送到中间层的数据都位于名为APP.STOCK.REQUEST的JMS队列中。

下面展示的是一个直接发送的JMS消息,它们包含了一些设定的市场数据。

Map marketData = new HashMap();
marketData.Add("TICKER","CSCO");
marketData.Add("PRICE", new Float(23.54));
... jmsTemplate.send(marketData);

在接收端,StockAppHandler的handle方法完成了所有处理过程。

public class SimpleHandler
{
public void HandleObject(IDictionary data)
{
log.InfoFormat("Received market data. Ticker = {0}, Price = {1}", data["TICKER"], data["PRICE"]);
// 直接发送给控制器来更新视图
. . .

}
}

应用执行时所需要的不过是这寥寥数行代码。然而,尽管基于哈希表的数据交换很简单并且切实可行,但它只适合应用于最简单的互操作场景中。我所说的“简单”是指少于5项且每次键值对少于10个的数据交换。它不适合更复杂场景,理由很明显,数据契约太松散,它无法确保两层之间的键值对不出现错误的匹配。这些类能够确保大部分数据内容的正确性,例如提供带参数的构造器,或使用第三方验证库。撇开验证的问题,与引入哈希表来满足MessageConverter的需求相比,直接在中间层为业务流程使用对象更简单。正因如此,为了直接使用这些对象,自定义消息转换器需要被创建并以插件形式纳入Spring的JMS体系结构中。

使用自定义转换器

Spring的MessageConverter接口非常简单。它包含以下两个方法,

public interface IMessageConverter
{
Message ToMessage(object objectToConvert, .Session session);
object FromMessage(Message message);
}

转换消息失败时将抛出MessageConversionException

我们将创建一个自定义消息转换器,来发送一个从客户端到中间层的关于交易创建的请求消息。TradeRequest类记录用户在填写一个创建交易的表单时的输入信息,并且还提供一个验证方法。TradeRequest类包括以下属性,股票代码、分红、价格、订单类型、账号名称、操作(买入或卖出)、requestid以及用户名。转换器的实现是直接编码的,简单地将这些属性添加为JMS MapMessage的对应字段。客户端“ToMessage”的实现代码如下:

public class TradeRequestConverter : IMessageConverter
{
public Message ToMessage(object objectToConvert, Session session)
{
TradeRequest tradeRequest = objectToConvert as TradeRequest;
if (tradeRequest == null)
{
throw new MessageConversionException("TradeRequestConverter can not convert object of type " +
objectToConvert.GetType());
}
try
{
MapMessage mm = session.CreateMapMessage();
mm.SetString("accountName", tradeRequest.AccountName);
mm.SetBoolean("buyRequest", tradeRequest.BuyRequest);
mm.SetString("orderType", tradeRequest.OrderType);
mm.SetDouble("price", tradeRequest.Price);
mm.SetLong("quantity", tradeRequest.Quantity);
mm.SetString("requestId", tradeRequest.RequestId);
mm.SetString("ticker", tradeRequest.Ticker);
mm.SetString("username", tradeRequest.UserName);

return mm;

} catch (Exception e)
{
throw new MessageConversionException("Could not convert TradeRequest to message", e);
}
}

... (FromMessage not shown)
}

“FromMessage” 的实现简单地创建了一个TradeRequest对象,利用消息中获取的值来设定它的属性。具体细节请查看代码。值得注意的是,如果你打算使用属性来marshall或unmarshall数据的话,请确认在一个marshall过程的上下文中,对这些属性的调用不会有任何副作用。

为了使客户端向中间层发送数据,我们需要为此前的JMS结构创建镜像,分别在客户端创建一个JmsTemplate,在中间层创建一个SimpleMessageListenerContainer。中间层的消息容器被配置来使用一个Java版的TradeRequestConverter和一个名为StockAppHandler的消息处理类,该处理类提供了一个方法签名为“void handle(TradeRequest)”的方法。客户端的配置如下:

 name="jmsTemplate" type="Spring...JmsTemplate, Spring.Messaging.Tibco.Ems">
name="ConnectionFactory" ref="connectionFactory"/>
name="DefaultDestinationName" value="APP.STOCK.REQUEST"/>
name="MessageConverter">
type="Spring.JmsInterop.Converters.TradeRequestConverter, Spring.JmsInterop"/>

硬编码后的客户端使用模板的方式如下:

public void SendTradeRequest()
{
TradeRequest tradeRequest = new TradeRequest();
tradeRequest.AccountName = "ACCT-123";
tradeRequest.BuyRequest = true;
tradeRequest.OrderType = "MARKET";
tradeRequest.Quantity = 314000000;
tradeRequest.RequestId = "REQ-1";
tradeRequest.Ticker = "CSCO";
tradeRequest.UserName = "Joe Trader";

jmsTemplate.ConvertAndSend(tradeRequest);
}

在客户端发送消息的顺序图如下:

使用一个简单的POJO消息处理器实现时,中间层的配置如下:

 id="jmsContainer" class="org.springframework.jms.listener.SimpleMessageListenerContainer">
name="connectionFactory" ref="connectionFactory"/>
name="destinationName" value="APP.STOCK.REQUEST"/>
name="concurrentConsumers" value="10"/>
name="messageListener" ref="messageListenerAdapter"/>


id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
name="delegate">
class="org.spring.jmsinterop.handlers.StockAppHandler"/>

name="defaultListenerMethod" value="handleObject"/>
name="messageConverter">
class="org.spring.jmsinterop.converters.TradeRequestConverter"/>



public class StockAppHandler {
protected final Log logger = LogFactory.getLog(getClass());
public void handleObject(TradeRequest tradeRequest)
{
logger.info("Recieved TradeRequest object");
}
}

这样做的优势是,客户端和中间层的开发者能“共享”同一个类,例如TradeRequest,在之前的例子中它既含有数据又提供功能。尤其对于大项目而言,类共享的一个缺点是,创建成对的业务对象和转换器时会出现冗余。如果层与层之间的数据交换很稳定,那么这种冗余就是一次性消耗。然而,如果数据交换每周或每天都在调整,就像项目还在开发一样的话,这就成了一项乏味的任务。

一个有效处理这类问题的方式是,创建一个单独的通用MessageConverter,由它来处理多种消息类型,然后首先用Java编写业务对象,随后使用源代码转换工具来生成C#版的业务对象。下一节对这个应用的讨论涉及更多细节。

通用消息转换器

正如你在前面TradeRequestConverter的代码列表中看到的,它的实现很繁琐,应该不由手工来编码。使用代码生成机制或反射机制的解决方案能够替代人工。示例代码包含一个'XStream'(译者注:XStream是一个开源项目,用于序列化对象与XML对象之间的相互转换),并应用了基于转换器ReflectionMessageConverter的反射机制,该转换器能够转化许多对象。这个转换器的特点及其局限性如下:

  • 利用所有字段成员的反射,依靠字段值将所有对象序列化为消息对象。这个选择可以避免由于为属性编写setter和getter的附加代码,从而产生的副作用。(以添加支持的方式对包含或排除哪些字段进行控制,这是一种进步。这种控制要么基于外部配置,要么利用属性或annotation,这些属性或annotation类似于WCF的DataContract或DataMember的属性。)
  • 支持的字段类型:原生类型 (int、 string等)、Date、Timestamp、以及原生类型的组合类型、hashmap、对象集合(非范型)。还包括循环引用。
  • 被传递的消息呈现为一个格式易于理解的JMS MapMessage对象。这将使其它不使用该对象的转换器的流程也能加入到JMS消息交换中来。
  • JMS提供者必须支持嵌入的映射消息来对集合类进行转换。
  • 提供为任意对象类型注册额外的转换器的功能。
  • 降低.NET与Java之间的耦合程度,因为一个转换器并不需要知道消息的类型。消息中的类型标识表明应该创建何种类型。在.NET或Java中,类型标识都将被单独映射为一个具体的数据类型。本案例中,所有的业务对象都以相似的形式命名,只是在命名空间或包上有所区别。为了方便,这个只进行了最简单的配置。

对这个转换器的开发一直在进行中,不久后Spring.NET网站就会提供下载。

独立的通用MessageConverter中实现了许多交互的应用,困难的工作都被交给成熟的marshall技术来完成了。例如,你可以使用一个XML/Object转换器,并将XML字符串作为JMS消息的有效负载进行发送。最近Tangosol提出了一种平台和语言无关的轻量级对象格式(Portable Object Format,POF),它同样可以被用于这种目的。

示例应用使用ReflectionMessageConverter将Trade对象发送到相应TradeRequest的客户端。一个发送更复杂对象的例子是,客户端发送PortfolioRequest,并接收一个Portfolio对象, User对象和Trade对象列表被包含在其中作为响应。这个转换器的配置文件如下:

 name="reflectionMessageConverter"
type="Spring.JmsInterop.Converters.ReflectionMessageConverter, Spring.JmsInterop">
property name="ConversionContext" ref="conversionContext"/>


name="conversionContext" type="Spring.JmsInterop.Converters.ConversionContext, Spring.JmsInterop">
name="TypeMapper" ref="typeMapper"/>


name="typeMapper" type="Spring.JmsInterop.Converters.SimpleTypeMapper, Spring.JmsInterop">


name="DefaultNamespace" value="Spring.JmsInterop.Bo"/>
name="DefaultAssemblyName" value="Spring.JmsInterop"/>


上述对TypeMapper简单的配置风格将完全限定类型名的最后一部分作为类型标识,在marshall的过程中放入了被传输的消息中。在unmarshall的过程中,DefaultNamespace和DefaultAssemblyName属性都被用于构建完全限定类型名。Spring的Java版中对mapper的相应定义配置如下:

 id="classMapper" class="org.spring.jmsinterop.converters.SimpleClassMapper">
name="defaultPackage" value="org.spring.jmsinterop.bo"/>



IdTypeMapping或IdClassMapping的属性(被标注为注释的)展示了你如何能避免使用类的完整名称,以及如何使用任意的标识符来指定类型。

共享业务对象

在保持业务对象时,能通过保持对象同步来减轻效果的一项技术是,使用Java语言转换器(JLCA)来自动将Java对象转换为C#7对象。当这个工具被用于对Java代码的一次性转换时,它被归入自动化构建过程,用于在Java和.NET间同步业务对象。业务对象实际上是转换器的候补,因为它们不包含特定技术的API,例如数据访问API或Web编程API,而这些API在不进行后期手工调整的情况下,很难正确转换。

然而,JLCA并非没有瑕疵的。尽管存在一些限制和古怪之处,但你仍然可以建立复杂的C#类,并且在不需要手工调整的情况下成功将其转换为Java类。最值得注意的古怪之处是方法名都被转换成了小写字母,并且JavaBean的get和set方法被转换成了.NET的属性。其它限制是,annotation不能被转换为属性,并且缺少对范型的支持。命名空间被作为java的包名,不过简单的正则匹配过程就能够轻松地解决这个问题。转换器还需要创建所需的一些支持类的C#实现,例如C#版的java.util.Set。通过少许实践你就会明白应该如何将这项技术应用到你的项目中。Gaurav Seth博客8上用一个"cheat sheet"总结了该转换器的功能。最后来看看提供JLCA的公司ArtinSoft,这个公司同时还销售自己的产品JLCA Companion,该产品允许你添加或调整转换的规则9

在本示例中,对Java类运行JLCA的效果很好。你可以通过在.NET解决方案中包括或排除”Bo”和”Jlca”目录,从而切换使用手工编码的C#业务对象或JLCA生成的业务对象。尤其可以查看或修改TradeRequest类中的验证方法,这个验证方法用到了简单的条件逻辑和对集合类的控制。在示例中提供了一个ant脚本,用于在Java业务对象上运行JLCA,并将包名改为正确的.NET命名空间。

客户端在接收少量市场数据事件后,同时发送了一个TradeRequest和一个PortfolioRequest,以下是这个场景的截图:

总结

如果你已经开始使用消息服务,或者打算使用消息服务的一些特性,例如异步通信和发布/订阅的投递,那么在Java和.NET中使用Spring的JMS支持将为你新建互操作性解决方案提供一个很高的起点。Spring在使用协议确保JMS生产者与消费者间的兼容性方面并不那么规范,但它提供了MessageConverter这个简单的扩展,使你能够为你的应用去定制协议。成熟的转换器和相关联的对象能够适应你应用系统复杂性的要求。这个股票交易系统和ReflectionMessageConverter构成你的这个简单实验的基础。

再次提到一个广为流传的关于Spring框架的描述——“它使简单的东西实现起来更简单,使困难的东西具有了实现的可能”。在.NET与Java的混合环境中,Spring的JMS支持同样符合这种说法,我希望你对这个观点会认同。本文到此即将结束,无论你为互操作性选择哪一条路线(.NET或Java),在.NET和Java上使用Spring都能使你受益,因为在这两个技术领域中,同样的编程模型和最佳实践都能轻松共享。

本文相关的代码请点击这里下载




    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多