spring-integration初探

    最近有幸,公司讓我研究了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 

    解釋者,轉換者,翻譯者,怎麼理解都可以。作用是可以將消息轉爲你想要的類型。可以將xml形式轉換成string類型。
<!-- 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

    過濾器,顧名思義,過濾用的,用來判斷一個消息是否應該被傳輸。用我的理解看,他就是spring-integration裏面的if語句。
<!-- 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

    管道攔截器,能夠以非常優雅,非常溫柔的方式捕獲管道傳遞之間的節點。對於攔截器,spring-integration給了我們六種節點

分別是發送前,郵寄後,發送成功後,接收前,接收後,接受成功後。可以分別在不同的節點進行操作。


四、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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章