Infrastructure for ReactiveMessageHandler (#3137)

* Infrastructure for ReactiveMessageHandler

We have now a `ReactiveMongoDbStoringMessageHandler` which implements
a `ReactiveMessageHandler`, but not a `MessageHandler` for possible
deferred subscriptions to the returned Reactor type

We don't have a proper application context processing for this
new type of message handlers

* Change a  `ConsumerEndpointFactoryBean` to apply an `MH` and `RMH`
as possible types for handler
* Introduce a `ReactiveMessageHandlerAdapter` to wrap an `RMH`
into a `MH` for synchronous calls in the regular consumer endpoints
* Wrap an `RMH` into a `ReactiveMessageHandlerAdapter` for regular
endpoints and unwrap for `ReactiveStreamsConsumer`
* Add `RMH`-based ctor into `ReactiveStreamsConsumer` for target
reactive streams composition (`flatMap()` on the `RMH`)
* Remove a `DelegatingSubscriber` from the `ReactiveStreamsConsumer`
in favor of direct calls from the `doOnSubscribe()`, `doOnComplete()`
& `doOnNext()`
* Add an `onErrorContinue()` to handle per-message errors, but don't
cancel the whole source `Publisher`
* Use `Disposable` from the `subscribe()` to cancel in the `stop()`
- recommended way in Reactor
* Use `onErrorContinue()` in the `FluxMessageChannel` instead of
`try..catch` in the `doOnNext()` - for possible `onErrorStop()`
in the provided upstream `Publisher`
* Handle `RMH` in the `ServiceActivatorFactoryBean` as a direct handler
as well with wrapping into `ReactiveMessageHandlerAdapter` for return.
The `ConsumerEndpointFactoryBean` extracts an `RMH` from the adapter
for the `ReactiveStreamsConsumer` anyway
* Add XML parsing test for `ReactiveMongoDbStoringMessageHandler`
* Add `log4j-slf4j-impl` for all the test runtime since `slf4j-api`
comes as a transitive dependency from many places

* * Fix conflicts after rebasing to master

* * Fix typo in warn message
* Change `Assert.state()` to `Assert.isTrue()`
for `ConsumerEndpointFactoryBean.setHandler()`

* * Fix `ConsumerEndpointFactoryBean` when reactive and no advice-chain
* Fix race condition in the
`ReactiveMongoDbStoringMessageHandlerTests.testReactiveMongoMessageHandlerFromApplicationContext()`

* * Handle `ReactiveMessageHandler` in Java DSL.
Essentially request a wrapping into `ReactiveMessageHandlerAdapter`.
Describe such a requirements in the `ReactiveMessageHandlerAdapter` JavaDocs
* Some Java DSL test polishing
* Add Java DSL for `ReactiveMongoDbStoringMessageHandler`
* Propagate missed `ApplicationContext` population into an internally
created `ReactiveMongoTemplate` in the `ReactiveMongoDbStoringMessageHandler`
This commit is contained in:
Artem Bilan
2020-01-13 08:41:41 -05:00
committed by Gary Russell
parent e0509cc339
commit d13752b40b
18 changed files with 501 additions and 131 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,14 +78,8 @@ public class FluxMessageChannel extends AbstractMessageChannel
Flux.from(publisher)
.delaySubscription(this.subscribedSignal.filter(Boolean::booleanValue).next())
.publishOn(Schedulers.boundedElastic())
.doOnNext((message) -> {
try {
send(message);
}
catch (Exception e) {
logger.warn("Error during processing event: " + message, e);
}
})
.doOnNext(this::send)
.onErrorContinue((ex, message) -> logger.warn("Error during processing event: " + message, ex))
.subscribe());
}

View File

@@ -42,12 +42,14 @@ import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.endpoint.ReactiveStreamsConsumer;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.ReactiveMessageHandlerAdapter;
import org.springframework.integration.handler.advice.HandleMessageAdvice;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.integration.support.channel.ChannelResolverUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.ReactiveMessageHandler;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.core.DestinationResolver;
import org.springframework.scheduling.TaskScheduler;
@@ -112,11 +114,17 @@ public class ConsumerEndpointFactoryBean
private volatile boolean initialized;
public void setHandler(MessageHandler handler) {
Assert.notNull(handler, "handler must not be null");
public void setHandler(Object handler) {
Assert.isTrue(handler instanceof MessageHandler || handler instanceof ReactiveMessageHandler,
"'handler' must be an instance of 'MessageHandler' or 'ReactiveMessageHandler'");
synchronized (this.handlerMonitor) {
Assert.isNull(this.handler, "handler cannot be overridden");
this.handler = handler;
if (handler instanceof ReactiveMessageHandler) {
this.handler = new ReactiveMessageHandlerAdapter((ReactiveMessageHandler) handler);
}
else {
this.handler = (MessageHandler) handler;
}
}
}
@@ -210,7 +218,12 @@ public class ConsumerEndpointFactoryBean
}
}
adviceChain();
if (!(this.handler instanceof ReactiveMessageHandlerAdapter)) {
adviceChain();
}
else if (!CollectionUtils.isEmpty(this.adviceChain)) {
LOGGER.warn("the advice chain cannot be applied to a 'ReactiveMessageHandler'");
}
if (this.channelResolver == null) {
this.channelResolver = ChannelResolverUtils.getChannelResolver(this.beanFactory);
}
@@ -282,7 +295,13 @@ public class ConsumerEndpointFactoryBean
pollingConsumer(channel);
}
else {
this.endpoint = new ReactiveStreamsConsumer(channel, this.handler);
if (this.handler instanceof ReactiveMessageHandlerAdapter) {
this.endpoint = new ReactiveStreamsConsumer(channel,
((ReactiveMessageHandlerAdapter) this.handler).getDelegate());
}
else {
this.endpoint = new ReactiveStreamsConsumer(channel, this.handler);
}
}
this.endpoint.setBeanName(this.beanName);
this.endpoint.setBeanFactory(this.beanFactory);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,9 +22,11 @@ import org.springframework.expression.Expression;
import org.springframework.integration.handler.AbstractMessageProducingHandler;
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.handler.ReactiveMessageHandlerAdapter;
import org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper;
import org.springframework.integration.handler.ServiceActivatingHandler;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.ReactiveMessageHandler;
import org.springframework.util.StringUtils;
/**
@@ -47,7 +49,7 @@ public class ServiceActivatorFactoryBean extends AbstractStandardMessageHandlerF
@Override
protected MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) {
MessageHandler handler = null;
MessageHandler handler;
handler = createDirectHandlerIfPossible(targetObject, targetMethodName);
if (handler == null) {
handler = configureHandler(
@@ -67,26 +69,30 @@ public class ServiceActivatorFactoryBean extends AbstractStandardMessageHandlerF
*/
protected MessageHandler createDirectHandlerIfPossible(final Object targetObject, String targetMethodName) {
MessageHandler handler = null;
if (targetObject instanceof MessageHandler
&& this.methodIsHandleMessageOrEmpty(targetMethodName)) {
if ((targetObject instanceof MessageHandler || targetObject instanceof ReactiveMessageHandler)
&& methodIsHandleMessageOrEmpty(targetMethodName)) {
if (targetObject instanceof AbstractMessageProducingHandler) {
// should never happen but just return it if it's already an AMPH
return (MessageHandler) targetObject;
}
/*
* Return a reply-producing message handler so that we still get 'produced no reply' messages
* and the super class will inject the advice chain to advise the handler method if needed.
*/
handler = new ReplyProducingMessageHandlerWrapper((MessageHandler) targetObject);
if (targetObject instanceof ReactiveMessageHandler) {
handler = new ReactiveMessageHandlerAdapter((ReactiveMessageHandler) targetObject);
}
else {
/*
* Return a reply-producing message handler so that we still get 'produced no reply' messages
* and the super class will inject the advice chain to advise the handler method if needed.
*/
handler = new ReplyProducingMessageHandlerWrapper((MessageHandler) targetObject);
}
}
return handler;
}
@Override
protected MessageHandler createExpressionEvaluatingHandler(Expression expression) {
ExpressionEvaluatingMessageProcessor<Object> processor = new ExpressionEvaluatingMessageProcessor<Object>(expression);
processor.setBeanFactory(this.getBeanFactory());
ExpressionEvaluatingMessageProcessor<Object> processor = new ExpressionEvaluatingMessageProcessor<>(expression);
processor.setBeanFactory(getBeanFactory());
ServiceActivatingHandler handler = new ServiceActivatingHandler(processor);
handler.setPrimaryExpression(expression);
return this.configureHandler(handler);
@@ -94,7 +100,7 @@ public class ServiceActivatorFactoryBean extends AbstractStandardMessageHandlerF
@Override
protected <T> MessageHandler createMessageProcessingHandler(MessageProcessor<T> processor) {
return this.configureHandler(new ServiceActivatingHandler(processor));
return configureHandler(new ServiceActivatingHandler(processor));
}
protected MessageHandler configureHandler(ServiceActivatingHandler handler) {
@@ -115,7 +121,6 @@ public class ServiceActivatorFactoryBean extends AbstractStandardMessageHandlerF
@Override
protected void postProcessReplyProducer(AbstractMessageProducingHandler handler) {
super.postProcessReplyProducer(handler);
if (this.headers != null) {
handler.setNotPropagatedHeaders(this.headers);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2019 the original author or authors.
* Copyright 2016-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,19 +27,25 @@ import org.springframework.integration.channel.ChannelUtils;
import org.springframework.integration.channel.MessageChannelReactiveUtils;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.handler.ReactiveMessageHandlerAdapter;
import org.springframework.integration.router.MessageRouter;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.ReactiveMessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
import reactor.core.CoreSubscriber;
import reactor.core.Disposable;
import reactor.core.publisher.BaseSubscriber;
import reactor.core.publisher.Flux;
/**
* An {@link AbstractEndpoint} implementation for Reactive Streams subscription into an
* input channel and reactive consumption of messages from that channel.
*
* @author Artem Bilan
*
* @since 5.0
@@ -48,17 +54,22 @@ public class ReactiveStreamsConsumer extends AbstractEndpoint implements Integra
private final MessageChannel inputChannel;
private final MessageHandler handler;
private final Publisher<Message<Object>> publisher;
private final MessageHandler handler;
@Nullable
private final ReactiveMessageHandler reactiveMessageHandler;
@Nullable
private final Subscriber<Message<?>> subscriber;
@Nullable
private final Lifecycle lifecycleDelegate;
private ErrorHandler errorHandler;
private volatile Subscription subscription;
private volatile Disposable subscription;
@SuppressWarnings("unchecked")
public ReactiveStreamsConsumer(MessageChannel inputChannel, MessageHandler messageHandler) {
@@ -68,10 +79,10 @@ public class ReactiveStreamsConsumer extends AbstractEndpoint implements Integra
: new MessageHandlerSubscriber(messageHandler));
}
public ReactiveStreamsConsumer(MessageChannel inputChannel, final Subscriber<Message<?>> subscriber) {
this.inputChannel = inputChannel;
public ReactiveStreamsConsumer(MessageChannel inputChannel, Subscriber<Message<?>> subscriber) {
Assert.notNull(inputChannel, "'inputChannel' must not be null");
Assert.notNull(subscriber, "'subscriber' must not be null");
this.inputChannel = inputChannel;
if (inputChannel instanceof NullChannel && logger.isWarnEnabled()) {
logger.warn("The consuming from the NullChannel does not have any effects: " +
@@ -90,6 +101,24 @@ public class ReactiveStreamsConsumer extends AbstractEndpoint implements Integra
else {
this.handler = this.subscriber::onNext;
}
this.reactiveMessageHandler = null;
}
/**
* Instantiate an endpoint based on the provided {@link MessageChannel} and {@link ReactiveMessageHandler}.
* @param inputChannel the channel to consume in reactive manner.
* @param reactiveMessageHandler the {@link ReactiveMessageHandler} to process messages.
* @since 5.3
*/
public ReactiveStreamsConsumer(MessageChannel inputChannel, ReactiveMessageHandler reactiveMessageHandler) {
Assert.notNull(inputChannel, "'inputChannel' must not be null");
this.inputChannel = inputChannel;
this.handler = new ReactiveMessageHandlerAdapter(reactiveMessageHandler);
this.reactiveMessageHandler = reactiveMessageHandler;
this.publisher = MessageChannelReactiveUtils.toPublisher(inputChannel);
this.subscriber = null;
this.lifecycleDelegate =
reactiveMessageHandler instanceof Lifecycle ? (Lifecycle) reactiveMessageHandler : null;
}
public void setErrorHandler(ErrorHandler errorHandler) {
@@ -132,55 +161,35 @@ public class ReactiveStreamsConsumer extends AbstractEndpoint implements Integra
if (this.lifecycleDelegate != null) {
this.lifecycleDelegate.start();
}
this.publisher.subscribe(new DelegatingSubscriber());
Flux<?> flux = null;
if (this.reactiveMessageHandler != null) {
flux = Flux.from(this.publisher)
.flatMap(this.reactiveMessageHandler::handleMessage);
}
else if (this.subscriber != null) {
flux = Flux.from(this.publisher)
.doOnSubscribe(this.subscriber::onSubscribe)
.doOnComplete(this.subscriber::onComplete)
.doOnNext(this.subscriber::onNext);
}
if (flux != null) {
this.subscription =
flux.onErrorContinue((ex, data) -> this.errorHandler.handleError(ex))
.subscribe();
}
}
@Override
protected void doStop() {
if (this.subscription != null) {
this.subscription.cancel();
this.subscription.dispose();
}
if (this.lifecycleDelegate != null) {
this.lifecycleDelegate.stop();
}
}
private final class DelegatingSubscriber extends BaseSubscriber<Message<?>> {
private final Subscriber<Message<?>> delegate = ReactiveStreamsConsumer.this.subscriber;
DelegatingSubscriber() {
}
@Override
public void hookOnSubscribe(Subscription s) {
ReactiveStreamsConsumer.this.subscription = s;
this.delegate.onSubscribe(s);
}
@Override
public void hookOnNext(Message<?> message) {
try {
this.delegate.onNext(message);
}
catch (Exception e) {
ReactiveStreamsConsumer.this.errorHandler.handleError(e);
hookOnError(e);
}
}
@Override
public void hookOnError(Throwable t) {
this.delegate.onError(t);
}
@Override
public void hookOnComplete() {
this.delegate.onComplete();
}
}
private static final class MessageHandlerSubscriber
implements CoreSubscriber<Message<?>>, Disposable, Lifecycle {

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.handler;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.ReactiveMessageHandler;
import org.springframework.util.Assert;
/**
* A {@link MessageHandler} implementation to adapt a {@link ReactiveMessageHandler}
* for synchronous invocations.
* A subscription to the returned reactive type from {@link ReactiveMessageHandler#handleMessage(Message)}
* call is done directly in the {@link #handleMessage} implementation.
* <p>
* The framework wraps a target {@link ReactiveMessageHandler} into this instance automatically
* for XML and Annotation configuration. For Java DSL it is recommended to wrap for generic usage
* ({@code .handle(MessageHandle)}) or it has to be done in the
* {@link org.springframework.integration.dsl.MessageHandlerSpec}
* implementation for protocol-specif {@link ReactiveMessageHandler}.
* <p>
* The framework unwraps a delegate {@link ReactiveMessageHandler} whenever it can compose
* reactive streams, e.g. {@link org.springframework.integration.endpoint.ReactiveStreamsConsumer}.
*
* @author Artem Bilan
*
* @since 5.3
*
* @see org.springframework.integration.endpoint.ReactiveStreamsConsumer
*/
public class ReactiveMessageHandlerAdapter implements MessageHandler {
private final ReactiveMessageHandler delegate;
/**
* Instantiate based on the provided {@link ReactiveMessageHandler}.
* @param reactiveMessageHandler the {@link ReactiveMessageHandler} to delegate to.
*/
public ReactiveMessageHandlerAdapter(ReactiveMessageHandler reactiveMessageHandler) {
Assert.notNull(reactiveMessageHandler, "'reactiveMessageHandler' must not be null");
this.delegate = reactiveMessageHandler;
}
/**
* Get access to the delegate {@link ReactiveMessageHandler}.
* Typically used in the framework internally in components which can compose reactive streams internally
* and allow us to avoid an explicit {@code subscribe()} call.
* @return the {@link ReactiveMessageHandler} this instance is delegating to.
*/
public ReactiveMessageHandler getDelegate() {
return this.delegate;
}
@Override
public void handleMessage(Message<?> message) throws MessagingException {
this.delegate.handleMessage(message).subscribe();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2019 the original author or authors.
* Copyright 2016-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.reactivestreams.Subscriber;
@@ -49,8 +49,13 @@ import org.springframework.integration.handler.MethodInvokingMessageHandler;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.ReactiveMessageHandler;
import org.springframework.messaging.support.GenericMessage;
import reactor.core.publisher.EmitterProcessor;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
/**
* @author Artem Bilan
*
@@ -261,4 +266,43 @@ public class ReactiveStreamsConsumerTests {
assertThat(result).containsExactly(testMessage, testMessage2, testMessage2);
}
@Test
public void testReactiveStreamsConsumerFluxMessageChannelReactiveMessageHandler() {
FluxMessageChannel testChannel = new FluxMessageChannel();
EmitterProcessor<Message<?>> processor = EmitterProcessor.create(2, false);
ReactiveMessageHandler messageHandler =
m -> {
processor.onNext(m);
return Mono.empty();
};
ReactiveStreamsConsumer reactiveConsumer = new ReactiveStreamsConsumer(testChannel, messageHandler);
reactiveConsumer.setBeanFactory(mock(BeanFactory.class));
reactiveConsumer.afterPropertiesSet();
reactiveConsumer.start();
Message<?> testMessage = new GenericMessage<>("test");
testChannel.send(testMessage);
reactiveConsumer.stop();
assertThatExceptionOfType(MessageDeliveryException.class)
.isThrownBy(() -> testChannel.send(testMessage))
.withCauseInstanceOf(IllegalStateException.class)
.withMessageContaining("doesn't have subscribers to accept messages");
reactiveConsumer.start();
Message<?> testMessage2 = new GenericMessage<>("test2");
testChannel.send(testMessage2);
StepVerifier.create(processor)
.expectNext(testMessage, testMessage2)
.thenCancel()
.verify();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2019 the original author or authors.
* Copyright 2016-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.integration.dsl.flows;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import java.io.Serializable;
import java.util.concurrent.CountDownLatch;
@@ -33,8 +32,7 @@ import java.util.function.Supplier;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.ListableBeanFactory;
@@ -64,8 +62,8 @@ import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.dsl.Transformers;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.GenericHandler;
import org.springframework.integration.handler.LoggingHandler;
import org.springframework.integration.handler.ReactiveMessageHandlerAdapter;
import org.springframework.integration.handler.advice.ErrorMessageSendingRecoverer;
import org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice;
import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice;
@@ -93,7 +91,9 @@ import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import reactor.core.publisher.Mono;
/**
* @author Artem Bilan
@@ -104,7 +104,7 @@ import org.springframework.test.context.junit4.SpringRunner;
* @since 5.0
*/
@ContextConfiguration(loader = NoBeansOverrideAnnotationConfigContextLoader.class)
@RunWith(SpringRunner.class)
@SpringJUnitConfig
@DirtiesContext
public class IntegrationFlowTests {
@@ -191,15 +191,12 @@ public class IntegrationFlowTests {
assertThat(this.beanFactory.containsBean("expressionFilter.handler")).isTrue();
QueueChannel replyChannel = new QueueChannel();
Message<String> message = MessageBuilder.withPayload("100").setReplyChannel(replyChannel).build();
try {
this.inputChannel.send(message);
fail("Expected MessageDispatchingException");
}
catch (Exception e) {
assertThat(e).isInstanceOf(MessageDeliveryException.class);
assertThat(e.getCause()).isInstanceOf(MessageDispatchingException.class);
assertThat(e.getMessage()).contains("Dispatcher has no subscribers");
}
assertThatExceptionOfType(MessageDeliveryException.class)
.isThrownBy(() -> this.inputChannel.send(message))
.withCauseInstanceOf(MessageDispatchingException.class)
.withMessageContaining("Dispatcher has no subscribers");
this.controlBus.send("@payloadSerializingTransformer.start()");
final AtomicBoolean used = new AtomicBoolean();
@@ -670,7 +667,7 @@ public class IntegrationFlowTests {
public IntegrationFlow wireTapFlow1() {
return IntegrationFlows.from("tappedChannel1")
.wireTap("tapChannel", wt -> wt.selector(m -> m.getPayload().equals("foo")))
.handle(loggingMessageHandler())
.handle(new ReactiveMessageHandlerAdapter((message) -> Mono.just(message).log().then()))
.get();
}
@@ -820,9 +817,10 @@ public class IntegrationFlowTests {
@Bean
public IntegrationFlow errorRecovererFlow() {
return IntegrationFlows.from(Function.class, (gateway) -> gateway.beanName("errorRecovererFunction"))
.handle((GenericHandler<?>) (p, h) -> {
throw new RuntimeException("intentional");
}, e -> e.advice(retryAdvice()))
.<Object>handle((p, h) -> {
throw new RuntimeException("intentional");
},
e -> e.advice(retryAdvice()))
.get();
}
@@ -903,7 +901,7 @@ public class IntegrationFlowTests {
return IntegrationFlows.from(Consumer.class,
(gateway) -> gateway.beanName("globalErrorChannelResolutionFunction"))
.channel(c -> c.executor(taskExecutor))
.handle((GenericHandler<?>) (p, h) -> {
.handle((p, h) -> {
throw new RuntimeException("intentional");
})
.get();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,7 @@ package org.springframework.integration.handler.advice;
import static org.assertj.core.api.Assertions.assertThat;
import org.aopalliance.aop.Advice;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -28,21 +27,22 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.handler.GenericHandler;
import org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice.MessageHandlingExpressionEvaluatingAdviceException;
import org.springframework.integration.message.AdviceMessage;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Gary Russell
* @author Artem Bilan
*
* @since 5.0
*
*/
@RunWith(SpringRunner.class)
@SpringJUnitConfig
public class ExpressionEvaluatingRequestHandlerAdviceTests {
@Autowired
@@ -70,14 +70,16 @@ public class ExpressionEvaluatingRequestHandlerAdviceTests {
@Bean
public IntegrationFlow advised() {
return f -> f.handle((GenericHandler<String>) (payload, headers) -> {
if (payload.equals("good")) {
return null;
}
else {
throw new RuntimeException("some failure");
}
}, c -> c.advice(expressionAdvice()));
return f -> f
.<String>handle((payload, headers) -> {
if (payload.equals("good")) {
return null;
}
else {
throw new RuntimeException("some failure");
}
},
c -> c.advice(expressionAdvice()));
}
@Bean