Cloud Foundry samples学习笔记7:Cloud Foundry上的Spring Integration应用集成

来源:互联网 发布:网络博客游戏开发平台 编辑:程序博客网 时间:2024/05/07 03:11

在实际项目开发中经常会有应用集成的需求,将几个分离的应用程序整合到一起,相互之间进行通信(传递消息或协同工作)或数据共享。Spring Integration可以很好地满足这一需求。SpringIntegration能在基于Spring的应用中进行轻量级的消息通信,并通过适配器与外部系统集成。这些适配器提供了一个更高级别的抽象,超越了Spring对远程调用、消息队列和调度的支持。

wgrus样例程序(实在想不通这名字怎么来的)就使用了SpringIntegration实现了两个简单的Spring应用的集成。两个Spring应用分别是wgrus-store和wgrus-inventory,前者接收用户提交的订单并通过Integration的消息通道(channel)转发,后者则从通道中提取消息并放入一个订单队列中缓存,该队列只记录最近25条订单信息。当然消息传递的过程比这稍微复杂一点,要流经一条channel链条。另外虽然程序中注入了mongodb依赖,但运行中并未发生数据的持久化。说白了就是多此一举

一、消息生产者wgrus-store

store部分的controller只有1个,StoreFront.java :

package org.wgrus.web;import java.util.concurrent.atomic.AtomicLong;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.integration.MessageChannel;import org.springframework.integration.core.MessagingTemplate;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import org.wgrus.Order;/** * Handles order requests. */@Controller@RequestMapping(value="/")public class StoreFront {private final AtomicLong orderIdCounter = new AtomicLong(1);@Autowired @Qualifier("orderChannel")private MessageChannel orderChannel;@RequestMapping(method=RequestMethod.GET)public String displayForm() {return "order";}@RequestMapping(method=RequestMethod.POST)public String placeOrder(@RequestParam String email, @RequestParam int quantity, @RequestParam String productId, Model model) {long orderId = orderIdCounter.getAndIncrement();Order order = new Order(orderId);order.setEmail(email);order.setQuantity(quantity);order.setProductId(productId);MessagingTemplate template = new MessagingTemplate(this.orderChannel);template.convertAndSend(order);model.addAttribute("orderId", orderId);return "order";}}
在它的post方法中生成了一个Order订单对象并使用Spring Integration的消息模板MessagingTemplate发送到了一个MessageChannel通道中,这就完成了一个订单的提交。看到这里不免心生疑惑,这个通道究竟是干什么的,消息进入通道就OK了?下面我们就来看一下通道内部的细节,这些细节就在应用程序上下文配置wgrus-store / src / main / resources / root-context.xml 当中。

应用程序上下文中除了我们所熟悉的rabbit、mongo以及Cloud Foundry的cloud等命名空间之外,在最顶层的bean中还引用了一个新的xmlns:int

xmlns:int="http://www.springframework.org/schema/integration"xsi:schemaLocation=http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp-2.1.xsdhttp://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd

"int:"是Spring Integration的命名空间。引入它,就可以进行应用集成的消息通道配置了,像下面这样:

<int:channel id="orderChannel"/><int:object-to-json-transformer input-channel="orderChannel" output-channel="jsonOrders"/><int:claim-check-in input-channel="jsonOrders" output-channel="amqpOut"/><int:channel id="amqpOut"/><amqp:outbound-channel-adapter channel="amqpOut" amqp-template="rabbitTemplate" routing-key="orders"/>
这里首先定义了消息通道的入口"orderChannel",也就是controller中消息所发向的目标;之后消息再流向第二段通道"jsonOrders",从标签元素的名字不难看出这一过程将Order对象转换成了json格式。之所以进行消息格式转换,是为了使消息生产者和消息消费者之间达到松耦合,从而使得双方在处理消息时均不需要担心消息格式是否有效。转换为json的消息继而流向第三段通道"amqpOut",这可以看做是通道的终点,只是还需一个adapter适配一下,使Integration消息与rabbit模板相匹配。
订单消息经过以上的流程之后就被放入了rabbit消息队列"orders"中,供wgrus-inventory消费(consume)。Rabbit队列定义:

<rabbit:queue name="orders"/><rabbit:admin connection-factory="rabbitConnectionFactory"/><rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory"/>
再来看看消息的消费者wgrus-inventory在干什么。

二、消息消费者wgrus-inventory

inventory部分也只有1个controller,将从消息队列中获得的订单信息罗列出来:

/** * Handles order requests. */@Controller@RequestMapping(value="/")public class OrdersView {@Autowiredprivate OrderQueue orderQueue;@RequestMapping(method=RequestMethod.GET)public String display(Model model) {model.addAttribute("count", orderQueue.count());model.addAttribute("orders", orderQueue.list());return "orders";}}
其中OrderQueue类型的属性是一个队列,存储最近的至多25条订单信息。那么inventory是如何获得订单信息的呢?当然也是从Spring Integration的通道了。应用程序上下文 wgrus-inventory / src / main / resources / root-context.xml 中的通道配置如下:

<amqp:inbound-channel-adapter channel="orderChannel"connection-factory="rabbitConnectionFactory"queue-names="orders"advice-chain="retryInterceptor" /><bean id="retryInterceptor" class="org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean" /><int:chain input-channel="orderChannel" output-channel="warehouseChannel"><int:claim-check-out/><int:json-to-object-transformer type="org.wgrus.Order"/></int:chain><int:publish-subscribe-channel id="warehouseChannel"/><int:chain input-channel="warehouseChannel"><int:object-to-string-transformer/><int:service-activator ref="orderQueue" method="add"/></int:chain><int:logging-channel-adapter id="logger" channel="warehouseChannel" level="WARN"/>
读取消息时也需要适配器,这里的amqp:inbound-channel-adapter元素的属性值应与wgrus-store部分对应。消息消费者先将消息从"orderChannel"导向"warehouseChannel",在这条channel链中将json消息转换回Order类对象。"warehouseChannel"是一条发布/订阅通道,消息从这里流过时,订阅了"warehouseChannel"的订阅者通道会依次收到通知。这里有两个订阅者,第一个订阅者是一条chain,它先经由转换器把Order对象消息转换成字符串表示(第12行),然后激活orderQueue的add方法把此订单添加到订单队列中(第13行);第二个订阅者是日志通道适配器"logger"。这就是inventory收到消息的处理过程。发布订阅通道适用于一对多通信的场景,消息进入一条发布订阅通道时,所有订阅了此通道的其他通道都会收到该消息。

三、Cloud Foundry运行时环境检测

这两个程序是基于Spring Integration实现的消息传递,另外各自都包含用于检测程序运行环境的监听器,src / main / java / org / wgrus / web /EnvironmentContextListener.java:

public class EnvironmentContextListener implements ServletContextListener {private static Logger logger = LoggerFactory.getLogger(EnvironmentContextListener.class);public void contextInitialized(ServletContextEvent sce) {if (System.getenv("VMC_APP_VERSION") != null|| System.getenv("VCAP_APP_VERSION") != null) {System.setProperty("spring.profiles.active", "cloud");// Extract the base domain from this cloud platform (e.g. cloudfoundry.com)String services = System.getenv("VCAP_APPLICATION");if (services == null) {services = System.getenv("VMC_APP_INSTANCE");}if (services != null) {System.setProperty("BASE_DOMAIN", services.replaceAll(".*wgrus-inventory\\.([a-zA-Z0-9.]*)\\\".*", "$1"));}logger.info("Cloud profile activated with base domain: "+System.getProperty("BASE_DOMAIN"));} else {System.setProperty("spring.profiles.active", "default");logger.info("Default profile set");}}public void contextDestroyed(ServletContextEvent sce) {}}
这里读取系统环境变量,判断程序运行的环境是本地default还是Cloud Foundry的云环境。当然,为使程序能够在Cloud Foundry上运行,需要在pom.xml和应用程序上下文中添加必要的Cloud Foundry运行时依赖,这是每个Cloud Foundry应用都相同且必不可少的基本配置,想必之前几个样例程序的演示已经让让大家再熟悉不过了。

另外,虽然程序构建时引用了MySQL库,但程序上下文中并未注入MySQL数据源的依赖Beans,用来初始化数据库的 wgrus-store / src / main / resources / setup.sql 脚本也没运行过,即使是MongoDB也仅仅是注入了Bean而并未进行数据持久化。可能是由于程序代码更新过之后这些旧的资源和配置没有一并删除,研究代码时大可无视之

原创粉丝点击