Spring Integration

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

The Cafe Sample(小卖部订餐例子)

    小卖部有一个订饮料服务,客户可以通过订单来订购所需要饮料。小卖部提供两种咖啡饮料
        LATTE(拿铁咖啡)和MOCHA(摩卡咖啡)。每种又都分冷饮和热饮
    整个流程如下:
        1.有一个下订单模块,用户可以按要求下一个或多个订单。
        2.有一个订单处理模块,处理订单中那些是关于订购饮料的。
        3.有一个饮料订购处理模块,处理拆分订购的具体是那些种类的饮料,把具体需要生产的饮料要求发给生产模块
        4.有一个生产模块,进行生产。
        5.等生成完成后,有一个订单确认模块(Waiter),把订单的生成的饮料输出。
Spring Integration_第1张图片
        
    这个例子利用Spring Integration实现了灵活的,可配置化的模式集成了上述这些服务模块。

Spring Integration提供两种模式的工作方式(Annotation和XML)
先来看一下XML方式,进行示例的开发:
配置文件如下:
xml version="1.0" encoding="UTF-8" ?>
< beans:beans  xmlns ="http://www.springframework.org/schema/integration"
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans
="http://www.springframework.org/schema/beans"
    xmlns:stream
="http://www.springframework.org/schema/integration/stream"
    xsi:schemaLocation
="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/integration
            http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
            http://www.springframework.org/schema/integration/stream
            http://www.springframework.org/schema/integration/stream/spring-integration-stream-1.0.xsd"
>

        

    
< gateway  id ="cafe"  service-interface ="org.springframework.integration.samples.cafe.Cafe" />

        

    
< channel  id ="orders" />
    

    
< splitter  input-channel ="orders"  ref ="orderSplitter"  method ="split"  output-channel ="drinks" />

        

    
< channel  id ="drinks" />
    

    
< router  input-channel ="drinks"  ref ="drinkRouter"  method ="resolveOrderItemChannel" />
        
         

    
< channel  id ="coldDrinks" >
        
< queue  capacity ="10" />
    
channel >
    

    
< service-activator  input-channel ="coldDrinks"  ref ="barista"
                       method
="prepareColdDrink"  output-channel ="preparedDrinks" />

         

    
< channel  id ="hotDrinks" >
        
< queue  capacity ="10" />
    
channel >
    

    
< service-activator  input-channel ="hotDrinks"  ref ="barista"
                       method
="prepareHotDrink"  output-channel ="preparedDrinks" />
        

    
< channel  id ="preparedDrinks" />
    

    
< aggregator  input-channel ="preparedDrinks"  ref ="waiter"
                method
="prepareDelivery"  output-channel ="deliveries" />

        

    
< stream:stdout-channel-adapter  id ="deliveries" />

    
< beans:bean  id ="orderSplitter"
                class
="org.springframework.integration.samples.cafe.xml.OrderSplitter" />

    
< beans:bean  id ="drinkRouter"
                class
="org.springframework.integration.samples.cafe.xml.DrinkRouter" />

    
< beans:bean  id ="barista"  class ="org.springframework.integration.samples.cafe.xml.Barista" />

    
< beans:bean  id ="waiter"  class ="org.springframework.integration.samples.cafe.xml.Waiter" />

beans:beans >

我们来看一下整体服务是怎么启动的
    
    首先我们来看一下CafeDemo这个类,它触发下定单操作
org.springframework.integration.samples.cafe.xml.CafeDemo
 1  public   class  CafeDemo {
 2 
 3       public   static   void  main(String[] args) {
 4           //// 加载Spring 配置文件 "cafeDemo.xml"
 5          AbstractApplicationContext context  =   null ;
 6           if (args.length  >   0 ) {
 7              context  =   new  FileSystemXmlApplicationContext(args);
 8          }
 9           else  {
10              context  =   new  ClassPathXmlApplicationContext( " cafeDemo.xml " , CafeDemo. class );
11          }
12           // 取得 Cafe实列
13          Cafe cafe  =  (Cafe) context.getBean( " cafe " );
14           // 准备 发送100条消息(订单)
15           for  ( int  i  =   1 ; i  <=   100 ; i ++ ) {
16              Order order  =   new  Order(i);
17               //  一杯热饮  参数说明1.饮料类型 2.数量 3.是否是冷饮(true表示冷饮)
18              order.addItem(DrinkType.LATTE,  2 false );
19               //  一杯冷饮  参数说明1.饮料类型 2.数量 3.是否是冷饮(true表示冷饮)
20              order.addItem(DrinkType.MOCHA,  3 true );
21               // 下发订单,把消息发给 orders 队列
22              cafe.placeOrder(order);
23          }
24      }
25 
26  }

下面是Cafe接口的源代码
public   interface  Cafe {

    
// 定义GateWay, 把消息发送到 orders 队列, Message的payLoad属性,保存 order参数值
    @Gateway(requestChannel = " orders " )
    
void  placeOrder(Order order);

}

OrderSplitter 源代码
 1  public   class  OrderSplitter {
 2 
 3       // 接收 从 orders队列接收的 order 消息后,调用 order.getItems方法
 4       // 进行订单的分解, 返回的List可会,被拆分为多个消息后(Message.payLoad),发到指定队列
 5       public  List < OrderItem >  split(Order order) {
 6           return  order.getItems();
 7      }
 8 
 9  }
10 

OrderSplitter.split把消息拆分后,变成多个消息,发送到 drinks队列.由drinkRouter进行消息的接收。
1  public   class  DrinkRouter {
2 
3       // 从 drinks队列的消息后,根据orderItem的属性,选择路由到不同的队列 coldDrinks或hotDrinks
4       public  String resolveOrderItemChannel(OrderItem orderItem) {
5           return  (orderItem.isIced())  ?   " coldDrinks "  :  " hotDrinks " ;
6      }
7 
8  }

下面看一下,如果是一杯冷饮,则消息发送到 coldDrinks队列
接收根据配置,由barista Bean的prepareColdDrink方法接收消息后,进行处理
如果是一杯热饮,则消息发送到 hotDrinks队列
接收根据配置,由barista Bean的prepareHotDrink方法接收消息后,进行处理
 1  public   class  Barista {
 2 
 3       private   long  hotDrinkDelay  =   5000 ;
 4 
 5       private   long  coldDrinkDelay  =   1000 ;
 6 
 7       private  AtomicInteger hotDrinkCounter  =   new  AtomicInteger();
 8 
 9       private  AtomicInteger coldDrinkCounter  =   new  AtomicInteger();
10 
11 
12       public   void  setHotDrinkDelay( long  hotDrinkDelay) {
13           this .hotDrinkDelay  =  hotDrinkDelay;
14      }
15 
16       public   void  setColdDrinkDelay( long  coldDrinkDelay) {
17           this .coldDrinkDelay  =  coldDrinkDelay;
18      }
19 
20       // 处理热饮订单,并生成Drink冷料
21       public  Drink prepareHotDrink(OrderItem orderItem) {
22           try  {
23              Thread.sleep( this .hotDrinkDelay);
24              System.out.println(Thread.currentThread().getName()
25                       +   "  prepared hot drink # "   +  hotDrinkCounter.incrementAndGet()  +   "  for order # "
26                       +  orderItem.getOrder().getNumber()  +   " "   +  orderItem);
27               return   new  Drink(orderItem.getOrder().getNumber(), orderItem.getDrinkType(), orderItem.isIced(),
28                      orderItem.getShots());
29          }  catch  (InterruptedException e) {
30              Thread.currentThread().interrupt();
31               return   null ;
32          }
33      }
34 
35       // 处理冷饮订单,并生成Drink冷料
36       public  Drink prepareColdDrink(OrderItem orderItem) {
37           try  {
38              Thread.sleep( this .coldDrinkDelay);
39              System.out.println(Thread.currentThread().getName()
40                       +   "  prepared cold drink # "   +  coldDrinkCounter.incrementAndGet()  +   "  for order # "
41                       +  orderItem.getOrder().getNumber()  +   " "   +  orderItem);
42               return   new  Drink(orderItem.getOrder().getNumber(), orderItem.getDrinkType(), orderItem.isIced(),
43                      orderItem.getShots());
44          }  catch  (InterruptedException e) {
45              Thread.currentThread().interrupt();
46               return   null ;
47          }
48      }
49 
50  }

接下来,已经把订单需要生产的饮料已经完成,现在可以交给服务员(waier)交给客人了。
这里使用的aggregate模式,让服务器等待这个订单的所有饮料生产完后的,交给客户.

下面来介绍该应用

< aggregator  input-channel ="preparedDrinks"  ref ="waiter"
                method
="prepareDelivery"  output-channel ="deliveries" />

最后,完成订单的消息会发到 waiter队列
 1  public   class  Waiter {
 2 
 3       public  Delivery prepareDelivery(List < Drink >  drinks) {
 4           return   new  Delivery(drinks);
 5      }
 6 
 7 
 8  }
 9 
10  public   class  Delivery {
11 
12       private   static   final  String SEPARATOR  =   " ----------------------- " ;
13 
14 
15       private  List < Drink >  deliveredDrinks;
16 
17       private   int  orderNumber;
18 
19 
20       public  Delivery(List < Drink >  deliveredDrinks) {
21           assert (deliveredDrinks.size()  >   0 );
22           this .deliveredDrinks  =  deliveredDrinks;
23           this .orderNumber  =  deliveredDrinks.get( 0 ).getOrderNumber();
24      }
25 
26 
27       public   int  getOrderNumber() {
28           return  orderNumber;
29      }
30 
31       public  List < Drink >  getDeliveredDrinks() {
32           return  deliveredDrinks;
33      }
34 
35      @Override
36       public  String toString() {
37          StringBuffer buffer  =   new  StringBuffer(SEPARATOR  +   " \n " );
38          buffer.append( " Order # "   +  getOrderNumber()  +   " \n " );
39           for  (Drink drink : getDeliveredDrinks()) {
40              buffer.append(drink);
41              buffer.append( " \n " );
42          }
43          buffer.append(SEPARATOR  +   " \n " );
44           return  buffer.toString();
45      }
46 
47  }

最后我们使用一个 stream channel adaptor把订单生产完成的饮料输出。
        
    
< stream:stdout-channel-adapter  id ="deliveries" />


这样整个流程就执行完了,最终我们的饮料产品就按照订单生产出来了。累了吧,喝咖啡提神着呢!!!

spring-integration官网: http://www.springsource.org/spring-integration

关于 Annotation的介绍,将在 下篇 介绍。

附:xml配置介绍
Service Activator 配置
1 
2  < service-activator  input-channel ="exampleChannel"  ref ="exampleHandler" />
3 
4  < service-activator  input-channel ="exampleChannel"  ref ="somePojo"  method ="someMethod" />
5  < service-activator  input-channel ="exampleChannel"  output-channel ="replyChannel"
6                     ref ="somePojo"  method ="someMethod" />


 触发指定的方法,接收消息队列配置(触发轮循访问的方式)
 1  < inbound-channel-adapter  ref ="source1"  method ="method1"  channel ="channel1" >
 2       < poller >
 3           < interval-trigger  interval ="5000" />
 4       poller >
 5  inbound-channel-adapter >
 6 
 7  < inbound-channel-adapter  ref ="source2"  method ="method2"  channel ="channel2" >
 8       < poller >
 9           < cron-trigger  expression ="30 * * * * MON-FRI" />
10       poller >
11  channel-adapter >

 
 触发指定的方法,发送消息
1    < outbound-channel-adapter  channel ="channel1"  ref ="target1"  method ="method1" />
2   
3    < outbound-channel-adapter  channel ="channel2"  ref ="target2"  method ="method2" >
4       < poller >
5           < interval-trigger  interval ="3000" />
6       poller >
7  outbound-channel-adapter >

Router
消息路由方式
1  < bean  id ="payloadTypeRouter"  class ="org.springframework.integration.router.PayloadTypeRouter" >
2       < property  name ="payloadTypeChannelMap" >
3           < map >
4               < entry  key ="java.lang.String"  value-ref ="stringChannel" />
5               < entry  key ="java.lang.Integer"  value-ref ="integerChannel" />
6           map >
7       property >
8  bean >

Aggregator 消息合并
 1  < channel  id ="inputChannel" />
 2 
 3  < aggregator  id ="completelyDefinedAggregator"  1
 4      input-channel ="inputChannel"  2
 5      output-channel ="outputChannel"   3
 6      discard-channel ="discardChannel"   4
 7      ref ="aggregatorBean"  5
 8      method ="add"  6
 9      completion-strategy ="completionStrategyBean"   7
10      completion-strategy-method ="checkCompleteness"  8
11      timeout ="42"  9
12      send-partial-result-on-timeout ="true"  10
13      reaper-interval ="135"  11
14      tracked-correlation-id-capacity ="99"  12
15      send-timeout ="86420000"  13  />  
16 
17  < channel  id ="outputChannel" />
18 
19  < bean  id ="aggregatorBean"  class ="sample.PojoAggregator" />
20 
21  < bean  id ="completionStrategyBean"  class ="sample.PojoCompletionStrategy" />

1

The id of the aggregator is optional.

2

The input channel of the aggregator. Required.

3

The channel where the aggregator will send the aggregation results. Optional (because incoming messages can specify a reply channel themselves).

4

The channel where the aggregator will send the messages that timed out (if send-partial-results-on-timeout is false)Optional.

5

A reference to a bean defined in the application context. The bean must implement the aggregation logic as described above. Required.

6

A method defined on the bean referenced by ref, that implements the message aggregation algorithm. Optional, with restrictions (see above).

7

A reference to a bean that implements the decision algorithm as to whether a given message group is complete. The bean can be an implementation of the CompletionStrategy interface or a POJO. In the latter case the completion-strategy-method attribute must be defined as well. Optional (by default, the aggregator .

8

A method defined on the bean referenced by completion-strategy, that implements the completion decision algorithm. Optional, with restrictions (requires completion-strategy to be present).

9

The timeout for aggregating messages (counted from the arrival of the first message). Optional.

10

Whether upon the expiration of the timeout, the aggregator shall try to aggregate the already arrived messages. Optional (false by default).

11

The interval (in milliseconds) at which a reaper task is executed, checking if there are any timed out groups. Optional.

12

The capacity of the correlation id tracker. Remembers the already processed correlation ids, preventing the formation of new groups for messages that arrive after their group has been already processed (aggregated or discarded). Optional.

13

The timeout for sending out messages. Optional.


配置消息合并策略
 1  public   class  PojoCompletionStrategy {
 2 
 3     public   boolean  checkCompleteness(List < Long >  numbers) {
 4       int  sum  =   0 ;
 5       for  ( long  number: numbers) {
 6        sum  +=  number;
 7      }
 8       return  sum  >=  maxValue;
 9    }
10  }


Good Luck!
Yours Matthew!

转载于:https://my.oschina.net/pvpCC9IFwqz4/blog/421955

你可能感兴趣的:(测试,java,python)