From 47b56fbcb35a1acc9bee7883e2d6aacb96fd2a48 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Mon, 22 May 2023 17:07:05 -0400 Subject: [PATCH] GH-2456: Suppress Duplicate Annotations with Spy Resolves https://github.com/spring-projects/spring-amqp/issues/2456 When spying a `@RabbitListener` bean, duplicate methods are resolved as well as duplicate class level `@RabbitListener` annotations. **cherry-pick to 2.4.x** (will require instanceof polishing for Java 8) --- ...bitListenerAnnotationBeanPostProcessor.java | 18 +++++++++++++++--- .../EnableRabbitIntegrationTests.java | 11 ++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/RabbitListenerAnnotationBeanPostProcessor.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/RabbitListenerAnnotationBeanPostProcessor.java index a30fc6aa36..2d03aecfc5 100644 --- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/RabbitListenerAnnotationBeanPostProcessor.java +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/RabbitListenerAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 the original author or authors. + * Copyright 2014-2023 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. @@ -339,7 +339,8 @@ private TypeMetadata buildMetadata(Class targetClass) { multiMethods.add(method); } } - }, ReflectionUtils.USER_DECLARED_METHODS); + }, ReflectionUtils.USER_DECLARED_METHODS + .and(meth -> !meth.getDeclaringClass().getName().contains("$MockitoMock$"))); if (methods.isEmpty() && multiMethods.isEmpty()) { return TypeMetadata.EMPTY; } @@ -352,6 +353,17 @@ private TypeMetadata buildMetadata(Class targetClass) { private List findListenerAnnotations(AnnotatedElement element) { return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY) .stream(RabbitListener.class) + .filter(tma -> { + Object source = tma.getSource(); + String name = ""; + if (source instanceof Class) { + name = ((Class) source).getName(); + } + else if (source instanceof Method) { + name = ((Method) source).getDeclaringClass().getName(); + } + return !name.contains("$MockitoMock$"); + }) .map(ann -> ann.synthesize()) .collect(Collectors.toList()); } @@ -359,7 +371,7 @@ private List findListenerAnnotations(AnnotatedElement element) { private void processMultiMethodListeners(RabbitListener[] classLevelListeners, Method[] multiMethods, Object bean, String beanName) { - List checkedMethods = new ArrayList(); + List checkedMethods = new ArrayList<>(); Method defaultMethod = null; for (Method method : multiMethods) { Method checked = checkProxy(method, bean); diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/EnableRabbitIntegrationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/EnableRabbitIntegrationTests.java index ea1bd5baba..70a1dfb19d 100644 --- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/EnableRabbitIntegrationTests.java +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/EnableRabbitIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 the original author or authors. + * Copyright 2014-2023 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,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import java.io.IOException; import java.io.Serializable; @@ -385,6 +389,7 @@ public void multiListener() { rabbitTemplate.convertAndSend("multi.exch", "multi.rk", bar); assertThat(this.rabbitTemplate.receiveAndConvert("sendTo.replies")) .isEqualTo("CRASHCRASH Test reply from error handler"); + verify(this.multi, times(2)).bar(any()); bar.field = "bar"; Baz baz = new Baz(); baz.field = "baz"; @@ -400,7 +405,7 @@ public void multiListener() { this.rabbitTemplate.setAfterReceivePostProcessors(mpp); assertThat(rabbitTemplate.convertSendAndReceive("multi.exch", "multi.rk", qux)).isEqualTo("QUX: qux: multi.rk"); assertThat(beanMethodHeaders).hasSize(2); - assertThat(beanMethodHeaders.get(0)).isEqualTo("MultiListenerBean"); + assertThat(beanMethodHeaders.get(0)).contains("$MultiListenerBean"); assertThat(beanMethodHeaders.get(1)).isEqualTo("qux"); this.rabbitTemplate.removeAfterReceivePostProcessor(mpp); assertThat(rabbitTemplate.convertSendAndReceive("multi.exch.tx", "multi.rk.tx", bar)).isEqualTo("BAR: barbar"); @@ -1966,7 +1971,7 @@ public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) { @Bean public MultiListenerBean multiListener() { - return new MultiListenerBean(); + return spy(new MultiListenerBean()); } @Bean