diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java index 86508722c0..5eda8fd98b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java @@ -90,6 +90,8 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper globalHeaderExpressions; + private final Map headers; + private final List parameterList; private final MethodArgsMessageMapper argsMapper; @@ -115,9 +117,17 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper headerExpressions, Map globalHeaderExpressions, MethodArgsMessageMapper mapper, MessageBuilderFactory messageBuilderFactory) { + this(method, headerExpressions, globalHeaderExpressions, null, mapper, messageBuilderFactory); + } + + GatewayMethodInboundMessageMapper(Method method, Map headerExpressions, + Map globalHeaderExpressions, Map headers, + MethodArgsMessageMapper mapper, + MessageBuilderFactory messageBuilderFactory) { Assert.notNull(method, "method must not be null"); this.method = method; this.headerExpressions = headerExpressions; + this.headers = headers; this.globalHeaderExpressions = globalHeaderExpressions; this.parameterList = getMethodParameterList(method); this.payloadExpression = parsePayloadExpression(method); @@ -354,6 +364,9 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper headers = null; + // We don't want to eagerly resolve the error channel here + Object errorChannel = this.errorChannel == null ? this.errorChannelName : this.errorChannel; + if (errorChannel != null && method.getReturnType().equals(void.class)) { + headers = new HashMap<>(); + headers.put(MessageHeaders.ERROR_CHANNEL, errorChannel); + } GatewayMethodInboundMessageMapper messageMapper = new GatewayMethodInboundMessageMapper(method, headerExpressions, this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null, - this.argsMapper, this.getMessageBuilderFactory()); + headers, this.argsMapper, this.getMessageBuilderFactory()); if (StringUtils.hasText(payloadExpression)) { messageMapper.setPayloadExpression(payloadExpression); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java index 94a825fcb8..378f2fd5fb 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java @@ -74,6 +74,7 @@ import org.springframework.messaging.PollableChannel; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.support.ChannelInterceptorAdapter; +import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Component; import org.springframework.test.annotation.DirtiesContext; @@ -133,6 +134,7 @@ public class GatewayInterfaceTests { equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.foo(java.lang.String)")); assertThat((Method) message.getHeaders().get("object"), equalTo(fooMethod)); assertThat((String) message.getPayload(), equalTo("hello")); + assertThat(new MessageHeaderAccessor(message).getErrorChannel(), equalTo("errorChannel")); called.set(true); } }; diff --git a/src/reference/asciidoc/gateway.adoc b/src/reference/asciidoc/gateway.adoc index b93ba2e7e8..1461d349e5 100644 --- a/src/reference/asciidoc/gateway.adoc +++ b/src/reference/asciidoc/gateway.adoc @@ -353,6 +353,12 @@ If you provide a one-way flow, then nothing would be sent back to the caller. In the case that you want to completely suppress Exceptions, you can provide a reference to the global "nullChannel" (essentially a /dev/null approach). Finally, as mentioned above, if no "error-channel" is defined at all, then the Exceptions will propagate as usual. +Starting with _version 5.0_, when using a gateway method with a `void` return type (one-way flow), the `error-channel` reference (if provided) is populated in the standard `errorChannel` header of each message sent. +This allows a downstream async flow, based on the standard `ExecutorChannel` configuration (or a `QueueChannel`), to override a default global `errorChannel` exceptions sending behavior. +Previously you had to specify an `errorChannel` header manually via `@GatewayHeader` annotation or `
` sub-element. +The `error-channel` property was ignored for `void` methods with an asynchronous flow; error messages were sent to the default `errorChannel` instead. + + IMPORTANT: Exposing the messaging system via simple POJI Gateways obviously provides benefits, but "hiding" the reality of the underlying messaging system does come at a price so there are certain things you should consider. We want our Java method to return as quickly as possible and not hang for an indefinite amount of time while the caller is waiting on it to return (void, return value, or a thrown Exception). When regular methods are used as a proxies in front of the Messaging system, we have to take into account the potentially asynchronous nature of the underlying messaging. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 8829ab0211..f84853a23b 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -21,3 +21,10 @@ Previously, Spring Integration JMS XML configuration used a default bean name `c It has now been renamed to `jmsConnectionFactory`, which is the bean name used by Spring Boot to auto-configure the JMS Connection Factory bean. If your application is relying on the previous behavior, rename your `connectionFactory` bean to `jmsConnectionFactory`, or specifically configure your components to use your bean using its current name. + +===== Gateway Changes + +The gateway now correctly sets the `errorChannel` header when the gateway method has a `void` return type and an error channel is provided. +Previously, the header was not populated. +This had the effect that synchronous downstream flows (running on the calling thread) would send the exception to the configured channel but an exception on an async downstream flow would be sent to the default `errorChannel` instead. +See <> for more information.