# 消息发布

## 消息发布

（面向方面的编程） AOP 消息发布特性允许你构造和发送消息，作为方法调用的副产品。例如，假设你有一个组件，并且每当该组件的状态发生变化时，你都希望得到一条消息的通知。发送此类通知的最简单方法是将消息发送到专用通道，但是如何将更改对象状态的方法调用连接到消息发送过程，以及通知消息应该如何进行结构化？ AOP 消息发布特性使用配置驱动的方法处理这些职责。

### 消息发布配置

Spring 集成提供了两种方法：XML 配置和注释驱动配置。

#### 注释驱动的配置，使用`@Publisher`注释

注释驱动的方法允许你使用`@Publisher`注释对任何方法进行注释，以指定“通道”属性。从版本 5.1 开始，要打开这个功能，你必须在某些`@Configuration`类上使用`@EnablePublisher`注释。有关更多信息，请参见[configuration and`@EnableIntegration`]。消息由方法调用的返回值构造，并发送到由“channel”属性指定的通道。为了进一步管理消息结构，还可以使用`@Payload`和`@Header`注释的组合。

在内部， Spring 集成的消息发布功能通过定义`PublisherAnnotationAdvisor`和 Spring 表达式语言使用 Spring  AOP，从而为你提供了相当大的灵活性，并可以控制它所发布的`Message`的结构。

`PublisherAnnotationAdvisor`定义并绑定了以下变量：

* `#return`:绑定到一个返回值，让你引用它或它的属性（例如，`#return.something`，其中’something’是绑定到`#return`的对象的一个属性）

* `#exception`:如果方法调用引发了异常，则绑定到异常

* `#args`:绑定到方法参数，这样就可以按名称提取单个参数（例如，`#args.fname`）

考虑以下示例：

```
@Publisher
public String defaultPayload(String fname, String lname) {
  return fname + " " + lname;
}
```

在前面的示例中，消息是用以下结构构造的：

* 消息有效负载是方法的返回类型和值。这是默认设置。

* 新构造的消息被发送到一个默认的发布者通道，该通道配置了一个注释后处理程序（将在本节后面介绍）。

下面的示例与前面的示例相同，只是它不使用默认的发布通道：

```
@Publisher(channel="testChannel")
public String defaultPayload(String fname, @Header("last") String lname) {
  return fname + " " + lname;
}
```

我们不使用默认的发布通道，而是通过设置`@Publisher`注释的’channel’属性来指定发布通道。我们还添加了`@Header`注释，这将导致名为“last”的消息头具有与“lname”方法参数相同的值。这个头被添加到新构建的消息中。

下面的示例与前面的示例几乎相同：

```
@Publisher(channel="testChannel")
@Payload
public String defaultPayloadButExplicitAnnotation(String fname, @Header String lname) {
  return fname + " " + lname;
}
```

唯一的区别是，我们在方法上使用了`@Payload`注释，以显式地指定方法的返回值应该用作消息的有效负载。

下面的示例通过使用`@Payload`注释中的 Spring 表达式语言对前面的配置进行了扩展，以进一步指导有关如何构造消息的框架：

```
@Publisher(channel="testChannel")
@Payload("#return + #args.lname")
public String setName(String fname, String lname, @Header("x") int num) {
  return fname + " " + lname;
}
```

在前面的示例中，消息是方法调用的返回值和“lname”输入参数的串联。名为“X”的消息头的值由“NUM”输入参数确定。这个头被添加到新构建的消息中。

```
@Publisher(channel="testChannel")
public String argumentAsPayload(@Payload String fname, @Header String lname) {
  return fname + " " + lname;
}
```

在前面的示例中，你可以看到`@Payload`注释的另一种用法。在这里，我们对一个方法参数进行注释，该方法参数将成为新构建的消息的有效负载。

与 Spring 中的大多数其他注释驱动特性一样，你需要注册一个后处理器（`PublisherAnnotationBeanPostProcessor`）。下面的示例展示了如何做到这一点：

```
<bean class="org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor"/>
```

为了获得更简洁的配置，你可以使用名称空间支持，如下例所示：

```
<int:annotation-config>
    <int:enable-publisher default-publisher-channel="defaultChannel"/>
</int:annotation-config>
```

对于 Java 配置，你必须使用`@EnablePublisher`注释，如下例所示：

```
@Configuration
@EnableIntegration
@EnablePublisher("defaultChannel")
public class IntegrationConfiguration {
    ...
}
```

从版本 5.1.3 开始，`<int:enable-publisher>`组件以及`@EnablePublisher`注释具有`proxy-target-class`和`order`属性，用于优化`ProxyFactory`配置。

类似于其他 Spring 注释（`@Component`，`@Scheduled`，以此类推），你也可以使用`@Publisher`作为元注释。这意味着你可以定义你自己的注释，这些注释的处理方式与`@Publisher`本身的处理方式相同。下面的示例展示了如何做到这一点：

```
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Publisher(channel="auditChannel")
public @interface Audit {
...
}
```

在前面的示例中，我们定义了`@Audit`注释，它本身用`@Publisher`注释。还请注意，你可以在元注释上定义`channel`属性，以封装在此注释中发送消息的位置。现在，你可以使用`@Audit`注释对任何方法进行注释，如下例所示：

```
@Audit
public String test() {
    return "Hello";
}
```

在前面的示例中，`test()`方法的每次调用都会导致一条消息，该消息的有效负载是从其返回值创建的。每条消息都被发送到名为`auditChannel`的通道。这种技术的一个好处是，你可以避免跨多个注释重复相同的通道名。你还可以在你自己的（可能是特定于领域的）注释和框架提供的注释之间提供一定级别的间接。

你还可以对类进行注释，这使你可以在该类的每个公共方法上应用此注释的属性，如下例所示：

```
@Audit
static class BankingOperationsImpl implements BankingOperations {

  public String debit(String amount) {
     . . .

  }

  public String credit(String amount) {
     . . .
  }

}
```

#### 使用`<publishing-interceptor>`元素的基于 XML 的方法

基于 XML 的方法允许你配置与基于名称空间的`MessagePublishingInterceptor`配置相同的基于 AOP 的消息发布功能。它当然比注释驱动的方法有一些好处，因为它允许你使用 AOP 切入点表达式，因此可能同时拦截多个方法，或者拦截和发布你没有源代码的方法。

要用 XML 配置消息发布，只需要做以下两件事：

* 通过使用`<publishing-interceptor>`XML 元素为`MessagePublishingInterceptor`提供配置。

* 提供 AOP 配置以将`MessagePublishingInterceptor`应用于托管对象。

下面的示例展示了如何配置`publishing-interceptor`元素：

```
<aop:config>
  <aop:advisor advice-ref="interceptor" pointcut="bean(testBean)" />
</aop:config>
<publishing-interceptor id="interceptor" default-channel="defaultChannel">
  <method pattern="echo" payload="'Echoing: ' + #return" channel="echoChannel">
    <header name="things" value="something"/>
  </method>
  <method pattern="repl*" payload="'Echoing: ' + #return" channel="echoChannel">
    <header name="things" expression="'something'.toUpperCase()"/>
  </method>
  <method pattern="echoDef*" payload="#return"/>
</publishing-interceptor>
```

`<publishing-interceptor>`配置看起来与基于注释的方法非常相似，并且它还使用了 Spring 表达式语言的强大功能。

在前面的示例中，执行`echo`方法的`testBean`将呈现具有以下结构的`Message`:

* `Message`有效载荷类型为`String`，其内容如下：`Echoing: [value]`，其中`value`是由执行的方法返回的值。

* `Message`具有一个标题，其名称为`things`，其值为`something`。

* 将`Message`发送到`echoChannel`。

第二种方法与第一种方法非常相似。在这里，以“REPL”开头的每个方法都会呈现具有以下结构的`Message`:

* `Message`有效载荷与前面的示例相同。

* `Message`有一个名为`things`的头，其值是 spel 表达式`'something'.toUpperCase()`的结果。

* 将`Message`发送到`echoChannel`。

第二种方法，映射以`echoDef`开头的任何方法的执行，生成具有以下结构的`Message`:

* `Message`有效负载是由执行的方法返回的值。

* 由于不提供`channel`属性，所以将`Message`发送到由`publisher`定义的`defaultChannel`。

对于简单的映射规则，你可以依赖`publisher`默认值，如下例所示：

```
<publishing-interceptor id="anotherInterceptor"/>
```

前面的示例将匹配切入点表达式的每个方法的返回值映射到一个有效负载，并将其发送到`default-channel`。如果没有指定`defaultChannel`（前面的示例没有这样做），则消息将被发送到全局`nullChannel`（相当于`/dev/null`）。

##### 异步发布

发布与组件的执行发生在同一个线程中。因此，默认情况下，它是同步的。这意味着整个消息流必须等到发布者的消息流完成。然而，开发人员通常想要完全相反的结果：使用这个消息发布特性来启动异步流。例如，你可能托管一个接收远程请求的服务（HTTP、WS 等）。你可能希望在内部将此请求发送到一个可能需要一段时间的流程中。然而，你也可能希望立即回复用户。因此，与将处理的入站请求发送到输出通道（传统的方式）不同，你可以使用“输出通道”或“replyChannel”头向调用者发送一个简单的类似于确认的答复，同时使用消息发布者功能来启动一个复杂的流。

以下示例中的服务接收到一个复杂的有效负载（需要进一步发送以进行处理），但它还需要向调用方回复一个简单的确认：

```
public String echo(Object complexPayload) {
     return "ACK";
}
```

因此，我们使用消息发布功能，而不是将复杂的流连接到输出通道。我们通过使用 Service 方法的输入参数（如前面的示例所示）将其配置为创建一个新消息，并将其发送到“LocalProcessChannel”。为了确保这个流是异步的，我们需要做的就是将它发送到任何类型的异步通道（在下一个示例中是`ExecutorChannel`）。下面的示例展示了如何使用异步`publishing-interceptor`:

```
<int:service-activator  input-channel="inputChannel" output-channel="outputChannel" ref="sampleservice"/>

<bean id="sampleservice" class="test.SampleService"/>

<aop:config>
  <aop:advisor advice-ref="interceptor" pointcut="bean(sampleservice)" />
</aop:config>

<int:publishing-interceptor id="interceptor" >
  <int:method pattern="echo" payload="#args[0]" channel="localProcessChannel">
    <int:header name="sample_header" expression="'some sample value'"/>
  </int:method>
</int:publishing-interceptor>

<int:channel id="localProcessChannel">
  <int:dispatcher task-executor="executor"/>
</int:channel>

<task:executor id="executor" pool-size="5"/>
```

处理这种情况的另一种方法是使用线接。见[Wire Tap](./channel.html#channel-wiretap)。

#### 基于调度触发器生成和发布消息

在前面的部分中，我们研究了消息发布特性，该特性将消息作为方法调用的副产品进行构造和发布。但是，在这些情况下，你仍然需要负责调用该方法。 Spring Integration2.0 在“inbound-channel-adapter”元素上添加了对调度消息生成器和发布器的支持，该支持带有新的`expression`属性。你可以基于几个触发器进行调度，其中任何一个都可以在“poller”元素上进行配置。目前，我们支持`cron`、`fixed-rate`、`fixed-delay`以及由你实现并由’trigger’属性值引用的任何自定义触发器。

如前所述，通过`<inbound-channel-adapter>`XML 元素提供了对计划生产者和发布者的支持。考虑以下示例：

```
<int:inbound-channel-adapter id="fixedDelayProducer"
       expression="'fixedDelayTest'"
       channel="fixedDelayChannel">
    <int:poller fixed-delay="1000"/>
</int:inbound-channel-adapter>
```

前面的示例创建了一个入站通道适配器，该适配器构造`Message`，其有效负载是在`expression`属性中定义的表达式的结果。每当出现`fixed-delay`属性指定的延迟时，就会创建和发送这样的消息。

下面的示例与前面的示例类似，只是使用了`fixed-rate`属性：

```
<int:inbound-channel-adapter id="fixedRateProducer"
       expression="'fixedRateTest'"
       channel="fixedRateChannel">
    <int:poller fixed-rate="1000"/>
</int:inbound-channel-adapter>
```

`fixed-rate`属性允许你以固定的速率（从每个任务的开始时间开始进行度量）发送消息。

下面的示例展示了如何应用具有`cron`属性中指定的值的 CRON 触发器：

```
<int:inbound-channel-adapter id="cronProducer"
       expression="'cronTest'"
       channel="cronChannel">
    <int:poller cron="7 6 5 4 3 ?"/>
</int:inbound-channel-adapter>
```

下面的示例展示了如何在消息中插入额外的标题：

```
<int:inbound-channel-adapter id="headerExpressionsProducer"
       expression="'headerExpressionsTest'"
       channel="headerExpressionsChannel"
       auto-startup="false">
    <int:poller fixed-delay="5000"/>
    <int:header name="foo" expression="6 * 7"/>
    <int:header name="bar" value="x"/>
</int:inbound-channel-adapter>
```

附加的消息头可以获取标量值或计算 Spring 表达式的结果。

如果需要实现自己的自定义触发器，则可以使用`trigger`属性提供对实现`org.springframework.scheduling.Trigger`接口的任何 Spring 配置 Bean 的引用。下面的示例展示了如何做到这一点：

```
<int:inbound-channel-adapter id="triggerRefProducer"
       expression="'triggerRefTest'" channel="triggerRefChannel">
    <int:poller trigger="customTrigger"/>
</int:inbound-channel-adapter>

<beans:bean id="customTrigger" class="o.s.scheduling.support.PeriodicTrigger">
    <beans:constructor-arg value="9999"/>
</beans:bean>
```