spring integration学习

3.5 Configuration and @EnableIntegration

在整个文档中,您将看到对在Spring集成流中声明元素的XML命名空间支持的引用。此支持由一系列命名空间分析器提供,这些名称分析器生成适当的bean定义来实现特定组件。例如,许多endpoints由MessageHandler bean和ConsumerEndpointFactoryBean组成,其中输入了处理程序和输入通道名称。

第一次遇到Spring集成命名空间元素时,框架自动声明用于支持运行时环境(任务调度器、隐式通道创建者等)的多个bean。

注意:从版本4开始,引入了@ EnableIntegration 注释,允许注册Spring集成基础结构bean(见JavaDoc)。仅当java开启注释后这个注释才能使用,如spring boot、spring integratio 消息注释的支持和没有XML的spring integration java DSL。

当你有一个没有spring integration组件的parent context和至少2个使用spring integration的child contexts,@EnableIntegration 注释也是有用的。它使这些公共组件只能在父上下文中声明一次。

@EnableIntegration 注解将许多基础设施组件注册到应用程序上下文中:
**注册一些内置bean,例如errorChannel 及其LoggingHandler、taskScheduler for pollers, jsonPath SpEL-function等;
**添加多个BeanFactoryPostProcessor 以增强全局和缺省集成环境的BeanFactory
**添加几个BeanPostProcessor,以增强和/或转换和包装特定bean用于集成目的;
**添加注释处理器,以解析应用程序上下文中的消息注释和寄存器组件。

还引入了@IntegrationComponentScan来允许类路径扫描。这个注释与标准Spring框架@ComponentScan有相似的作用,但它仅限于Spring integration特定组件和注释,而这些标准Spring框架组件扫描机制是不可达到的。例如,第84.6节,“@MessagingGateway Annotation”。
已经引入了@EnablePublisher来注册PublisherAnnotationBeanPostProcessor ,并为没有channel 属性的那些@Publisher注释配置default-publisher-channel。如果发现不止一个@ EnablePuisher注释,则它们必须具有与默认通道相同的值。有关更多信息,请参阅B.1.1节,“Annotation-driven approach via @Publisher annotation” 。

已经引入了@ GlobalChannelInterceptor注解来标记ChannelInterceptor beans用于全局信道拦截。这个注释类似于XML元素(参见称为“Global Channel Interceptor Configuration”的部分)。@GlobalChannelInterceptor 注释可以放置在类级别(带有@Component注释)或带@Configuration类中的@ bean方法。在这两种情况下,bean都必须是ChannelInterceptor。
将@IntegrationConverter注释引入到标记(Converter, GenericConverter or ConverterFactory)转换器、通用转换器或转换器工厂bean中作为integrationConversionService。这个注释类似于 XML元素(参见第八节1.1.6节,“Payload Type Conversion”)。@IntegrationConverter可以放置在类级别(带有@Component注释),或者位于@Configuration类中的@ bean方法。

也请参阅F.6节,“注释支持”以获取更多关于消息注释的信息。

3.6 Programming Considerations

一般建议你使用普通的java对象(POJO),尽可能在绝对必要的时候暴露你的代码框架。

如果您将框架暴露在类中,则需要考虑一些考虑因素,尤其是在应用程序启动期间;其中一些问题在这里列出。

如果您的组件是ApplicationContextAware,您通常不应该在setApplicationContext方法中“使用”ApplicationContext ,只需存储一个引用并在上下文生命周期中推迟这些使用。
如果您的组件是InitializingBean 或使用@PostConstruct方法,不要从这些初始化方法发送任何消息——当调用这些方法时,应用程序上下文尚未初始化,并且发送这样的消息可能会失败。如果您需要在启动期间发送消息,请实现ApplicationListener并等待ContextRefreshedEvent事件。或者,实现SmartLifecycle,将bean放在后期,并从start()方法发送消息。

4. Messaging Channels

默认DirectChannel:Point to Point

<int:channel id="exampleChannel"/>

Publish Subscribe channel:

<int:publish-subscribe-channel id="exampleChannel"/>

DirectChannel Configuration
默认通道将具有循环负载均衡器,并将启用failover(参见“DirectChannel”一节中的详细讨论)。若要禁用其中之一或两者,请添加子元素并配置属性:

<int:channel id="failFastChannel">
    <int:dispatcher failover="false"/></int:channel>
<int:channel id="channelWithFixedOrderSequenceFailover">
<int:dispatcher load-balancer="none"/></int:channel>

Datatype Channel Configuration

有时消费者只能处理特定类型的有效载荷,因此需要确保输入消息的有效载荷类型。当然,首先想到的是消息过滤器。然而,所有消息过滤器都会过滤掉不符合消费者需求的消息。另一种方法是使用基于内容的路由器,并将具有非兼容数据类型的消息路由到特定的变压器,以强制转换/转换为所需的数据类型。当然,这是可行的,但是实现同样的事情的一个更简单的方法是应用数据类型信道模式。对于每个特定的有效载荷数据类型,可以使用单独的数据类型通道。

<int:channel id="numberChannel" datatype="java.lang.Number"/>
如果数据类型是多种的:
<int:channel id="stringOrNumberChannel" datatype="java.lang.String,java.lang.Number"/>

numberChannel只能接受Number类型的数据,如果不是就会报错

MessageChannel inChannel = context.getBean("numberChannel", MessageChannel.class);
inChannel.send(new GenericMessage<String>("5"));
报错:
Exception in thread "main" org.springframework.integration.MessageDeliveryException:
Channel 'numberChannel'
expected one of the following datataypes [class java.lang.Number],
but received [class java.lang.String]


这时可以定义一个数据转换器

public static class StringToIntegerConverter implements Converter<String, Integer> {
    public Integer convert(String source) {
        return Integer.parseInt(source);
    }
}

在xml中注册:

<int:converter ref="strToInt"/>

<bean id="strToInt" class="org.springframework.integration.util.Demo.StringToIntegerConverter"/>

当转换器元素被解析时,如果没有定义的话,它将按需创建“integrationConversionService”bean。使用该转换器,发送操作将是成功的,因为数据类型通道将使用该转换器将字符串有效载荷转换为整数。

**QueueChannel Configuration**
<int:channel id="queueChannel">
<queue capacity="25"/>
</int:channel>

如果不为这个<queue/>子元素”提供容量属性值,则生成的队列将是无界的。为了避免诸如OutOfMemoryErrors之类的问题,强烈建议为有界队列设置显式值。

由于QueueChannel 提供了缓冲消息的能力,但是仅在默认情况下在内存中这样做,它还引入了在系统故障的情况下可能丢失消息的可能性。为了减轻这种风险,QueueChannel 可以通过MessageGroupStore 策略接口的支持来实现持久性。有关MessageGroupStore 和MessageStore 的详细信息,请参阅第9.4节“消息存储”。

注意:Capacity属性和message-store不能同时使用

默认情况下,QueueChannel 将消息存储在内存队列中,从而导致上述丢失消息场景。但是Spring integration提供持久存储,例如JdbcChannelMessageStore。

<int:channel id="dbBackedChannel">
    <int:queue message-store="channelStore"/></int:channel>
<bean id="channelStore" class="o.s.i.jdbc.store.JdbcChannelMessageStore">
    <property name="dataSource" ref="dataSource"/>
    <property name="channelMessageStoreQueryProvider" ref="queryProvider"/></bean>

4.3 Channel Adapter

4.3.1配置入站通道适配器

<int:inbound-channel-adapter ref="source1" method="method1" channel="channel1">
<int:poller fixed-rate="5000"/>
</int:inbound-channel-adapter>
<int:inbound-channel-adapter ref="source2" method="method2" channel="channel2">
<int:poller cron="30 * 9-17 * * MON-FRI"/>
</int:channel-adapter>

4.3.2配置出站通道适配器

“出站通道适配器”元素还可以将MessageChannelPOJO消费者方法连接到应该与发送到该通道的消息的有效载荷一起调用的任何POJO消费者方法。

<int:outbound-channel-adapter  channel = “channel1”  ref = “target”  method = “handle” /> 
<beansbean  id = “targetclass = “org.Foo” />

如果正在调整的频道是a PollableChannel,则提供一个轮询器子元素:

<int:outbound-channel-adapter  channel = “channel2”  ref = “target”  method = “handle” > 
<intpoller  fixed-rate = “3000” />
</ intoutbound-channel-adapter> 
<beansbean  id = “targetclass = “org.Foo” />

如果POJO消费者实现可以在其他定义中重用,通常建议使用“ref”属性。但是,如果消费者实现仅由单个定义引用,则可以将其定义为内部bean:

<int:outbound-channel-adapter  channel = “channel”  method = “handle” > 
<beansbean  class = “org.Foo” />
</ intoutbound-channel-adapter>

<outbound-channel-adapter>不允许在同一配置中同时使用“ref”属性和内部处理程序定义,因为它会创建一个模糊的条件。这样的配置将导致异常被抛出。
任何通道适配器都可以在没有“通道”引用的情况下创建,在这种情况下,它将隐式创建一个实例DirectChannel。创建的频道名称将与<inbound-channel-adapter>or <outbound-channel-adapter>元素的“id”属性匹配。因此,如果没有提供“频道”,则需要“ID”。

4.3.3通道适配器表达式和脚本

<inbound-channel-adapter><outbound-channel-adapter>还提供对SpEL表达式评估的支持。
从Spring Integration 3.0开始,<int:inbound-channel-adapter/>也可以使用SpEL <expression/>(或甚至 <script/>)子元素进行配置

<int:inbound-channel-adapter  ref = “source1”  method = “method1”  channel = “channel1” > 
    <int:poller  max-messages-per-poll =1”  fixed-delay =5000/> 
<script :script  lang = “ruby”  location = “Foo.rb”  refresh-check-delay =5000/>
</ int:inbound-channel-adapter>

4.4 Messaging Bridge

4.4.1介绍

消息传递桥是一个相对简单的端点,它只是简单地连接两个消息通道或通道适配器。例如,您可能希望将PollableChannel连接到SubscribableChannel以便订阅端点,不必担心任何轮询配置。相反,消息传递桥提供了轮询配置。
通过在两个通道之间提供中间轮询器,可以使用消息传递桥来限制入站消息。轮询器的触发器将决定消息到达第二个通道的速率,轮询器的“maxMessagesPerPoll”属性将强制限制吞吐量。
消息传递桥的另一个有效用途是连接两个不同的系统。在这种情况下,Spring Integration的作用将仅限于在这些系统之间建立连接并在必要时管理轮询器。在两个系统之间至少有一个Transformer在它们的格式之间进行转换可能更常见,在这种情况下,这些通道将作为Transformer端点的输入通道和输出通道。如果不需要数据格式转换,则消息传递桥确实足够了。

4.4.2配置网桥

<bridge>元素用于在两个消息通道或通道适配器之间创建消息传递桥。只需提供“输入通道”和“输出通道”属性:

<int:bridge  input-channel = “input”  output-channel = “output” />

如上所述,Messaging Bridge的常见用例是将a连接PollableChannel到a SubscribableChannel,并且在执行此角色时,Messaging Bridge也可用作调节器:

<int:bridge  input-channel = “pollable”  output-channel = “subscribable” > 
     <int:poller  max-messages-per-poll =10”  fixed-rate =5000/> 
 </ int:bridge>

连接通道适配器同样简单。下面是来自Spring Integration的“stream”命名空间的“stdin”和“stdout”适配器之间的简单回显示例。

<int-stream:stdin-channel-adapter  id = “stdin” /> 

 <int-stream:stdout-channel-adapter  id = “stdout” /> 

 <int:bridge  id = “echo”  input-channel = “stdin”  output-channel = “stdout” />

当然,对于其他(可能更有用的)通道适配器桥接(例如文件到JMS)或邮件到文件,该配置将类似。各种渠道适配器将在即将出版的章节中讨论。

5. Message Construction

5.1消息

Spring Integration Message是一个通用的数据容器。任何对象都可以作为有效负载提供,并且每个对象Message还包含包含用户可扩展属性(作为键值对)的标头。
5.1.1消息接口

public interface Message<T> {

    T getPayload();

    MessageHeaders getHeaders();

}

5.1.2消息头

public final class MessageHeaders implements Map<String, Object>, Serializable {
  ...
}

即使MessageHeaders实现了Map,它实际上也是一个只读实现。任何尝试在Map中放置一个值都会导致一个UnsupportedOperationException。这同样适用于删除和清除。由于消息可能会传递给多个消费者,因此无法修改该地图的结构。同样,消息的有效载荷对象不能在初始创建后设置。然而,头部值本身(或有效载荷对象)的可变性故意留作框架用户的决定。
作为Map的一个实现,显然可以通过调用get(..)标题的名称来获取标题。或者,您可以提供预期的类作为附加参数。更好的是,当检索一个预定义的值时,便利的获取器可用。以下是这三个选项中的每一个的示例:

 Object someValue = message.getHeaders().get("someKey");

 CustomerId customerId = message.getHeaders().get("customerId", CustomerId.class);

 Long timestamp = message.getHeaders().getTimestamp();

从Spring Framework 4.0和Spring Integration 4.0开始,核心消息传递抽象已经转移到Spring消息传递模块,并且MessageHeaderAccessor引入了新的API来提供对消息传递实现的额外抽象。所有(核心)Spring集成特定的消息头常量现在在IntegrationMessageHeaderAccessor类中声明:

5.1.3消息实现

Message接口的基本实现是GenericMessage<T>,它提供了两个构造函数:

new GenericMessage<T>(T payload);

new GenericMessage<T>(T payload, Map<String, Object> headers)

当消息被创建时,将生成一个随机的唯一ID。接受标题图的构造函数会将提供的标题复制到新创建的消息中。
还有一个Message设计用于沟通错误条件的方便实施。该实现将Throwable对象作为其有效载荷:

ErrorMessage message = new ErrorMessage(someThrowable);

Throwable t = message.getPayload();

注意这个实现利用了GenericMessage基类被参数化的事实。因此,如两个示例所示,检索消息有效负载对象时不需要强制转换。

5.1.4 MessageBuilder助手类

您可能会注意到,Message接口为其有效负载和标题定义了检索方法,但没有定义setter。原因是消息在初始创建后无法修改。因此,当一个Message实例被发送给多个消费者时(例如通过发布订阅频道),如果其中一个消费者需要发送具有不同有效载荷类型的答复,则需要创建一个新的消息。结果,其他消费者不受这些变化的影响。请记住,多个消费者可以访问相同的有效负载实例或标题值,并且这样的实例本身是不可变的,这是一个留给开发人员的决定。换句话说,Messages的合同与一个不可修改的Collection类似,而MessageHeaders的地图进一步说明了这一点; 即使MessageHeaders类实现java.util.Map,任何尝试在MessageHeaders 上调用put操作(或删除或清除)都会导致UnsupportedOperationException。
而不需要地图的创建和人口通入GenericMessage构造,Spring集成并提供一个更方便的方式来构造消息:MessageBuilder。MessageBuilder提供了两种工厂方法,用于从现有消息或有效负载对象中创建消息。从现有消息构建时,该消息的标题和有效内容将被复制到新消息:

Message<String> message1 = MessageBuilder.withPayload("test")
        .setHeader("foo", "bar")
        .build();

Message<String> message2 = MessageBuilder.fromMessage(message1).build();

assertEquals("test", message2.getPayload());
assertEquals("bar", message2.getHeaders().get("foo"));

如果您需要使用新的有效内容创建消息,但仍想从现有消息复制标题,则可以使用其中一种复制方法。

Message<String> message3 = MessageBuilder.withPayload("test3")
        .copyHeaders(message1.getHeaders())
        .build();

Message<String> message4 = MessageBuilder.withPayload("test4")
        .setHeader("foo", 123)
        .copyHeadersIfAbsent(message1.getHeaders())
        .build();

assertEquals("bar", message3.getHeaders().get("foo"));
assertEquals(123, message4.getHeaders().get("foo"));

请注意,这copyHeadersIfAbsent不会覆盖现有的值。另外,在上面的第二个示例中,您可以看到如何设置任何用户定义的标题setHeader。最后,为预定义的头文件提供了设置方法,以及用于设置任何头文件的非破坏性方法(MessageHeaders也为预定义的头文件名称定义了常量)。

Message<Integer> importantMessage = MessageBuilder.withPayload(99)
        .setPriority(5)
        .build();

assertEquals(5, importantMessage.getHeaders().getPriority());

Message<Integer> lessImportantMessage = MessageBuilder.fromMessage(importantMessage)
        .setHeaderIfAbsent(IntegrationMessageHeaderAccessor.PRIORITY, 2)
        .build();

assertEquals(2, lessImportantMessage.getHeaders().getPriority());

在priority使用时,报头只考虑PriorityChannel(如在下一章说明)。它被定义为java.lang.Integer。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值