GH-3452: Warn Publisher.onError in NullChannel (#3453)
* GH-3452: Warn Publisher.onError in NullChannel Fixes https://github.com/spring-projects/spring-integration/issues/3452 The `NullChannel` can subscribe to the `Publisher` payload fully ignoring the possible data according the `NullChannel` nature. However an error thrown from the reactive stream processing is also ignored. * Log WARN message from the `Subscriber.onError()` when `NullChannel` subscribes to the `Publisher` produced to this channel. * Mention the logic in the `NullChannel` docs; point to the `ReactiveRequestHandlerAdvice` for further possible error handling in the target application **Cherry-pick to `5.4.x`** * * Remove unused imports * * Fix `PseudoTransactionalMessageSourceTests` for `LogAccessor` from `NullChannel`
This commit is contained in:
committed by
Gary Russell
parent
5ed8c11c58
commit
ebe79aedfb
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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,13 +18,12 @@ package org.springframework.integration.channel;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.integration.IntegrationPattern;
|
||||
import org.springframework.integration.IntegrationPatternType;
|
||||
import org.springframework.integration.support.management.IntegrationManagedResource;
|
||||
@@ -43,6 +42,8 @@ import org.springframework.messaging.PollableChannel;
|
||||
* Unless the payload of a sent message is a {@link Publisher} implementation, in
|
||||
* which case the {@link Publisher#subscribe(Subscriber)} is called to initiate
|
||||
* the reactive stream, although the data is discarded by this channel.
|
||||
* An error thrown from a reactive stream processing (see {@link Subscriber#onError(Throwable)})
|
||||
* is logged under the {@code warn} level.
|
||||
* Note however that the invocations are logged at debug-level.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
@@ -53,7 +54,7 @@ import org.springframework.messaging.PollableChannel;
|
||||
public class NullChannel implements PollableChannel,
|
||||
BeanNameAware, IntegrationManagement, IntegrationPattern {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
private static final LogAccessor LOG = new LogAccessor(NullChannel.class);
|
||||
|
||||
private final ManagementOverrides managementOverrides = new ManagementOverrides();
|
||||
|
||||
@@ -122,8 +123,8 @@ public class NullChannel implements PollableChannel,
|
||||
|
||||
@Override
|
||||
public boolean send(Message<?> message) {
|
||||
if (this.loggingEnabled && this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("message sent to null channel: " + message);
|
||||
if (this.loggingEnabled) {
|
||||
LOG.debug(() -> "message sent to null channel: " + message);
|
||||
}
|
||||
|
||||
Object payload = message.getPayload();
|
||||
@@ -135,12 +136,12 @@ public class NullChannel implements PollableChannel,
|
||||
subscription.request(Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override public void onNext(Object o) {
|
||||
@Override public void onNext(Object value) {
|
||||
|
||||
}
|
||||
|
||||
@Override public void onError(Throwable t) {
|
||||
|
||||
@Override public void onError(Throwable ex) {
|
||||
LOG.warn(ex, "An error happened in a reactive stream processing");
|
||||
}
|
||||
|
||||
@Override public void onComplete() {
|
||||
@@ -173,7 +174,7 @@ public class NullChannel implements PollableChannel,
|
||||
@Override
|
||||
public Message<?> receive() {
|
||||
if (this.loggingEnabled) {
|
||||
this.logger.debug("receive called on null channel");
|
||||
LOG.debug("receive called on null channel");
|
||||
}
|
||||
incrementReceiveCounter();
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.channel.NullChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
@@ -97,14 +98,16 @@ public class PseudoTransactionalMessageSourceTests {
|
||||
|
||||
MessageChannel afterCommitChannel = new NullChannel();
|
||||
syncProcessor.setAfterCommitChannel(afterCommitChannel);
|
||||
Log logger = TestUtils.getPropertyValue(afterCommitChannel, "logger", Log.class);
|
||||
LogAccessor logAccessor = TestUtils.getPropertyValue(afterCommitChannel, "LOG", LogAccessor.class);
|
||||
|
||||
Log logger = logAccessor.getLog();
|
||||
|
||||
logger = Mockito.spy(logger);
|
||||
|
||||
Mockito.when(logger.isDebugEnabled()).thenReturn(true);
|
||||
|
||||
DirectFieldAccessor dfa = new DirectFieldAccessor(afterCommitChannel);
|
||||
dfa.setPropertyValue("logger", logger);
|
||||
DirectFieldAccessor dfa = new DirectFieldAccessor(logAccessor);
|
||||
dfa.setPropertyValue("log", logger);
|
||||
|
||||
TransactionSynchronizationManager.initSynchronization();
|
||||
TransactionSynchronizationManager.setActualTransactionActive(true);
|
||||
@@ -115,7 +118,7 @@ public class PseudoTransactionalMessageSourceTests {
|
||||
assertThat(beforeCommitMessage).isNotNull();
|
||||
assertThat(beforeCommitMessage.getPayload()).isEqualTo("qox");
|
||||
|
||||
Mockito.verify(logger).debug(Mockito.anyString());
|
||||
Mockito.verify(logger).debug(Mockito.any(CharSequence.class));
|
||||
|
||||
TransactionSynchronizationUtils.triggerAfterCompletion(TransactionSynchronization.STATUS_COMMITTED);
|
||||
TransactionSynchronizationManager.clearSynchronization();
|
||||
|
||||
@@ -1145,6 +1145,8 @@ For example, you can use this technique to configure a test case to verify messa
|
||||
Two special channels are defined within the application context by default: `errorChannel` and `nullChannel`.
|
||||
The 'nullChannel' (an instance of `NullChannel`) acts like `/dev/null`, logging any message sent to it at the `DEBUG` level and returning immediately.
|
||||
The special treatment is applied for an `org.reactivestreams.Publisher` payload of a sent message: it is subscribed to in this channel immediately, to initiate reactive stream processing, although the data is discarded.
|
||||
An error thrown from a reactive stream processing (see `Subscriber.onError(Throwable)`) is logged under the warn level for possible investigation.
|
||||
If there is need to do anything with such an error, the `<<./handler-advice.adoc#reactive-advice,ReactiveRequestHandlerAdvice>>` with a `Mono.doOnError()` customization can be applied to the message handler producing `Mono` reply into this `nullChannel`.
|
||||
Any time you face channel resolution errors for a reply that you do not care about, you can set the affected component's `output-channel` attribute to 'nullChannel' (the name, 'nullChannel', is reserved within the application context).
|
||||
|
||||
The 'errorChannel' is used internally for sending error messages and may be overridden with a custom configuration.
|
||||
|
||||
Reference in New Issue
Block a user