Skip to content

Unable to use ListenerContainerWithDlqAndRetryCustomizer #2345

Description

@mattdupuy

Hi,

I have been trying to use the new ListenerContainerCustomizer "ListenerContainerWithDlqAndRetryCustomizer" for Kafka Binder.
I want to use it to perform a complex retry process.
After following the documentation https://docs.spring.io/spring-cloud-stream-binder-kafka/docs/3.2.2/reference/html/spring-cloud-stream-binder-kafka.html#retry-and-dlq-processing, when I start my application, my customizer is never call, so I have performed some debugging to trying to figure out what is happening.

I have found a strange behavior in the class KafkaMessageChannelBinder, in the method "createConsumerEndpoint", line 748 and line 797.

if (!(customizer instanceof ListenerContainerWithDlqAndRetryCustomizer)
			|| ((ListenerContainerWithDlqAndRetryCustomizer) customizer)
					.retryAndDlqInBinding(destination.getName(), group)) {
		kafkaMessageDrivenChannelAdapter
				.setRetryTemplate(buildRetryTemplate(extendedConsumerProperties));
		kafkaMessageDrivenChannelAdapter
				.setRecoveryCallback(errorInfrastructure.getRecoverer());
}

if (customizer instanceof ListenerContainerWithDlqAndRetryCustomizer) {
	BiFunction<ConsumerRecord<?, ?>, Exception, TopicPartition> destinationResolver = createDestResolver(
			extendedConsumerProperties.getExtension());
	BackOff createBackOff = extendedConsumerProperties.getMaxAttempts() > 1
			? createBackOff(extendedConsumerProperties)
			: null;
	((ListenerContainerWithDlqAndRetryCustomizer) customizer)
			.configure(messageListenerContainer, destination.getName(), consumerGroup, destinationResolver,
					createBackOff);
}

The code is checking if the customizer is an instance of "ListenerContainerWithDlqAndRetryCustomizer", however, even when the customizer if an instance of "ListenerContainerWithDlqAndRetryCustomizer", the result if false. After investigating, I have found out that the customizer is wrap inside a Anonymous implementaton of ListenerContainerCustomizer, declare inside the DefaultBinderFactory class, line 425.

image

ListenerContainerCustomizer customizerWrapper = new ListenerContainerCustomizer() {
        @SuppressWarnings("unchecked")
        @Override
        public void configure(Object container, String destinationName, String group) {
	        try {
		        customizerEntry.getValue().configure(container, destinationName, group);
	        }
	        catch (Exception e) {
		        logger.warn("Failed while applying ListenerContainerCustomizer. In situations when multiple "
				        + "binders are used this is expected, since a particular customizer may not be applicable.");
	        }
        }
};
((GenericApplicationContext) binderProducingContext).registerBean(customizerEntry.getKey(),
	ListenerContainerCustomizer.class, () -> customizerWrapper);

Because of this wrapping, even if we declare a bean of type ListenerContainerWithDlqAndRetryCustomizer, it is wrap inside a ListenerContainerCustomizer anonymous implementation, which make the code of the class KafkaMessageChannelBinder always return false on:

customizer instanceof ListenerContainerWithDlqAndRetryCustomizer

Steps to reproduce the behavior:
Follow the documentation inside the documentation:
https://docs.spring.io/spring-cloud-stream-binder-kafka/docs/3.2.2/reference/html/spring-cloud-stream-binder-kafka.html#retry-and-dlq-processing

Version of the framework: 3.2.2
Expected behavior: When a Bean of type ListenerContainerWithDlqAndRetryCustomizer is configured, it use as described in the documentation

Maybe I am doing something wrong in the configuration, but I haven't found out yet.

Thanks for looking into it.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions