Spring Integration

925 阅读4分钟

Spring Integration 项目是Sring对Enterprise Patterns(企业集成模式)内容的实现,这些实现扩展了原有的Spring消息编程模型。

EIP内部提出了很多概念,比如消息的过滤、聚合、点对点发送、发布/订阅模式、消息转换、TCP/UDP、JMS、E-mail等内容。spring-messaging模块仅仅是对Spring Integration项目消息部分关键内容进行抽象和简单实现,Spring Integration项目是真正复杂的实现。

Spring Integration 流程图

核心组件概述

  1. MessageDipatcher:消息分发器。消息分发给MessageHandler的策略接口。

    • BroadcastingDipatcher(广播模式):每次消费分发给所有的MessageHandler
    • UnicastingDispatcher(单播模式):每次消费只能让一个MessageHandler处理,单播模式分发给哪个MessageHandler,涉及负载均衡策略LoadBalancingStrategy
  2. LoadBalancingStrategy:默认只有轮询实现RoundRobinLoadBalancingStrategy

  3. Transformer:消息转换器。可以吧Message A转换成Message B,在转换过程中,比如可以过滤消息内HEADERPayload信息,仅仅保留重要部分。

  4. MessageSelector:消息选择器。决定是否选择(accept)消息,与MessageFilter配合进行消息过滤。

  5. MessageRouter:消息路由。获取MessageHandler中的消息,并根据不同的条件发送给不同的MessageHandler

  6. Aggregator:消息集合器。把一组消息根据一些条件聚合成一条消息。

  7. Splitter:消息分割器。把一条消息根据一些条件分割成多个消息。

  8. ChannelAdapter:通道适配器。应用跟MessageChannel之间的桥梁。分为OutboundChannelAdapterInboundChannelAdapter

  9. MessagingGateway:消息网关。以HTTP网关的形式将消息的操作暴露出去,用户通过Rest请求就可与消息系统进行交互。

核心组件使用

  • @Filter注解可以用来过滤消息
@Filter(inputChannel = "input", discardChannel = "discard", outputChannel = "output")
public boolean receiveByFilter(String msg) {
  if (msg.contains("keywords")) {
    return false;
  }
  return true;
}

过滤掉的消息会被发送到名称为discardMessageHandler中,没被过滤的消息则被发送到名称为outputMessageHandler中。

  • @Transformer注解用于消息的转换
    @Transformer(inputChannel = "input", outputChannel = "output")
    public Message receiveByTransformer(Message msg) {
          msg.getHeaders().remove("secret");
          return msg;
    }

会删除HEADER中key为secret的内容

  • @Splitter注解用于消息分割
    @Splitter(inputChannel = "input", outputChannel = "output")
    public String[] receiveByFilter(String msg) {
         return msg.split("-");
    }

会把一个消息内的内容根据“-”分割成多个消息。

  • @MessageGateway定义一个消息网关接口
    @MessagingGateway(name = "testGateway", defaultRequestChannel = "order")
    public interface TestGateway {
        @Gateway(requestChannel = "order", replyTimeout = 2, requestTimeout = 200)
        String echo(OrderMsg orderMsg);
    }

可以注入@Autowired并进行调用,底层通过动态代理的方式进行消息的发送。

  • @Poller用于消息的轮询消费
    @Bean
    @InboundChannelAdapter(value = "order", poller = @Poller(fixedDelay = "10000", maxMessagesPerPoll = "1"))
    public MessageSource<OrderMsg> orderMessageSource() {
        return () -> {
            String randomGoods = new ArrayList<>(goodsCount.keySet()).get(random.nextInt(goodsCount.size()));
            return MessageBuilder.withPayload(new OrderMsg(randomGoods, random.nextInt(5))).build();
        };
    }

ChannelAdapter合作进行消息的消费(每次最多拉取1条消息,间隔10s操作一次)

  • MessageChannel实现

    • DirectChannel:单播模式的消息通道,默认使用轮询均衡策略。
    • ExecutorChannel:基于线程池的单播模式的消息通道,默认使用轮询负载均衡策略。
    • PublishSubscribeChannel:基于线程池的广播模式的消息通道。
    • QueueChannelPollableChannel接口的实现类,基于队列和信号量完成。
    • PriorityChannelPollableChannel接口的实现类,基于优先队列。

spring-integration-amqpRabbitMQ)和spring-integration-kafkaKafka)这些项目是针对不同消息中间件的适配。

示例代码

@SpringBootApplication
@EnableIntegration
public class IntegrationApplication {
    public static void main(String[] args) {
        SpringApplication.run(IntegrationApplication.class, args);
    }

    private static Random random = new Random();
    private static BigDecimal totalMoney = new BigDecimal(0);
    private static Map<String, Integer> goodsCount = new HashMap();
    private static Map<String, BigDecimal> goodsMoney = new HashMap();

    static {
        goodsCount.put("apple", 10);
        goodsCount.put("book", 100);
        goodsCount.put("clothes", 50);

        goodsMoney.put("apple", new BigDecimal(4000));
        goodsMoney.put("book", new BigDecimal(30));
        goodsMoney.put("clothes", new BigDecimal(500));
    }

    @RestController
    class MessagingController {
        @Autowired
        TestGateway testGateway;

        @GetMapping("/order")
        void order() {
            testGateway.echo(new OrderMsg("book", 10));
        }

        @GetMapping("/result")
        String result() {
            return "totalMoney: " + totalMoney + ", goods count info: " + goodsCount;
        }
    }

    @MessagingGateway(name = "testGateway", defaultRequestChannel = "order")
    public interface TestGateway {
        @Gateway(requestChannel = "order", replyTimeout = 2, requestTimeout = 200)
        String echo(OrderMsg orderMsg);
    }

    @Bean
    MessageChannel order() {
        return new DirectChannel();
    }

    @Bean
    MessagingTemplate messagingTemplate() {
        return new MessagingTemplate(order());
    }

    @Bean
    CommandLineRunner runner() {
        return args -> {
            messagingTemplate().send(MessageBuilder.withPayload(
                    new OrderMsg("apple", 5)).build());
            messagingTemplate().send(MessageBuilder.withPayload(
                    new OrderMsg("book", 30)).build());
            messagingTemplate().send(MessageBuilder.withPayload(
                    new OrderMsg("clothes", 1)).build());
        };
    }



    @Bean
    @InboundChannelAdapter(value = "order", poller = @Poller(fixedDelay = "10000", maxMessagesPerPoll = "1"))
    public MessageSource<OrderMsg> orderMessageSource() {
        return () -> {
            String randomGoods = new ArrayList<>(goodsCount.keySet()).get(random.nextInt(goodsCount.size()));
            return MessageBuilder.withPayload(new OrderMsg(randomGoods, random.nextInt(5))).build();
        };
    }

    @ServiceActivator(inputChannel = "order", outputChannel = "errorTopic")
    Message receive(@Payload OrderMsg order) {
        if (goodsCount.containsKey(order.getGoods()) && order.getCount() > 0) {
            int newCount = goodsCount.get(order.getGoods()) - order.getCount();
            if (newCount < 0) {
                return MessageBuilder.withPayload("goods: " + order.getGoods() + " count is not enough").build();
            } else {
                goodsCount.put(order.getGoods(), newCount);
                totalMoney = totalMoney.add(
                        goodsMoney.get(order.getGoods()).multiply(new BigDecimal(order.getCount())));
                return MessageBuilder.withPayload("goods: " + order.getGoods() + " handle successful").build();
            }
        } else {
            return MessageBuilder.withPayload("goods: " + order.getGoods() + " count is not enough or count is invalid")
                    .build();
        }
    }

    @ServiceActivator(inputChannel = "errorTopic")
    void result(String msg) {
        System.out.println(msg);
    }

    static class OrderMsg {

        String goods;
        Integer count;

        public OrderMsg() {
        }

        public OrderMsg(String goods, Integer count) {
            this.goods = goods;
            this.count = count;
        }

        public String getGoods() {
            return goods;
        }

        public void setGoods(String goods) {
            this.goods = goods;
        }

        public Integer getCount() {
            return count;
        }

        public void setCount(Integer count) {
            this.count = count;
        }
    }

}