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:
committed by
Gary Russell
parent
e0509cc339
commit
d13752b40b
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user