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

在实际项目开发中经常会有应用集成的需求,将几个分离的应用程序整合到一起,相互之间进行通信(传递消息或协同工作)或数据共享。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.xsd
		http://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 {

	@Autowired
	private 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而并未进行数据持久化。可能是由于程序代码更新过之后这些旧的资源和配置没有一并删除,研究代码时大可无视之

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值