最近有幸,公司讓我研究了spring-integration,對於這個spring出品的功能強大的工具,功能繁多且複雜。寫此博客分享一下心得,也爲記錄一下最近研究這麼久的知識點。理解的不夠深,如果有錯誤的地方,希望各位朋友能批評指出。
一、what
首先,什麼是spring-integration?研究之初,對這根管道有些迷惑,這是隊列?這個activeMQ有啥區別?待研究了一段時間之後,才發現,spring-integration越來越像曾經做過的esb組件。那麼spring-integration到底是什麼呢?
官網給出的解釋是,spring-integration是一個功能強大的EIP(Enterprise Integration Patterns),即企業集成模式。對,spring-integration是一個集大成者。就我自己的理解,集成了衆多功能的它,是一種便捷的事件驅動消息框架用來在系統之間做消息傳遞的。
二、why
那麼,我們爲什麼用它呢?spring-integration的官網上,給出了以下說法
spring-integration的目標
- 提供一個簡單的模型來實現複雜的企業集成解決方案
- 爲基於spring的應用添加異步的、消息驅動的行爲
- 讓更多的Spring用戶來使用他
看這種解釋,我的直觀感覺是:啥玩意?不懂啊!接着看到spring-integration的原則
- 組件之間應該是鬆散的,模塊性的易測的
- 應用框架應該強迫分離業務邏輯和集成邏輯
- 擴展節點應該有更好的抽象和可以再使用的能力
感覺,這個應該說的是解耦吧。另外看了下其他人的理解,如果你的系統處在各個系統的中間,需要JMS交互,又需要Database/Redis/MongoDB,還需要監聽Tcp/UDP等,還有固定的文件轉移,分析。還面對着時不時的更改需求的風險。那麼,它再適合不過了。
三、how
那麼,重點來了,如何使用呢?在介紹之前,先簡單的介紹幾個名詞。
1.Message
Message是它的基礎構件和核心,所有的流程都圍繞着Message運轉,如圖所示
Message,就是所說的消息體,用來承載傳輸的信息用的。Message分爲兩部分,header和payload。header是頭部信息,用來存儲傳輸的一些特性屬性參數。payload是用來裝載數據的,他可以攜帶的任何Object對象,放什麼都行,隨你 。
2.MessageChannel
消息管道,生產者生產一個消息到channel,消費者從channel消費一個消息,所以channel可以對消息組件解耦,並且提供一個方便的攔截功能和監控功能。
對於MessageChannel,有以下幾種
(1).PublishSubscribeChannel
發佈訂閱式通道形式,多用於消息廣播形式,發送給所有已經訂閱了的用戶。在3.x版本之前,訂閱者如果是0,啓動會報錯或者發送的時候報錯。在4.x版本後,訂閱者是0,則仍然會返回true。當然,可以配置最小訂閱者數量(min-subscribers)
(2).QueueChannel
隊列模式通道,最常用的形式。與發佈訂閱通道不同,此通道實現點對點式的傳輸方式,管道內部是隊列方式,可以設置管道的容量,如果內部的消息已經達到了最大容量,則會阻塞住,直到隊列有時間,或者發送的消息被超時處理。
(3).PriorityChannel
優先級隊列通道,我的理解爲QueueChannel的升級版,可以無視排隊,根據設置的優先級直接插隊。(壕無人性)
(4).RendezvousChannel
前方施工,禁止通行!這個是一個強行阻塞的通道,當消息進入通道後,通道禁止通行,直到消息在對方通道receive()後,才能繼續使用。
(5).DirectChannel
最簡單的點對點通道方式,一個簡單的單線程通道。是spring-integration的默認通道類型
(6).ExecutorChannel
多線程通道模式,開啓多線程執行點對點通道形式。這個通道博主還未研究,不敢多說........
3.Message Endpoint
消息的終點,或者我稱他爲消息節點,在channel你不能操作消息,只能在endpoint操作。對於常用的消息節點,有以下幾種
(1).Transformer
<!-- Filter過濾器 -->
<int:channel id="filterAChannel"/>
<int:filter input-channel="filterAChannel" output-channel="filterBChannel" expression="payload.name.equals('haha')"/>
<int:channel id="filterBChannel"/>
<int:service-activator input-channel="filterBChannel" expression="@receiveServiceImpl.helloMoreParam(payload.name,payload.age)"/>
(2).Filter
<!-- transformer轉換器 -->
<int:channel id="transformerInChannel"/>
<int:transformer input-channel="transformerInChannel" output-channel="transformerOutChannel"
expression="payload.name.toUpperCase() + '- [' + T(java.lang.System).currentTimeMillis() + ']'"/>
<int:channel id="transformerOutChannel">
<int:queue/>
</int:channel>
<int:outbound-channel-adapter channel="transformerOutChannel" ref="receiveServiceImpl" method="helloTransformer">
<int:poller fixed-delay="0"/>
</int:outbound-channel-adapter>
(3).Router
路由器,用來管理一個消息應該被髮送到哪個channel中。相當於JAVA裏面的switch case語句吧。判斷條件很多,可是使用header裏面的參數具體值(比如header裏面有個定義爲testRouter的參數,數值爲A,那麼消息經過路由會發送到判斷爲A的通道內,後面使用中再詳細講解)
(4).Service Activator
我稱他爲服務激活器,是一個連接服務實例到消息系統的通用端點。對於服務激活器,可能是因爲我理解的不夠全面,我總是將他和通道適配器搞混,因爲我自己測試發現,激活器和適配器都可以作爲一個消息出通道的節點。
(5).Channel Adapter
通道適配器是將消息通道連接到某個其他系統或傳輸的端點。通道適配器可以是入站或出站。通常情況下,通道適配器將在消息與從其他系統(文件,HTTP請求,JMS消息等)接收或發送的任何對象或資源之間進行映射。
(6).Channel Bridge
通道橋樑,用來作爲管道之間進行通信使用的,常用情景爲:在一個輸入管道,將管道的內容發送到另外N個管道輸出,配置方式如下
<!-- bridge -->
<int:channel id="bridgeSendChannel"/>
<int:bridge input-channel="bridgeSendChannel" output-channel="bridgeReceiveAChannel"/>
<int:channel id="bridgeReceiveAChannel"/>
<int:bridge input-channel="bridgeReceiveAChannel" output-channel="bridgeReceiveBChannel"/>
<int:channel id="bridgeReceiveBChannel">
<int:queue/>
</int:channel>
<int:outbound-channel-adapter channel="bridgeReceiveBChannel"
expression="@receiveServiceImpl.helloBridge(payload.name,payload.age)">
<int:poller fixed-delay="0"/>
</int:outbound-channel-adapter>
另外還有Splitter(分解器),Aggregator(聚合器)等。對於其他的消息節點,博主還沒有做過多研究,就不再次誤人子弟了。後續會將未研究到的一一補上。
4.Channel Interceptor
分別是發送前,郵寄後,發送成功後,接收前,接收後,接受成功後。可以分別在不同的節點進行操作。
四、use
下面使用到的Test類爲
import lombok.Data;
/**
* 普通測試dto
* @author lin
*/
@Data
public class Test {
private String name;
private String age;
}
(1)普通方式
xml配置,這裏配置了一個通道helloWorldChannel,配置了個接收激活點,即接收方的地址爲helloServiceImpl裏面的hello方法。(其中ref指對應接收的類名,method指類裏面接收的方法)
<!-- 測試dto模式傳輸 -->
<int:channel id="testChannel"/>
<int:service-activator input-channel="testChannel" ref="receiveServiceImpl" method="hello"/>
發送方Service裏面
/**
* 測試傳輸dto
*/
@Override
public void testDto() {
System.out.println("testDto方法");
Test test = new Test();
test.setName("testDto");
test.setAge("18");
testChannel.send(MessageBuilder.withPayload(test).build());
}
接收方Service裏面
@Override
public void hello(Test test) {
System.out.println(test.getName() + " " + test.getAge());
}
(2)普通多參數方式
xml配置,這裏通過獲取payload裏面的具體參數來傳參的形式
<!-- 測試多參數傳遞 -->
<int:channel id="moreParamChannel"/>
<int:service-activator input-channel="moreParamChannel"
expression="@receiveServiceImpl.helloMoreParam(payload.name,payload.age)"/>
發送方Service裏面,將所有的參數通過Map形式裝載到payload裏面
/**
* 測試多參數傳輸
*/
@Override
public void moreParamm() {
System.out.println("greetMoreParam方法");
HashMap<String, String> map = new HashMap();
map.put("name", "moreParam");
map.put("age", "18");
helloWorldMoreParamChannel.send(MessageBuilder.withPayload(map).build());
}
接收方Service裏面
@Override
public void helloMoreParam(String name, String age) {
System.out.println(name + " " + age);
}
(3)JMS方式
xml配置,這裏配置了個MQ,將消息放入mq中進行傳遞
<!-- 測試Mq配置-->
<int:channel id="topicChannel"/>
<bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>tcp://127.0.0.1:61616?trace=true&keepAlive=true</value>
</property>
<property name="useAsyncSend" value="true"/>
</bean>
<int-jms:outbound-channel-adapter channel="topicChannel" destination-name="topic.myTopic" pub-sub-domain="true"/>
<int:channel id="listenerChannel"/>
<int-jms:message-driven-channel-adapter id="messageDrivenAdapter" channel="listenerChannel"
destination-name="topic.myTopic" pub-sub-domain="true"/>
<int:service-activator input-channel="listenerChannel" ref="messageListenerImpl" method="processMessage"/>
發送方Service裏面
/**
* 使用mq進行傳輸發送方法
*/
@Override
public void send() {
HashMap<String,Object> map = new HashMap<>();
map.put("name","MqService");
map.put("age","18");
topicChannel.send(MessageBuilder.withPayload(map).build());
}
接收方Service裏面
public void processMessage(HashMap<String,Object> map) {
System.out.println("MessageListener::::::Received message: " + map.toString());
}
(4)訂閱方式
xml配置,這裏配置了兩個訂閱者,訂閱者分別是兩個方法
<!-- 測試訂閱發佈 -->
<!--min-subscribers=""參數爲預期最小訂閱者,如果必須有訂閱者,則這裏填寫最少數;默認值爲0-->
<int:publish-subscribe-channel id="pubsubChannel"/>
<int:outbound-channel-adapter channel="pubsubChannel" ref="receiveServiceImpl" method="helloReceiveOne">
</int:outbound-channel-adapter>
<int:outbound-channel-adapter channel="pubsubChannel" ref="receiveServiceImpl" method="helloReceiveTwo">
</int:outbound-channel-adapter>
發送方Service裏面
@Override
public void pubsubSend() {
Test test = new Test();
test.setName("pubsubSend");
test.setAge("18");
publishSubscribeChannel.send(MessageBuilder.withPayload(test).build());
}
接收方Service裏面
@Override
public void helloReceiveOne(Test test){
System.out.println("One:"+test.getName()+" "+test.getAge());
}
@Override
public void helloReceiveTwo(Test test){
System.out.println("Two:"+test.getName()+" "+test.getAge());
}
(5)router方式
xml配置,這裏配置了一個入口通道,當消息進入入口後,通過判斷header裏面的'tsetHeader'參數的值,如果值爲A,則進入routerAChannel通道,如果爲B則進入routerBChannel通道。進入通道後分別進入兩者的接收方法中。其中兩種方法用了傳遞類,和多參數傳遞的形式。
<!-- 測試路由 -->
<!-- 路由入口 -->
<int:channel id="routingChannel">
<int:queue/>
</int:channel>
<!-- 路由器 -->
<int:header-value-router input-channel="routingChannel" header-name="testHeader">
<int:poller fixed-delay="0"/>
<int:mapping value="A" channel="routerAChannel"/>
<int:mapping value="B" channel="routerBChannel"/>
</int:header-value-router>
<!-- 路由出口 -->
<int:channel id="routerAChannel">
<int:queue/>
</int:channel>
<int:outbound-channel-adapter channel="routerAChannel" ref="receiveServiceImpl" method="helloRouterTest">
<int:poller fixed-delay="0"/>
</int:outbound-channel-adapter>
<int:channel id="routerBChannel">
<int:queue/>
</int:channel>
<int:outbound-channel-adapter channel="routerBChannel"
expression="@receiveServiceImpl.helloRouterMap(payload.name,payload.age)">
<int:poller fixed-delay="0"/>
</int:outbound-channel-adapter>
發送方Service裏面
@Override
public void routerA(String name, String age) {
Test test = new Test();
test.setAge(age);
test.setName(name);
routingChannel.send(MessageBuilder.withPayload(test).setHeader("testHeader", "A").build());
}
@Override
public void routerB(String name, String age) {
HashMap<String,String> map = new HashMap<>();
map.put("name", name);
map.put("age", age);
routingChannel.send(MessageBuilder.withPayload(map).setHeader("testHeader", "B").build());
}
接收方Service裏面
@Override
public void helloRouterTest(Test test){
System.out.println("routerA方法");
System.out.println("helloRouterTest:"+test.getName()+" "+test.getAge());
}
@Override
public void helloRouterMap(String name,String age){
System.out.println("routerB方法");
System.out.println("helloRouterMap:"+name+" "+age);
}
(6)網關方式
xml配置,在這裏面配置了一個接口類,當調用這個接口的方法時,就會進入網關配置的通道
<!-- 網關通道口模式,dto -->
<int:channel id="getWayChannel">
<int:queue/>
</int:channel>
<int:gateway service-interface="com.lin.integration.service.interfaces.UseGetWaySender" id="helloGetWaySender"
default-request-channel="getWayChannel"/>
<int:outbound-channel-adapter channel="getWayChannel" ref="receiveServiceImpl" method="hello">
<int:poller fixed-delay="0"></int:poller>
</int:outbound-channel-adapter>
<!-- 網關通道口模式,多參數傳遞 -->
<int:channel id="getWayMoreParamChannel">
<int:queue/>
</int:channel>
<int:gateway service-interface="com.lin.integration.service.interfaces.MoreParamSender" id="getWayMoreParamSender"
default-request-channel="getWayMoreParamChannel"/>
<int:outbound-channel-adapter channel="getWayMoreParamChannel"
expression="@receiveServiceImpl.helloMoreParam(payload.name,payload.age)">
<int:poller fixed-delay="0"></int:poller>
</int:outbound-channel-adapter>
網關interface裏面
public interface UseGetWaySender {
void sendMessage(Test test);
}
public interface MoreParamSender {
void sendMessage(Map map);
}
發送方Service裏面
/**
* 測試網關dto
*/
@Override
public void getWay() {
Test test = new Test();
test.setAge("18");
test.setName("getWay");
useGetWaySender.sendMessage(test);
}
/**
* 測試網關多參數
*/
@Override
public void getWayMoreParam() {
HashMap<String, String> map = new HashMap();
map.put("name", "getWayMoreParam");
map.put("age", "18");
moreParamSender.sendMessage(map);
}
(7)全局攔截器
攔截器中,將需要攔截的管道進行攔截,攔截之後就會對這個管道的發送端,接收端進行攔截,攔截的接口在上文已經提到過,攔截的配置如下
<!-- 全局攔截器 -->
<int:channel-interceptor pattern="testInterceptorChannel" order="3" ref="countingChannelInterceptor">
</int:channel-interceptor>
<int:channel id="testInterceptorChannel"/>
<int:service-activator input-channel="testInterceptorChannel" ref="receiveServiceImpl" method="hello"/>
對於近期的spring-integration研究,這些只是“初探”,如此好的一個框架模式,我也將在今後進行深入研究,會將文章進行補充,希望各位對於我文章裏面的不足與錯誤的地方進行批評指出,從而能互相交流研究,多謝。
參考文獻:
https://docs.spring.io/spring-integration/docs/5.0.4.RELEASE/reference/html/
https://www.aliyun.com/jiaocheng/301276.html
https://blog.csdn.net/xiayutai1/article/details/53302652?locationNum=4&fps=1
http://www.iteye.com/topic/744524
https://blog.csdn.net/slivefox/article/details/3740541
https://my.oschina.net/zhzhenqin/blog/86586
http://www.importnew.com/16538.html
demo碼雲地址:
https://gitee.com/doubletreelin/spring-integration-demo