Dynamic Routers
Spring Integration 为常见的基于内容的路由用例提供了许多不同的路由器配置,以及实现自定义路由器作为 POJO 的选项。例如,PayloadTypeRouter
提供了一种简单的方法来配置一个根据传入消息的有效负载类型计算信道的路由器,而 HeaderValueRouter
在配置一个通过计算特定消息 Header 的值来计算信道的路由器时提供了同样的便利。还有一些基于表达式的(SpEL)路由器,其信道是根据计算表达式的结果决定的。所有这些类型的路由器都表现出一些动态特性。
Spring Integration provides quite a few different router configurations for common content-based routing use cases as well as the option of implementing custom routers as POJOs.
For example, PayloadTypeRouter
provides a simple way to configure a router that computes channels based on the payload type of the incoming message while HeaderValueRouter
provides the same convenience in configuring a router that computes channels by evaluating the value of a particular message Header.
There are also expression-based (SpEL) routers, in which the channel is determined based on evaluating an expression.
All of these type of routers exhibit some dynamic characteristics.
然而,这些路由器都需要静态配置。即使是在基于表达式的路由器的情况下,表达式本身也被定义为路由器配置的一部分,这意味着对同一值执行的同一表达式始终会计算出同一信道。在大多数情况下,这是可以接受的,因为此类路由是定义明确的,因而可以预测。但有时我们需要动态更改路由器配置,以便消息流可以被路由到不同的信道。
However, these routers all require static configuration. Even in the case of expression-based routers, the expression itself is defined as part of the router configuration, which means that the same expression operating on the same value always results in the computation of the same channel. This is acceptable in most cases, since such routes are well-defined and therefore predictable. But there are times when we need to change router configurations dynamically so that message flows may be routed to a different channel.
例如,您可能想要将系统的某个部分关闭以进行维护,并将消息暂时重新路由到不同的消息流中。另一个示例是,您可能希望通过为处理更具体类型的 java.lang.Number
添加另一个路由来为消息流引入更多的粒度(在 PayloadTypeRouter
的情况下)。
For example, you might want to bring down some part of your system for maintenance and temporarily re-reroute messages to a different message flow.
As another example, you may want to introduce more granularity to your message flow by adding another route to handle a more concrete type of java.lang.Number
(in the case of PayloadTypeRouter
).
不幸的是,对于静态路由配置来说,为了实现这两个目标,您将不得不关闭整个应用程序,更改路由器的配置(更改路由),然后重新启动应用程序。显然,这不是任何人希望得到的解决方案。
Unfortunately, with static router configuration to accomplish either of those goals, you would have to bring down your entire application, change the configuration of the router (change routes), and bring the application back up. This is obviously not a solution anyone wants.
dynamic router 模式描述了您可以在不关闭系统或各个路由器的情况下更改或动态配置路由器的机制。
The dynamic router pattern describes the mechanisms by which you can change or configure routers dynamically without bringing down the system or individual routers.
在我们了解 Spring Integration 如何支持动态路由的具体内容之前,我们需要考虑路由器的典型流程:
Before we get into the specifics of how Spring Integration supports dynamic routing, we need to consider the typical flow of a router:
-
Compute a channel identifier, which is a value calculated by the router once it receives the message. Typically, it is a String or an instance of the actual
MessageChannel
. -
Resolve the channel identifier to a channel name. We describe specifics of this process later in this section.
-
Resolve the channel name to the actual
MessageChannel
如果步骤 1 会导致 MessageChannel
的实际实例,那么在动态路由方面不能做很多事情,因为 MessageChannel
是任何路由器工作的最终产品。但是,如果第一步导致的通道标识符不是 MessageChannel
的实例,您有许多可能的方法来影响派生 MessageChannel
的过程。请考虑以下有效负载类型路由器的示例:
There is not much that can be done with regard to dynamic routing if Step 1 results in the actual instance of the MessageChannel
, because the MessageChannel
is the final product of any router’s job.
However, if the first step results in a channel identifier that is not an instance of MessageChannel
, you have quite a few possible ways to influence the process of deriving the MessageChannel
.
Consider the following example of a payload type router:
<int:payload-type-router input-channel="routingChannel">
<int:mapping type="java.lang.String" channel="channel1" />
<int:mapping type="java.lang.Integer" channel="channel2" />
</int:payload-type-router>
在有效负载类型路由器的上下文中,前面提到的三个步骤将实现如下:
Within the context of a payload type router, the three steps mentioned earlier would be realized as follows:
-
Compute a channel identifier that is the fully qualified name of the payload type (for example,
java.lang.String
). -
Resolve the channel identifier to a channel name, where the result of the previous step is used to select the appropriate value from the payload type mapping defined in the
mapping
element. -
Resolve the channel name to the actual instance of the
MessageChannel
as a reference to a bean within the application context (which is hopefully aMessageChannel
) identified by the result of the previous step.
换句话说,每个步骤都为下一步提供输入,直到流程完成。
In other words, each step feeds the next step until the process completes.
现在考虑标头值路由器的示例:
Now consider an example of a header value router:
<int:header-value-router input-channel="inputChannel" header-name="testHeader">
<int:mapping value="foo" channel="fooChannel" />
<int:mapping value="bar" channel="barChannel" />
</int:header-value-router>
现在我们可以考虑标头值路由器的三个步骤如何工作:
Now we can consider how the three steps work for a header value router:
-
Compute a channel identifier that is the value of the header identified by the
header-name
attribute. -
Resolve the channel identifier to a channel name, where the result of the previous step is used to select the appropriate value from the general mapping defined in the
mapping
element. -
Resolve the channel name to the actual instance of the
MessageChannel
as a reference to a bean within the application context (which is hopefully aMessageChannel
) identified by the result of the previous step.
前两种不同路由器类型的配置看起来几乎相同。但是,如果您查看 HeaderValueRouter
的备用配置,我们会清楚地看到没有 mapping
子元素,如下表所示:
The preceding two configurations of two different router types look almost identical.
However, if you look at the alternate configuration of the HeaderValueRouter
we clearly see that there is no mapping
sub element, as the following listing shows:
<int:header-value-router input-channel="inputChannel" header-name="testHeader"/>
但是,该配置仍然完全有效。因此,自然而然的问题是第二步中的映射是什么?
However, the configuration is still perfectly valid. So the natural question is what about the mapping in the second step?
现在第二步是可选的。如果未定义 mapping
,则在第一步中计算出的通道标识符值将自动被视为 channel name
,该值现在解析为实际的 MessageChannel
,如第三步所示。这也意味着第二步是为路由器提供动态特性的关键步骤之一,因为它引入了一个让您更改通道标识符解析为通道名称的方式的过程,从而影响从初始通道标识符确定 MessageChannel
最终实例的过程。
The second step is now optional.
If mapping
is not defined, then the channel identifier value computed in the first step is automatically treated as the channel name
, which is now resolved to the actual MessageChannel
, as in the third step.
What it also means is that the second step is one of the key steps to providing dynamic characteristics to the routers, since it introduces a process that lets you change the way channel identifier resolves to the channel name, thus influencing the process of determining the final instance of the MessageChannel
from the initial channel identifier.
例如,在前面的配置中,假设 testHeader
值为“kermit”,它现在是一个通道标识符(第一步)。由于此路由器中没有映射,因此无法将此通道标识符解析为通道名称(第二步),并且此通道标识符现在被视为通道名称。但是,如果有一个映射但针对不同的值呢?最终结果仍然相同,因为如果无法通过将通道标识符解析为通道名称的过程确定新值,则通道标识符将变为通道名称。
For example, in the preceding configuration, assume that the testHeader
value is 'kermit', which is now a channel identifier (the first step).
Since there is no mapping in this router, resolving this channel identifier to a channel name (the second step) is impossible and this channel identifier is now treated as the channel name.
However, what if there was a mapping but for a different value?
The end result would still be the same, because, if a new value cannot be determined through the process of resolving the channel identifier to a channel name, the channel identifier becomes the channel name.
剩下的就是第三步将通道名称(“kermit”)解析为由该名称标识的 MessageChannel
的实际实例。基本上涉及提供的名称的 bean 查找。现在,所有包含标头-值对 testHeader=kermit
的消息都将路由到 bean 名称(其 id
)为“kermit”的 MessageChannel
。
All that is left is for the third step to resolve the channel name ('kermit') to an actual instance of the MessageChannel
identified by this name.
That basically involves a bean lookup for the provided name.
Now all messages that contain the header-value pair as testHeader=kermit
are going to be routed to a MessageChannel
whose bean name (its id
) is 'kermit'.
但是,如果您想将这些消息路由到“simpson”通道,该怎么办?显然,更改静态配置是有用的,但这样做还需要关闭系统。但是,如果您已经访问了通道标识符映射,则可以引入一个新映射,其中标头-值对现在为 kermit=simpson
,从而让第二步将“kermit”视为通道标识符,同时将其解析为“simpson”作为通道名称。
But what if you want to route these messages to the 'simpson' channel? Obviously changing a static configuration works, but doing so also requires bringing your system down.
However, if you have had access to the channel identifier map, you could introduce a new mapping where the header-value pair is now kermit=simpson
, thus letting the second step treat 'kermit' as a channel identifier while resolving it to 'simpson' as the channel name.
显然,这同样适用于 PayloadTypeRouter
,您现在可以重新映射或删除特定有效负载类型映射。事实上,它适用于所有其他路由器,包括基于表达式的路由器,因为它们计算的值现在有机会通过第二步解析为实际的 channel name
。
The same obviously applies for PayloadTypeRouter
, where you can now remap or remove a particular payload type mapping.
In fact, it applies to every other router, including expression-based routers, since their computed values now have a chance to go through the second step to be resolved to the actual channel name
.
任何作为 AbstractMappingMessageRouter
子类的路由器(其中包括大多数框架定义的路由器)都是动态路由器,因为 channelMapping
定义在 AbstractMappingMessageRouter
层级中。该映射的 setter 方法与 'setChannelMapping' 和 'removeChannelMapping' 方法一起公开为公共方法。只要您有对路由器本身的引用,就可以在运行时更改、添加和删除路由器映射。这也意味着您可以通过 JMX(参见 JMX Support)或 Spring Integration 控制总线(参见 Control Bus)功能公开这些相同的配置选项。
Any router that is a subclass of the AbstractMappingMessageRouter
(which includes most framework-defined routers) is a dynamic router, because the channelMapping
is defined at the AbstractMappingMessageRouter
level.
That map’s setter method is exposed as a public method along with the 'setChannelMapping' and 'removeChannelMapping' methods.
These let you change, add, and remove router mappings at runtime, as long as you have a reference to the router itself.
It also means that you could expose these same configuration options through JMX (see JMX Support) or the Spring Integration control bus (see Control Bus) functionality.
将信道键用作信道名称具有灵活性且方便。但是,如果你不信任消息创建者,恶意行为者(具有系统的知识)可以创建路由到意外信道的消息。例如,如果将密钥设置为路由器输入信道的信道名称,则这样一条消息将被路由回路由器,最终导致堆栈溢出错误。因此,你可能希望禁用此功能(设置 channelKeyFallback
属性为 false
),并在需要时更改映射。
Falling back to the channel key as the channel name is flexible and convenient.
However, if you don’t trust the message creator, a malicious actor (who has knowledge of the system) could create a message that is routed to an unexpected channel.
For example, if the key is set to the channel name of the router’s input channel, such a message would be routed back to the router, eventually resulting in a stack overflow error.
You may therefore wish to disable this feature (set the channelKeyFallback
property to false
), and change the mappings instead if needed.
Manage Router Mappings using the Control Bus
管理路由器映射的一种方法是通过 control bus 模式,它公开一个控制通道,您可以向该通道发送控制消息以管理和监视 Spring Integration 组件(包括路由器)。
One way to manage the router mappings is through the control bus pattern, which exposes a control channel to which you can send control messages to manage and monitor Spring Integration components, including routers.
关于控制总线的更多信息,请参阅 Control Bus。 |
For more information about the control bus, see Control Bus. |
通常,您会发送一条控制消息来要求对特定的受管理组件(例如路由器)调用特定操作。以下受管理操作(方法)特定于更改路由器解析流程:
Typically, you would send a control message asking to invoke a particular operation on a particular managed component (such as a router). The following managed operations (methods) are specific to changing the router resolution process:
-
public void setChannelMapping(String key, String channelName)
: Lets you add a new or modify an existing mapping betweenchannel identifier
andchannel name
-
public void removeChannelMapping(String key)
: Lets you remove a particular channel mapping, thus disconnecting the relationship betweenchannel identifier
andchannel name
请注意,这些方法可用于进行简单的更改(例如,更新单一路由或添加或删除路由)。但是,如果您想删除一条路由并添加另一条路由,则这些更新不是原子的。这意味着路由表在两次更新之间可能处于不确定的状态。从 4.0 版开始,您现在可以使用控制总线以原子方式更新整个路由表。以下方法允许您这样做:
Note that these methods can be used for simple changes (such as updating a single route or adding or removing a route). However, if you want to remove one route and add another, the updates are not atomic. This means that the routing table may be in an indeterminate state between the updates. Starting with version 4.0, you can now use the control bus to update the entire routing table atomically. The following methods let you do so:
-
public Map<String, String>getChannelMappings()
: Returns the current mappings. -
public void replaceChannelMappings(Properties channelMappings)
: Updates the mappings. Note that thechannelMappings
parameter is aProperties
object. This arrangement lets a control bus command use the built-inStringToPropertiesConverter
, as the following example shows:
"@'router.handler'.replaceChannelMappings('foo=qux \n baz=bar')"
请注意,每个映射都用一个换行符(\n
)分隔。为了出于类型安全性考虑对映射进行编程上的更改,我们建议您使用 setChannelMappings
方法。replaceChannelMappings
忽略不是 String
对象的键或值。
Note that each mapping is separated by a newline character (\n
).
For programmatic changes to the map, we recommend that you use the setChannelMappings
method, due to type-safety concerns.
replaceChannelMappings
ignores keys or values that are not String
objects.
Manage Router Mappings by Using JMX
您还可以使用 Spring 的 JMX 支持来公开一个路由程序实例,然后使用您 favorite 的 JMX 客户端(例如,JConsole)来管理更改路由程序配置的那些操作(方法)。
You can also use Spring’s JMX support to expose a router instance and then use your favorite JMX client (for example, JConsole) to manage those operations (methods) for changing the router’s configuration.
关于 Spring Integration 的 JMX 支持的更多信息,请参阅 JMX Support。 |
For more information about Spring Integration’s JMX support, see JMX Support. |