# 系统管理

## 系统管理

### 指标和管理

本节描述如何捕获 Spring 集成的指标。在最近的版本中，我们更多地依赖于 Micrometer（参见[https://micrometer.io](https://micrometer.io)），并且我们计划在未来的版本中更多地使用 Micrometer。

#### 遗留指标

旧的度量标准在版本 5.4 中被删除；请参见下面的 Micrometer 集成。

#### 在大容量环境中禁用日志记录

你可以在主消息流中控制调试日志记录。在非常大容量的应用程序中，对`isDebugEnabled()`的调用在一些日志记录子系统中可能非常昂贵。你可以禁用所有这样的日志记录，以避免这种开销。异常日志记录（调试或其他方式）不受此设置的影响。

下面的清单显示了用于控制日志记录的可用选项：

爪哇

```
@Configuration
@EnableIntegration
@EnableIntegrationManagement(
    defaultLoggingEnabled = "true" <1>)

public static class ContextConfiguration {
...
}
```

XML

```
<int:management default-logging-enabled="true"/> (1)
```

|**1**|设置为`false`以禁用主消息流中的所有日志记录，无论日志系统的类别设置如何。<br/>设置为“true”，以启用调试日志记录（如果还启用了日志记录子系统）。<br/>仅当你没有在 Bean 定义中显式配置该设置时才应用。<br/>默认值为`true`。|
|-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |`defaultLoggingEnabled`仅在未在 Bean 定义中显式配置相应设置的情况下才应用。|
|---|-----------------------------------------------------------------------------------------------------------------------------|

#### 千分尺积分

##### 概述

从版本 5.0.3 开始，在应用程序上下文中出现[Micrometer](https://micrometer.io/)`MeterRegistry`会触发对千分尺指标的支持。

要使用 Micrometer，将`MeterRegistry`bean 中的一个添加到应用程序上下文中。

对于每个`MessageH和ler`和`MessageChannel`，计时器被注册。对于每个`MessageSource`，注册一个计数器。

这仅适用于扩展`AbstractMessageHandler`、`AbstractMessageChannel`和`AbstractMessageSource`的对象（大多数框架组件都是这种情况）。

用于在消息通道上进行发送操作的`Timer`计具有以下名称或标记：

* `name`: `spring.integration.send`

* `tag`: `type:channel`

* `tag`: `name:<componentName>`

* `tag`: `result:(success|failure)`

* `tag`: `exception:(none|exception simple class name)`

* `description`: `Send processing time`

（带有`none`异常的`failure`结果表示通道的`send()`操作返回`false`。

用于在可匹配消息通道上进行接收操作的`Counter`计具有以下名称或标记：

* `name`: `spring.integration.receive`

* `tag`: `type:channel`

* `tag`: `name:<componentName>`

* `tag`: `result:(success|failure)`

* `tag`: `exception:(none|exception simple class name)`

* `description`: `Messages received`

用于消息处理程序上的操作的`Timer`表具有以下名称或标记：

* `name`: `spring.integration.send`

* `tag`: `type:handler`

* `tag`: `name:<componentName>`

* `tag`: `result:(success|failure)`

* `tag`: `exception:(none|exception simple class name)`

* `description`: `Send processing time`

消息源的`Counter`表具有以下名称/标记：

* `name`: `spring.integration.receive`

* `tag`: `type:source`

* `tag`: `name:<componentName>`

* `tag`: `result:success`

* `tag`: `exception:none`

* `description`: `Messages received`

此外，还有三个`Gauge`米：

* `spring.integration.channels`:应用程序中`MessageChannels`的数量。

* `spring.integration.handlers`:应用程序中`MessageHandlers`的数量。

* `spring.integration.sources`:应用程序中`MessageSources`的数量。

通过提供`MicrometerMetricsCaptor`的子类，可以定制由集成组件创建的`Meters`的名称和标记。[MicroMeterCustomMetricstests](https://github.com/spring-projects/spring-integration/blob/main/spring-integration-core/src/test/java/org/springframework/integration/support/management/micrometer/MicrometerCustomMetricsTests.java)测试用例展示了如何实现这一点的一个简单示例。你还可以通过在 Builder 子类上重载`build()`方法来进一步自定义仪表。

从版本 5.1.13 开始，`QueueChannel`公开了用于队列大小和剩余容量的千分尺：

* `name`: `spring.integration.channel.queue.size`

* `tag`: `type:channel`

* `tag`: `name:<componentName>`

* `description`: `The size of the queue channel`

and

* `name`: `spring.integration.channel.queue.remaining.capacity`

* `tag`: `type:channel`

* `tag`: `name:<componentName>`

* `description`: `The remaining capacity of the queue channel`

##### 禁用仪表

使用[遗留指标](#legacy-metrics)（现已删除），你可以指定哪些集成组件将收集度量。默认情况下，所有仪表都是在首次使用时注册的。现在，使用 Micrometer，可以将`MeterFilter`s 添加到`MeterRegistry`中，以防止部分或全部被注册。你可以通过提供的任何属性（`name`、`tag`等）过滤掉（拒绝）表。有关更多信息，请参见千分尺文档中的[仪表过滤器](https://micrometer.io/docs/concepts#_meter_filters)。

例如，给出：

```
@Bean
public QueueChannel noMeters() {
    return new QueueChannel(10);
}
```

你可以通过以下方式抑制仅此频道的仪表注册：

```
registry.config().meterFilter(MeterFilter.deny(id ->
        "channel".equals(id.getTag("type")) &&
        "noMeters".equals(id.getTag("name"))));
```

####  Spring 集成 JMX 支持

另见[JMX 支持](./jmx.html#jmx)。

### 消息历史记录

消息传递体系结构的主要好处是松耦合，这样参与的组件就不会对彼此保持任何意识。仅这一点就使应用程序变得非常灵活，允许你在不影响流的其余部分的情况下更改组件、更改消息传递路径、更改消息消费风格（轮询与事件驱动），等等。然而，当情况出现问题时，这种低调的建筑风格可能会变得很困难。在调试时，你可能需要尽可能多的有关消息的信息（消息的来源、经过的通道和其他详细信息）。

消息历史是一种模式，它可以帮助你选择保持对消息路径的某种级别的了解，用于调试目的或维护审计跟踪。 Spring 集成提供了一种简单的方式来配置消息流，以维护消息历史记录，方法是在消息中添加一个头，并在每次消息通过被跟踪的组件时更新该头。

#### 消息历史配置

要启用消息历史记录，只需要在配置中定义`message-history`元素（或`@EnableMessageHistory`），如以下示例所示：

```
@Configuration
@EnableIntegration
@EnableMessageHistory
```

```
<int:message-history/>
```

现在，每个已命名的组件（定义了“ID”的组件）都会被跟踪。该框架在你的消息中设置“历史”头。其值 a`List<Properties>`。

考虑以下配置示例：

```
@MessagingGateway(defaultRequestChannel = "bridgeInChannel")
public interface SampleGateway {
   ...
}

@Bean
@Transformer(inputChannel = "enricherChannel", outputChannel="filterChannel")
HeaderEnricher sampleEnricher() {
    HeaderEnricher enricher =
           new HeaderEnricher(Collections.singletonMap("baz", new StaticHeaderValueMessageProcessor("baz")));
    return enricher;
}
```

```
<int:gateway id="sampleGateway"
    service-interface="org.springframework.integration.history.sample.SampleGateway"
    default-request-channel="bridgeInChannel"/>

<int:header-enricher id="sampleEnricher" input-channel="enricherChannel" output-channel="filterChannel">
    <int:header name="baz" value="baz"/>
</int:header-enricher>
```

前面的配置生成一个简单的消息历史结构，其输出类似于以下内容：

```
[{name=sampleGateway, type=gateway, timestamp=1283281668091},
 {name=sampleEnricher, type=header-enricher, timestamp=1283281668094}]
```

要访问消息历史记录，只需访问`MessageHistory`头。下面的示例展示了如何做到这一点：

```
Iterator<Properties> historyIterator =
    message.getHeaders().get(MessageHistory.HEADER_NAME, MessageHistory.class).iterator();
assertTrue(historyIterator.hasNext());
Properties gatewayHistory = historyIterator.next();
assertEquals("sampleGateway", gatewayHistory.get("name"));
assertTrue(historyIterator.hasNext());
Properties chainHistory = historyIterator.next();
assertEquals("sampleChain", chainHistory.get("name"));
```

你可能不想跟踪所有的组件。要根据组件的名称将历史限制为某些组件，可以提供`tracked-components`属性，并指定一个以逗号分隔的组件名称和模式列表，该列表与要跟踪的组件相匹配。下面的示例展示了如何做到这一点：

```
@Configuration
@EnableIntegration
@EnableMessageHistory("*Gateway", "sample*", "aName")
```

```
<int:message-history tracked-components="*Gateway, sample*, aName"/>
```

在前面的示例中，仅对以“gateway”结尾、以“sample”开头或与名称“aname”完全匹配的组件维护消息历史记录。

此外，`MessageHistoryConfigurer` Bean 现在被`IntegrationMBeanExporter`公开为 JMX MBean（参见[MBean 出口商](./jmx.html#jmx-mbean-exporter)），允许你在运行时更改模式。但是，请注意，为了更改模式，必须停止 Bean（关闭消息历史记录）。这个特性对于临时打开历史记录来分析系统可能是有用的。MBean 的对象名是`<domain>:name=messageHistoryConfigurer,type=MessageHistoryConfigurer`。

|   |只有一个`@EnableMessageHistory`（或`<message-history/>`）必须在应用程序上下文中声明为组件跟踪配置的单个源。<br/>不要为`MessageHistoryConfigurer`使用通用的 Bean 定义。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |根据定义，消息历史记录头是不可变的（不能重写历史记录），因此，在编写消息历史记录值时，组件要么创建新消息（当组件是源消息时），要么从请求消息中复制历史记录，修改它并在回复消息上设置新的列表。<br/>在这两种情况下，即使消息本身跨越了线程边界，也可以追加这些值。<br/>这意味着历史值可以大大简化异步消息流中的调试。|
|---| |

### 消息存储

[*Enterprise 整合模式 *](https://www.enterpriseintegrationpatterns.com/)书标识了几种具有缓冲消息能力的模式。例如，聚合器缓冲消息直到可以释放消息，而`QueueChannel`缓冲消息直到消费者明确地从该通道接收这些消息。由于消息流中的任何一点都可能发生故障，缓冲消息的 EIP 组件还引入了消息可能丢失的点。

为了减少丢失消息的风险，EIP 定义了[消息存储](https://www.enterpriseintegrationpatterns.com/MessageStore.html)模式，该模式允许 EIP 组件存储消息，通常是在某种类型的持久性存储（例如 RDBMS）中。

Spring 集成通过以下方式提供对消息存储模式的支持：

* 定义`org.springframework.integration.store.MessageStore`策略接口

* 提供此接口的几种实现方式

* 在所有具有缓冲消息功能的组件上公开`message-store`属性，以便你可以插入实现`MessageStore`接口的任何实例。

有关如何配置特定消息存储实现以及如何将`MessageStore`实现注入特定缓冲组件的详细信息在整个手册中进行了描述（请参阅特定组件，例如[Queuechannel](./channel.html#channel-configuration-queuechannel)，[Aggregator](./aggregator.html#aggregator)，[Delayer](./delayer.html#delayer)等）。以下两个示例展示了如何为`QueueChannel`和聚合器向消息存储区添加引用：

例1.Queuechannel

```
<int:channel id="myQueueChannel">
    <int:queue message-store="refToMessageStore"/>
<int:channel>
```

例2.聚合器

```
<int:aggregator … message-store="refToMessageStore"/>
```

默认情况下，消息通过使用`o.s.i.store.SimpleMessageStore`存储在内存中，这是`MessageStore`的一种实现。对于开发环境或简单的低容量环境来说，这可能是很好的，在这些环境中，潜在的非持久性消息丢失不是一个问题。然而，典型的生产应用程序需要一个更健壮的选项，不仅要减轻消息丢失的风险，还要避免潜在的内存不足错误。因此，我们还为各种数据存储提供`MessageStore`实现。以下是受支持的实现的完整列表：

* [JDBC 消息存储](./jdbc.html#jdbc-message-store):使用 RDBMS 存储消息

* [Redis 消息存储](./redis.html#redis-message-store):使用 Redis 键/值数据存储来存储消息

* [MongoDB 消息存储](./mongodb.html#mongodb-message-store):使用 MongoDB 文档存储来存储消息

* [Gemfire 消息存储](./gemfire.html#gemfire-message-store):使用 Gemfire 分布式缓存存储消息

|   |但是，在使用`MessageStore`的持久实现时要注意一些限制。<br/><br/>消息数据（有效载荷和报头）通过使用不同的序列化策略进行序列化和反序列化，这取决于`MessageStore`的实现方式。<br/>例如，当使用`JdbcMessageStore`时，默认情况下，只有`Serializable`数据是持久的。<br/>在这种情况下，在进行序列化之前，要删除不可序列化的标头。<br/>此外，要注意传输适配器（例如 FTP、HTTP、JMS 和其他）注入的特定于协议的标头。<br/>例如，`<http:inbound-channel-adapter/>`将 HTTP 标头映射到消息标头，其中之一是一个`ArrayList`的不可序列化`org.springframework.http.MediaType`实例。你可以将你自己的`Serializer`和`Deserializer`策略接口的实现注入到一些`MessageStore`实现中（例如`JdbcMessageStore`），以改变序列化和反序列化的行为。<br/><br/>特别注意表示某些类型数据的头。<br/>例如，如果其中一个头包含某个 Spring  Bean 的实例，那么在反序列化时，你可能会得到另一个 Bean 的实例，这将直接影响框架创建的一些隐式头（例如`REPLY_CHANNEL`或`ERROR_CHANNEL`）。，<br/>目前，它们不能序列化，但是，即使它们是，反序列化的通道也不会表示预期的实例。<br/><br/>从 Spring Integration3.0 开始，你可以使用配置为在用`HeaderChannelRegistry`注册通道后用名称替换这些头的 header 来解决此问题，<br/><br/>同样，考虑一下，当你按照以下方式配置消息流时会发生什么：网关队列通道（由持久性消息存储支持）服务激活器，<br/>该网关创建了一个临时回复通道，当服务激活器的 poller 从队列中读取消息时，该通道会丢失。，再次，<gtr=“217”/，你可以使用 header enricher 用`String`表示替换 header。<br/><br/>有关更多信息，请参见[页眉 Enricher](./content-enrichment.html#header-enricher)。|
|---| |

Spring Integration4.0 引入了两个新的接口：

* `ChannelMessageStore`:实现针对`QueueChannel`实例的特定操作

* `PriorityCapableChannelMessageStore`:标记用于`MessageStore`实例的`PriorityChannel`实现，并为持久消息提供优先顺序。

真正的行为取决于执行情况。该框架提供了以下实现方式，它们可以用作`MessageStore`和`PriorityChannel`的持久性`MessageStore`:

* [Redis Channel 消息存储](./redis.html#redis-cms)

* [MongoDB 通道消息存储](./mongodb.html#mongodb-priority-channel-message-store)

* [支持消息通道](./jdbc.html#jdbc-message-store-channels)

|   |注意`SimpleMessageStore`<br/><br/>从版本 4.1 开始，在调用`SimpleMessageStore`时，`SimpleMessageStore`不再复制消息组。对于大型消息组，<br/>，这是一个重要的性能问题。<br/>4.0.1 引入了一个布尔`copyOnGet`属性，该属性允许你控制此行为，<br/>当聚合器内部使用时，将此属性设置为`false`以提高性能。<br/>默认情况下，它现在是`false`。<br/><br/>在组件之外访问组存储的用户因为聚合器现在获得了对聚合器正在使用的组的直接引用，而不是一个副本。<br/>聚合器之外的组的操作可能会导致不可预测的结果。<br/><br/>由于这个原因，你不应该执行这样的操作，或者将`copyOnGet`属性设置为`true`。|
|---| |

#### 使用`MessageGroupFactory`

从版本 4.3 开始，一些`MessageGroupStore`实现可以被注入一个自定义`MessageGroupFactory`策略来创建和定制`MessageGroup`实例，该实例由`MessageGroupStore`使用。这默认为`SimpleMessageGroupFactory`，它基于`GroupType.HASH_SET`（`LinkedHashSet`）内部集合生成`SimpleMessageGroup`实例。其他可能的选项是`SYNCHRONISED_SET`和`BLOCKING_QUEUE`，其中最后一个选项可以用来恢复先前的`SimpleMessageGroup`行为。而且`PERSISTENT`选项也是可用的。有关更多信息，请参见下一节。从版本 5.0.1 开始，当组中消息的顺序和唯一性无关紧要时，`LIST`选项也可用。

#### 持久`MessageGroupStore`和 lazy-load

从版本 4.3 开始，所有持久的`MessageGroupStore`实例都以延迟加载的方式从存储中检索`MessageGroup`实例及其`messages`实例。在大多数情况下，对于相关`MessageHandler`实例是有用的（参见[Aggregator](./aggregator.html#aggregator)和[重测序器](./resequencer.html#resequencer)），当它将增加开销以从存储中加载整个`MessageGroup`上的每个相关操作时。

你可以使用`AbstractMessageGroupStore.setLazyLoadMessageGroups(false)`选项从配置中关闭惰性负载行为。

我们对 MongoDB`MessageStore`（[MongoDB 消息存储](./mongodb.html#mongodb-message-store)）和`<aggregator>`（[Aggregator](./aggregator.html#aggregator)）上的 lazy-load 进行的性能测试使用了一个自定义`release-strategy`，类似于以下内容：

```
<int:aggregator input-channel="inputChannel"
                output-channel="outputChannel"
                message-store="mongoStore"
                release-strategy-expression="size() == 1000"/>
```

它为 1000 条简单消息生成类似于以下结果的结果：

```
...
StopWatch 'Lazy-Load Performance': running time (millis) = 38918
-----------------------------------------
ms     %     Task name
-----------------------------------------
02652  007%  Lazy-Load
36266  093%  Eager
...
```

然而，从版本 5.5 开始，所有持久的`MessageGroupStore`实现都提供了基于目标数据库流 API 的`streamMessagesForGroup(Object groupId)`契约。当存储中的组非常大时，这将提高资源利用率。在框架内部，这个新的 API 在[Delayer](./delayer.html#delayer)中使用（例如），当它重新安排启动时的持久化消息时。返回的`Stream<Message<?>>`必须在处理结束时关闭，例如通过`try-with-resources`自动关闭。每当使用`PersistentMessageGroup`时，将其`streamMessages()`委托给`MessageGroupStore.streamMessagesForGroup()`。

#### 消息组条件

从版本 5.5 开始，`MessageGroup`抽象提供了`condition`字符串选项。此选项的值可以是任何可以在稍后进行解析的内容，以便为组做出决定。例如，来自[相关消息处理程序](./aggregator.html#aggregator-api)的`ReleaseStrategy`可能会从组中查询此属性，而不是迭代组中的所有消息。`MessageGroupStore`公开了`setGroupCondition(Object groupId, String condition)`API。为此，在`AbstractCorrelatingMessageHandler`中添加了`setGroupConditionSupplier(BiFunction<Message<?>, String, String>)`选项。此函数是在将每个消息添加到该组以及该组的现有条件之后，针对每个消息进行求值的。该实现可以决定返回一个新的值、现有的值，或者将目标条件重置为`null`。`condition`的值可以是 JSON、SPEL 表达式、数字或任何可以序列化为字符串并在之后进行解析的内容。例如，来自[文件聚合器](./file.html#file-aggregator)组件的`FileMarkerReleaseStrategy`将一个条件从`FileHeaders.LINE_COUNT`消息的`FileSplitter.FileMarker.Mark.END`头中填充到一个组中，并从其`canRelease()`中与该组中的值进行比较，并与之进行协商。这样，它就不会迭代组中的所有消息来查找带有`FileHeaders.LINE_COUNT`头的`FileSplitter.FileMarker.Mark.END`消息。它还允许结束标记在所有其他记录之前到达聚合器；例如，在多线程环境中处理文件时。

此外，为了方便配置，引入了`GroupConditionProvider`契约。`AbstractCorrelatingMessageHandler`检查所提供的`ReleaseStrategy`是否实现了该接口，并提取了用于组条件评估逻辑的`conditionSupplier`。

### 元数据存储

许多外部系统、服务或资源不是事务性的（Twitter、RSS、文件系统等），并且没有任何能力将数据标记为已读。此外，有时，你可能需要在一些集成解决方案中实现 Enterprise 集成模式[幂等接收机](https://www.enterpriseintegrationpatterns.com/IdempotentReceiver.html)。为了实现这一目标并在下一次与外部系统交互之前存储端点的一些先前状态或处理下一次消息， Spring 集成提供了元数据存储组件作为`org.springframework.integration.metadata.MetadataStore`接口的一种实现方式，具有一般的键值契约。

元数据存储旨在存储各种类型的通用元数据（例如，已处理的最后一个提要条目的发布日期），以帮助提要适配器等组件处理重复数据。如果组件没有直接提供对`MetadataStore`的引用，则用于定位元数据存储的算法如下：首先，在应用程序上下文中查找具有`metadataStore`ID 的 Bean。如果找到了，就使用它。否则，创建一个`SimpleMetadataStore`的新实例，这是一个内存中的实现，它只在当前运行的应用程序上下文的生命周期内持久化元数据。这意味着，在重新启动时，你可能会以重复的条目结束。

如果需要在应用程序上下文重新启动之间持久化元数据，则框架提供以下持久化`MetadataStores`:

* `PropertiesPersistingMetadataStore`

* [Gemfire 元数据存储](./gemfire.html#gemfire-metadata-store)

* [JDBC 元数据存储](./jdbc.html#jdbc-metadata-store)

* [MongoDB 元数据存储](./mongodb.html#mongodb-metadata-store)

* [Redis 元数据存储](./redis.html#redis-metadata-store)

* [ZooKeeper 元数据存储](./zookeeper.html#zk-metadata-store)

`PropertiesPersistingMetadataStore`由一个属性文件和一个[`PropertiesPersister`]（https://DOCS. Spring.io/ Spring/DOCS/current/javadoc-api/org/springframework/util/propertiespersister.html）支持。

默认情况下，它仅在应用程序上下文正常关闭时保持状态。它实现了`Flushable`，这样你就可以通过调用`flush()`随意地持久化状态。下面的示例展示了如何使用 XML 配置“PropertiesPersistingMetaDataStore”：

```
<bean id="metadataStore"
    class="org.springframework.integration.metadata.PropertiesPersistingMetadataStore"/>
```

或者，你可以提供你自己的`MetadataStore`接口的实现（例如，`JdbcMetadataStore`），并将其配置为应用程序上下文中的 Bean。

从版本 4.0 开始，`SimpleMetadataStore`，`PropertiesPersistingMetadataStore`，和`RedisMetadataStore`实现`ConcurrentMetadataStore`。它们提供了原子更新，并且可以跨多个组件或应用程序实例使用。

#### 幂等接收器和元数据存储

当需要过滤已处理的传入消息时，元数据存储对于实现 EIP[幂等接收机](https://www.enterpriseintegrationpatterns.com/IdempotentReceiver.html)模式非常有用，并且可以丢弃它或在丢弃时执行其他逻辑。下面的配置展示了如何这样做的示例：

```
<int:filter input-channel="serviceChannel"
			output-channel="idempotentServiceChannel"
			discard-channel="discardChannel"
			expression="@metadataStore.get(headers.businessKey) == null"/>

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

<int:outbound-channel-adapter channel="idempotentServiceChannel"
                              expression="@metadataStore.put(headers.businessKey, '')"/>

<int:service-activator input-channel="idempotentServiceChannel" ref="service"/>
```

幂等条目的`value`可能是一个到期日期，在此日期之后，某个预定的收割者应该将该条目从元数据存储中删除。

另见[幂等接收机 Enterprise 集成模式](./handler-advice.html#idempotent-receiver)。

#### `MetadataStoreListener`

一些元数据存储（目前只有 ZooKeeper）支持注册侦听器，以便在项目更改时接收事件，如下例所示：

```
public interface MetadataStoreListener {

	void onAdd(String key, String value);

	void onRemove(String key, String oldValue);

	void onUpdate(String key, String newValue);
}
```

有关更多信息，请参见[Javadoc](https://docs.spring.io/spring-integration/api/org/springframework/integration/metadata/MetadataStoreListenerAdapter.html)。如果你只对事件的一个子集感兴趣，那么`MetadataStoreListenerAdapter`可以被子类。

### 控制总线

正如[*Enterprise 整合模式 *](https://www.enterpriseintegrationpatterns.com/)一书中所描述的，控制总线背后的思想是，可以使用与“应用程序级”消息传递相同的消息传递系统来监视和管理框架内的组件。在 Spring 集成中，我们构建在上面描述的适配器的基础上，以便你可以发送消息作为调用公开操作的一种方式。

下面的示例展示了如何使用 XML 配置控制总线：

```
<int:control-bus input-channel="operationChannel"/>
```

控制总线有一个输入通道，可以访问该通道来调用应用程序上下文中的 bean 上的操作。它还具有服务激活端点的所有公共属性。例如，如果操作的结果具有要发送到下游通道的返回值，则可以指定输出通道。

控制总线以 Spring 表达式语言表达式的形式在输入通道上运行消息。它接收一条消息，将正文编译为表达式，添加一些上下文，然后运行它。默认上下文支持用`@ManagedAttribute`或`@ManagedOperation`注释的任何方法。它还支持 Spring 的`Lifecycle`接口上的方法（以及自版本 5.2 以来的`Pausable`扩展），并且支持用于配置 Spring 的`TaskExecutor`和`TaskScheduler`实现的几种方法。确保你自己的方法对控制总线可用的最简单的方法是使用`@ManagedAttribute`或`@ManagedOperation`注释。由于这些注释也用于向 JMX MBean 注册中心公开方法，因此它们提供了一个方便的副产品：通常，你希望向控制总线公开的相同类型的操作在通过 JMX 公开时是合理的。应用程序上下文中任何特定实例的解析都是用典型的 SPEL 语法实现的。要做到这一点，请为 Bean 名称提供带有 bean 的 SPEL 前缀（`@`）。例如，要在 Spring  Bean 上执行方法，客户端可以向操作通道发送如下消息：

```
Message operation = MessageBuilder.withPayload("@myServiceBean.shutdown()").build();
operationChannel.send(operation)
```

表达式的上下文的根是`Message`本身，因此你还可以访问`payload`和`headers`作为表达式中的变量。这与 Spring 集成端点中的所有其他表达式支持一致。

使用 Java 注释，你可以将控制总线配置如下：

```
@Bean
@ServiceActivator(inputChannel = "operationChannel")
public ExpressionControlBusFactoryBean controlBus() {
    return new ExpressionControlBusFactoryBean();
}
```

类似地，你可以按以下方式配置 Java DSL 流定义：

```
@Bean
public IntegrationFlow controlBusFlow() {
    return IntegrationFlows.from("controlBus")
              .controlBus()
              .get();
}
```

如果你更喜欢使用带有自动`DirectChannel`创建的 lambdas，则可以按以下方式创建控制总线：

```
@Bean
public IntegrationFlow controlBus() {
    return IntegrationFlowDefinition::controlBus;
}
```

在这种情况下，信道被命名为`controlBus.input`。

### 有序关机

如“[MBean 出口商](./jmx.html#jmx-mbean-exporter)”中所述，MBean Exporter 提供了一个名为`stopActiveComponents`的 JMX 操作，该操作用于以有序的方式停止应用程序。该操作有一个`Long`参数。该参数指示操作等待多长时间（以毫秒为单位）以允许飞行中的消息完成。该行动的工作方式如下：

1. 在所有实现`OrderlyShutdownCapable`的 bean 上调用`beforeShutdown()`。

   这样做可以让这些组件为关闭做好准备。实现这个接口的组件的例子，以及它们对这个调用所做的工作，包括 JMS 和 AMQP 消息驱动的适配器，这些适配器停止它们的侦听器容器，TCP 服务器连接工厂停止接受新连接（同时保持现有连接打开），TCP 入站端点丢弃（记录）接收到的任何新消息，和 HTTP 入站端点，对于任何新请求返回`503 - Service Unavailable`。

2. 停止任何活动通道，如 JMS 或 AMQP 支持的通道。

3. 停止所有`MessageSource`实例。

4. 停止所有入站`MessageProducer`s（不是`OrderlyShutdownCapable`）。

5. 等待剩余的任何时间，如传递给操作的`Long`参数的值所定义的。

   这样做可以让任何飞行中的信息完成它们的旅程。因此，在调用此操作时选择一个适当的超时是很重要的。

6. 在所有`OrderlyShutdownCapable`组件上调用`afterShutdown()`。

   这样做可以让这些组件执行最后的关闭任务（例如，关闭所有打开的套接字）。

正如[有序停机管理运行](./jmx.html#jmx-mbean-shutdown)中所讨论的，可以通过使用 JMX 调用此操作。如果希望以编程方式调用该方法，则需要注入或以其他方式获得对`IntegrationMBeanExporter`的引用。如果在`<int-jmx:mbean-export/>`定义中没有提供`id`属性，则 Bean 具有生成的名称。如果同一 JVM（`MBeanServer`）中存在多个 Spring 集成上下文，则该名称包含一个随机组件，以避免`ObjectName`冲突。

出于这个原因，如果你希望以编程方式调用该方法，我们建议你为 exporter 提供一个`id`属性，以便你可以在应用程序上下文中轻松地访问它。

最后，可以使用`<control-bus>`元素调用该操作。有关详细信息，请参见[monitoring Spring Integration sample application](https://github.com/spring-projects/spring-integration-samples/tree/main/intermediate/monitoring)。

|   |前面描述的算法在版本 4.1 中得到了改进。<br/>以前，所有的任务执行器和调度程序都被停止。<br/>这可能导致`QueueChannel`实例中的中间流消息保持不变。<br/>现在关闭将使 pollers 继续运行，以使这些消息被耗尽并得到处理。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 积分图

从版本 4.3 开始， Spring 集成提供了对应用程序运行时对象模型的访问，其中可以选择包括组件指标。它以图形的形式公开，可用于可视化集成应用程序的当前状态。`o.s.i.support.management.graph`包包含收集、构建和呈现 Spring 集成组件的运行时状态作为单个树状`Graph`对象所需的所有类。应该将`IntegrationGraphServer`声明为用于构建、检索和刷新`Graph`对象的 Bean。结果`Graph`对象可以序列化为任何格式，尽管 JSON 在客户端解析和表示时非常灵活和方便。 Spring 只包含缺省组件的集成应用程序将公开如下图：

```
{
  "contentDescriptor" : {
    "providerVersion" : "5.5.9",
    "providerFormatVersion" : 1.2,
    "provider" : "spring-integration",
    "name" : "myAppName:1.0"
  },
  "nodes" : [ {
    "nodeId" : 1,
    "componentType" : "null-channel",
    "integrationPatternType" : "null_channel",
    "integrationPatternCategory" : "messaging_channel",
    "properties" : { },
    "sendTimers" : {
      "successes" : {
        "count" : 1,
        "mean" : 0.0,
        "max" : 0.0
      },
      "failures" : {
        "count" : 0,
        "mean" : 0.0,
        "max" : 0.0
      }
    },
    "receiveCounters" : {
      "successes" : 0,
      "failures" : 0
    },
    "name" : "nullChannel"
  }, {
    "nodeId" : 2,
    "componentType" : "publish-subscribe-channel",
    "integrationPatternType" : "publish_subscribe_channel",
    "integrationPatternCategory" : "messaging_channel",
    "properties" : { },
    "sendTimers" : {
      "successes" : {
        "count" : 1,
        "mean" : 7.807002,
        "max" : 7.807002
      },
      "failures" : {
        "count" : 0,
        "mean" : 0.0,
        "max" : 0.0
      }
    },
    "name" : "errorChannel"
  }, {
    "nodeId" : 3,
    "componentType" : "logging-channel-adapter",
    "integrationPatternType" : "outbound_channel_adapter",
    "integrationPatternCategory" : "messaging_endpoint",
    "properties" : { },
    "output" : null,
    "input" : "errorChannel",
    "sendTimers" : {
      "successes" : {
        "count" : 1,
        "mean" : 6.742722,
        "max" : 6.742722
      },
      "failures" : {
        "count" : 0,
        "mean" : 0.0,
        "max" : 0.0
      }
    },
    "name" : "errorLogger"
  } ],
  "links" : [ {
    "from" : 2,
    "to" : 3,
    "type" : "input"
  } ]
}
```

|   |版本 5.2 不赞成使用旧版度量标准，而使用[度量管理](./metrics.html#metrics-management).<br/>所讨论的千分尺。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在前面的示例中，图由三个顶级元素组成。

`contentDescriptor`图元素包含有关提供数据的应用程序的一般信息。可以在`IntegrationGraphServer` Bean 或`spring.application.name`应用程序上下文环境属性中定制`name`。框架提供了其他属性，让你将类似的模型与其他来源区分开来。

`links`图元素表示来自`nodes`图元素的节点之间的连接，因此表示源 Spring 集成应用程序中的集成组件之间的连接。例如，从一个`MessageChannel`到一个`EventDrivenConsumer`，加上一些`MessageHandler`，或者从一个`AbstractReplyProducingMessageHandler`到一个`MessageChannel`。为了方便起见并让你确定链接的目的，该模型包含`type`属性。可能的类型有：

* `input`:标识从`MessageChannel`到端点的方向，`inputChannel`，或`requestChannel`属性

* `output`:从`MessageHandler`，`MessageProducer`，或`SourcePollingChannelAdapter`到`MessageChannel`的方向，通过`outputChannel`或`replyChannel`属性

* `error`:从`MessageHandler`on`PollingConsumer`或`MessageProducer`or`SourcePollingChannelAdapter`到`MessageChannel`通过`errorChannel`属性；

* `discard`:通过`errorChannel`属性从`DiscardingMessageHandler`（如`MessageFilter`）到`MessageChannel`。

* `route`:从`AbstractMappingMessageRouter`（如`HeaderValueRouter`）到`MessageChannel`。类似于`output`，但在运行时确定。可以是已配置的通道映射或动态解析的通道.为此，路由器通常只保留多达 100 个动态路由，但是你可以通过设置`dynamicChannelLimit`属性来修改该值。

来自此元素的信息可以由可视化工具用于呈现来自`nodes`图元素的节点之间的连接，其中`from`和`to`数字表示来自链接节点的`nodeId`属性的值。例如，`link`元素可用于确定目标节点上的适当`port`。

下面的“文本图像”显示了类型之间的关系：

```
              +---(discard)
              |
         +----o----+
         |         |
         |         |
         |         |
(input)--o         o---(output)
         |         |
         |         |
         |         |
         +----o----+
              |
              +---(error)
```

`nodes`图元素可能是最有趣的，因为它的元素不仅包含运行时组件及其`componentType`实例和`name`值，而且还可以选择性地包含组件公开的度量。节点元素包含各种属性，这些属性通常是不言自明的。例如，基于表达式的组件包括`expression`属性，该属性包含组件的主表达式字符串。要启用度量，可以将`@EnableIntegrationManagement`添加到`@Configuration`类中，或者将`<int:management/>`元素添加到 XML 配置中。有关完整信息，请参见[度量和管理](./metrics.html#metrics-management)。

`nodeId`表示一个唯一的增量标识符，可以让你区分一个组件和另一个组件。在`links`元素中，它也用于表示此组件与其他组件（如果有的话）的关系（连接）。`input`和`output`属性用于`inputChannel`和`outputChannel`属性的`AbstractEndpoint`，`MessageHandler`，`SourcePollingChannelAdapter`，或`MessageProducerSupport`。有关更多信息，请参见下一节。

从版本 5.1 开始，`IntegrationGraphServer`对于特定的`NamedComponent`，在`IntegrationNode`上的附加属性的总体接受`Function<NamedComponent, Map<String, Object>> additionalPropertiesCallback`。例如，你可以将`SmartLifecycle``autoStartup`和`running`属性公开到目标图形中：

```
server.setAdditionalPropertiesCallback(namedComponent -> {
            Map<String, Object> properties = null;
            if (namedComponent instanceof SmartLifecycle) {
                SmartLifecycle smartLifecycle = (SmartLifecycle) namedComponent;
                properties = new HashMap<>();
                properties.put("auto-startup", smartLifecycle.isAutoStartup());
                properties.put("running", smartLifecycle.isRunning());
            }
            return properties;
        });
```

#### 图运行时模型

Spring 集成组件具有不同级别的复杂性。例如，任何被调查者`MessageSource`还具有一个`SourcePollingChannelAdapter`和一个`MessageChannel`，从源数据定期向其发送消息。其他组件可能是中间件请求-回复组件（例如`JmsOutboundGateway`），其使用`AbstractEndpoint`来订阅（或轮询）用于消息的`requestChannel`（`input`），以及用于生成向下游发送的回复消息的`replyChannel`（`output`）。同时，任何`MessageProducerSupport`实现（例如`ApplicationEventListeningMessageProducer`）都会封装一些源协议侦听逻辑并将消息发送到`outputChannel`。

在图中， Spring 积分组件通过使用`IntegrationNode`类层次结构来表示，你可以在`o.s.i.support.management.graph`包中找到它。例如，对于`AggregatingMessageHandler`可以使用`ErrorCapableDiscardingMessageHandlerNode`（因为它有一个`discardChannel`选项），并且在使用`PollableChannel`时可以产生错误。另一个例子是`CompositeMessageHandlerNode`—当通过使用`EventDrivenConsumer`订阅`SubscribableChannel`时，用于`MessageHandlerChain`。

|   |`@MessagingGateway`（参见[消息传递网关](./gateway.html#gateway)）为其每个方法提供节点，其中`name`属性基于网关的 Bean 名称和简短的方法签名。<br/>考虑以下网关示例：|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

```
@MessagingGateway(defaultRequestChannel = "four")
public interface Gate {

	void foo(String foo);

	void foo(Integer foo);

	void bar(String bar);

}
```

前面的网关产生的节点类似于以下内容：

```
{
  "nodeId" : 10,
  "name" : "gate.bar(class java.lang.String)",
  "stats" : null,
  "componentType" : "gateway",
  "integrationPatternType" : "gateway",
  "integrationPatternCategory" : "messaging_endpoint",
  "output" : "four",
  "errors" : null
},
{
  "nodeId" : 11,
  "name" : "gate.foo(class java.lang.String)",
  "stats" : null,
  "componentType" : "gateway",
  "integrationPatternType" : "gateway",
  "integrationPatternCategory" : "messaging_endpoint",
  "output" : "four",
  "errors" : null
},
{
  "nodeId" : 12,
  "name" : "gate.foo(class java.lang.Integer)",
  "stats" : null,
  "componentType" : "gateway",
  "integrationPatternType" : "gateway",
  "integrationPatternCategory" : "messaging_endpoint",
  "output" : "four",
  "errors" : null
}
```

你可以使用这个`IntegrationNode`层次结构在客户端解析图形模型，以及理解一般的 Spring 集成运行时行为。有关更多信息，请参见[编程技巧和技巧](./overview.html#programming-tips)。

版本 5.3 引入了一个`IntegrationPattern`抽象和所有开箱即用的组件，它们代表一个 Enterprise 集成模式，实现了这个抽象，并提供了一个`IntegrationPatternType`enum 值。对于目标应用程序中的某些分类逻辑来说，这些信息可能是有用的，或者，当暴露在图形节点中时，UI 可以使用这些信息来确定如何绘制组件。

### 积分图控制器

如果你的应用程序是基于 Web 的（或构建在带有嵌入式 Web 容器的 Spring 启动之上），并且 Spring 集成 HTTP 或 WebFlux 模块（分别参见和）存在于 Classpath 上，你可以使用`IntegrationGraphController`将`IntegrationGraphServer`功能公开为 REST 服务。为此，`@EnableIntegrationGraphController`和`@Configuration`类注释和`<int-http:graph-controller/>`XML 元素在 HTTP 模块中可用。与`@EnableWebMvc`注释（或用于 XML 定义的`<mvc:annotation-driven/>`）一起，此配置注册了`IntegrationGraphController``@RestController`，其中其`@RequestMapping.path`可以在`@EnableIntegrationGraphController`注释或`<int-http:graph-controller/>`元素上进行配置。默认路径是`/integration`。

`IntegrationGraphController``@RestController`提供以下服务：

* `@GetMapping(name = "getGraph")`:检索自上次`IntegrationGraphServer`刷新后 Spring 集成组件的状态。将`o.s.i.support.management.graph.Graph`作为 REST 服务的`@ResponseBody`返回。

* `@GetMapping(path = "/refresh", name = "refreshGraph")`:为实际运行时状态刷新当前`Graph`，并将其作为 REST 响应返回。没有必要刷新图表以获取度量。当图形被检索时，它们是实时提供的。如果自上次检索图表以来应用程序上下文已被修改，则可以调用刷新。在这种情况下，这个图是完全重建的。

你可以使用 Spring Security 和 Spring MVC 项目提供的标准配置选项和组件，为`IntegrationGraphController`设置安全性和跨源限制。下面的示例实现了这些目标：

```
<mvc:annotation-driven />

<mvc:cors>
	<mvc:mapping path="/myIntegration/**"
				 allowed-origins="http://localhost:9090"
				 allowed-methods="GET" />
</mvc:cors>

<security:http>
    <security:intercept-url pattern="/myIntegration/**" access="ROLE_ADMIN" />
</security:http>

<int-http:graph-controller path="/myIntegration" />
```

下面的示例展示了如何对 Java 配置执行相同的操作：

```
@Configuration
@EnableWebMvc // or @EnableWebFlux
@EnableWebSecurity // or @EnableWebFluxSecurity
@EnableIntegration
@EnableIntegrationGraphController(path = "/testIntegration", allowedOrigins="http://localhost:9090")
public class IntegrationConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
	    http
            .authorizeRequests()
               .antMatchers("/testIntegration/**").hasRole("ADMIN")
            // ...
            .formLogin();
    }

    //...

}
```

注意，为了方便起见，`@EnableIntegrationGraphController`注释提供了一个`allowedOrigins`属性。这提供了`GET`对`path`的访问。为了更复杂，你可以使用标准的 Spring MVC 机制来配置 CORS 映射。