From b888f362e54e22420e3f2124d57a4b3a6f7c391f Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 26 Jul 2024 17:52:20 +0200 Subject: [PATCH] Document ControllerAdviceBean as internal usage This commit documents `ControllerAdviceBean` as internal usage, as it is not meant for application to manually create controller advice bean instances. This also refactors the existing partial implementation of the support for creating controller advice beans "programmatically". Closes gh-32776 --- .../web/method/ControllerAdviceBean.java | 114 +++-------- .../web/method/ControllerAdviceBeanTests.java | 186 +++++++++--------- .../RequestResponseBodyAdviceChainTests.java | 13 +- 3 files changed, 138 insertions(+), 175 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java index b7d1701a37..20e4207c46 100644 --- a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java +++ b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java @@ -33,7 +33,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.OrderUtils; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -43,10 +42,11 @@ import org.springframework.web.bind.annotation.ControllerAdvice; /** * Encapsulates information about an {@link ControllerAdvice @ControllerAdvice} * Spring-managed bean without necessarily requiring it to be instantiated. + * The {@link #findAnnotatedBeans(ApplicationContext)} method can be used to + * discover such beans. * - *

The {@link #findAnnotatedBeans(ApplicationContext)} method can be used to - * discover such beans. However, a {@code ControllerAdviceBean} may be created - * from any object, including ones without an {@code @ControllerAdvice} annotation. + *

This class is internal to Spring Framework and is not meant to be used + * by applications to manually create {@code @ControllerAdvice} beans. * * @author Rossen Stoyanchev * @author Brian Clozel @@ -56,11 +56,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; */ public class ControllerAdviceBean implements Ordered { - /** - * Reference to the actual bean instance or a {@code String} representing - * the bean name. - */ - private final Object beanOrName; + private final String beanName; private final boolean isSingleton; @@ -76,38 +72,12 @@ public class ControllerAdviceBean implements Ordered { private final HandlerTypePredicate beanTypePredicate; - @Nullable private final BeanFactory beanFactory; @Nullable private Integer order; - /** - * Create a {@code ControllerAdviceBean} using the given bean instance. - * @param bean the bean instance - */ - public ControllerAdviceBean(Object bean) { - Assert.notNull(bean, "Bean must not be null"); - this.beanOrName = bean; - this.isSingleton = true; - this.resolvedBean = bean; - this.beanType = ClassUtils.getUserClass(bean.getClass()); - this.beanTypePredicate = createBeanTypePredicate(this.beanType); - this.beanFactory = null; - } - - /** - * Create a {@code ControllerAdviceBean} using the given bean name and - * {@code BeanFactory}. - * @param beanName the name of the bean - * @param beanFactory a {@code BeanFactory} to retrieve the bean type initially - * and later to resolve the actual bean - */ - public ControllerAdviceBean(String beanName, BeanFactory beanFactory) { - this(beanName, beanFactory, null); - } - /** * Create a {@code ControllerAdviceBean} using the given bean name, * {@code BeanFactory}, and {@link ControllerAdvice @ControllerAdvice} @@ -115,21 +85,20 @@ public class ControllerAdviceBean implements Ordered { * @param beanName the name of the bean * @param beanFactory a {@code BeanFactory} to retrieve the bean type initially * and later to resolve the actual bean - * @param controllerAdvice the {@code @ControllerAdvice} annotation for the - * bean, or {@code null} if not yet retrieved + * @param controllerAdvice the {@code @ControllerAdvice} annotation for the bean * @since 5.2 */ - public ControllerAdviceBean(String beanName, BeanFactory beanFactory, @Nullable ControllerAdvice controllerAdvice) { + public ControllerAdviceBean(String beanName, BeanFactory beanFactory, ControllerAdvice controllerAdvice) { Assert.hasText(beanName, "Bean name must contain text"); Assert.notNull(beanFactory, "BeanFactory must not be null"); Assert.isTrue(beanFactory.containsBean(beanName), () -> "BeanFactory [" + beanFactory + "] does not contain specified controller advice bean '" + beanName + "'"); + Assert.notNull(controllerAdvice, "ControllerAdvice must not be null"); - this.beanOrName = beanName; + this.beanName = beanName; this.isSingleton = beanFactory.isSingleton(beanName); this.beanType = getBeanType(beanName, beanFactory); - this.beanTypePredicate = (controllerAdvice != null ? createBeanTypePredicate(controllerAdvice) : - createBeanTypePredicate(this.beanType)); + this.beanTypePredicate = createBeanTypePredicate(controllerAdvice); this.beanFactory = beanFactory; } @@ -158,21 +127,14 @@ public class ControllerAdviceBean implements Ordered { @Override public int getOrder() { if (this.order == null) { - String beanName = null; Object resolvedBean = null; - if (this.beanFactory != null && this.beanOrName instanceof String stringBeanName) { - beanName = stringBeanName; - String targetBeanName = ScopedProxyUtils.getTargetBeanName(beanName); - boolean isScopedProxy = this.beanFactory.containsBean(targetBeanName); - // Avoid eager @ControllerAdvice bean resolution for scoped proxies, - // since attempting to do so during context initialization would result - // in an exception due to the current absence of the scope. For example, - // an HTTP request or session scope is not active during initialization. - if (!isScopedProxy && !ScopedProxyUtils.isScopedTarget(beanName)) { - resolvedBean = resolveBean(); - } - } - else { + String targetBeanName = ScopedProxyUtils.getTargetBeanName(this.beanName); + boolean isScopedProxy = this.beanFactory.containsBean(targetBeanName); + // Avoid eager @ControllerAdvice bean resolution for scoped proxies, + // since attempting to do so during context initialization would result + // in an exception due to the current absence of the scope. For example, + // an HTTP request or session scope is not active during initialization. + if (!isScopedProxy && !ScopedProxyUtils.isScopedTarget(this.beanName)) { resolvedBean = resolveBean(); } @@ -180,9 +142,9 @@ public class ControllerAdviceBean implements Ordered { this.order = ordered.getOrder(); } else { - if (beanName != null && this.beanFactory instanceof ConfigurableBeanFactory cbf) { + if (this.beanFactory instanceof ConfigurableBeanFactory cbf) { try { - BeanDefinition bd = cbf.getMergedBeanDefinition(beanName); + BeanDefinition bd = cbf.getMergedBeanDefinition(this.beanName); if (bd instanceof RootBeanDefinition rbd) { Method factoryMethod = rbd.getResolvedFactoryMethod(); if (factoryMethod != null) { @@ -225,9 +187,7 @@ public class ControllerAdviceBean implements Ordered { */ public Object resolveBean() { if (this.resolvedBean == null) { - // this.beanOrName must be a String representing the bean name if - // this.resolvedBean is null. - Object resolvedBean = obtainBeanFactory().getBean((String) this.beanOrName); + Object resolvedBean = this.beanFactory.getBean(this.beanName); // Don't cache non-singletons (e.g., prototypes). if (!this.isSingleton) { return resolvedBean; @@ -237,11 +197,6 @@ public class ControllerAdviceBean implements Ordered { return this.resolvedBean; } - private BeanFactory obtainBeanFactory() { - Assert.state(this.beanFactory != null, "No BeanFactory set"); - return this.beanFactory; - } - /** * Check whether the given bean type should be advised by this * {@code ControllerAdviceBean}. @@ -257,17 +212,17 @@ public class ControllerAdviceBean implements Ordered { @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof ControllerAdviceBean that && - this.beanOrName.equals(that.beanOrName) && this.beanFactory == that.beanFactory)); + this.beanName.equals(that.beanName) && this.beanFactory == that.beanFactory)); } @Override public int hashCode() { - return this.beanOrName.hashCode(); + return this.beanName.hashCode(); } @Override public String toString() { - return this.beanOrName.toString(); + return this.beanName; } @@ -308,22 +263,13 @@ public class ControllerAdviceBean implements Ordered { return (beanType != null ? ClassUtils.getUserClass(beanType) : null); } - private static HandlerTypePredicate createBeanTypePredicate(@Nullable Class beanType) { - ControllerAdvice controllerAdvice = (beanType != null ? - AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class) : null); - return createBeanTypePredicate(controllerAdvice); - } - - private static HandlerTypePredicate createBeanTypePredicate(@Nullable ControllerAdvice controllerAdvice) { - if (controllerAdvice != null) { - return HandlerTypePredicate.builder() - .basePackage(controllerAdvice.basePackages()) - .basePackageClass(controllerAdvice.basePackageClasses()) - .assignableType(controllerAdvice.assignableTypes()) - .annotation(controllerAdvice.annotations()) - .build(); - } - return HandlerTypePredicate.forAnyHandlerType(); + private static HandlerTypePredicate createBeanTypePredicate(ControllerAdvice controllerAdvice) { + return HandlerTypePredicate.builder() + .basePackage(controllerAdvice.basePackages()) + .basePackageClass(controllerAdvice.basePackageClasses()) + .assignableType(controllerAdvice.assignableTypes()) + .annotation(controllerAdvice.annotations()) + .build(); } } diff --git a/spring-web/src/test/java/org/springframework/web/method/ControllerAdviceBeanTests.java b/spring-web/src/test/java/org/springframework/web/method/ControllerAdviceBeanTests.java index b04f5c22a9..f89bd5366d 100644 --- a/spring-web/src/test/java/org/springframework/web/method/ControllerAdviceBeanTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/ControllerAdviceBeanTests.java @@ -23,22 +23,23 @@ import java.util.List; import jakarta.annotation.Priority; import org.junit.jupiter.api.Test; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; /** * Unit and integration tests for {@link ControllerAdviceBean}. @@ -48,54 +49,52 @@ import static org.mockito.Mockito.verify; */ class ControllerAdviceBeanTests { + private StaticApplicationContext applicationContext = new StaticApplicationContext(); + @Test - void constructorPreconditions() { + void shouldFailForNullOrEmptyBeanName() { assertThatIllegalArgumentException() - .isThrownBy(() -> new ControllerAdviceBean(null)) - .withMessage("Bean must not be null"); + .isThrownBy(() -> new ControllerAdviceBean(null, null, null)) + .withMessage("Bean name must contain text"); assertThatIllegalArgumentException() - .isThrownBy(() -> new ControllerAdviceBean(null, null)) - .withMessage("Bean name must contain text"); + .isThrownBy(() -> new ControllerAdviceBean(" ", null, null)) + .withMessage("Bean name must contain text"); + } + @Test + void shouldFailForNullBeanFactory() { assertThatIllegalArgumentException() - .isThrownBy(() -> new ControllerAdviceBean("", null)) - .withMessage("Bean name must contain text"); + .isThrownBy(() -> new ControllerAdviceBean("beanName", null, null)) + .withMessage("BeanFactory must not be null"); + } + @Test + void shouldFailWhenBeanFactoryDoesNotContainBean() { + BeanFactory beanFactory = mock(BeanFactory.class); + given(beanFactory.containsBean(eq("beanName"))).willReturn(false); assertThatIllegalArgumentException() - .isThrownBy(() -> new ControllerAdviceBean("\t", null)) - .withMessage("Bean name must contain text"); + .isThrownBy(() -> new ControllerAdviceBean("beanName", beanFactory, null)) + .withMessageContaining("does not contain specified controller advice bean 'beanName'"); + } + @Test + void shouldFailWhenControllerAdviceNull() { + BeanFactory beanFactory = mock(BeanFactory.class); + given(beanFactory.containsBean(eq("beanName"))).willReturn(true); assertThatIllegalArgumentException() - .isThrownBy(() -> new ControllerAdviceBean("myBean", null)) - .withMessage("BeanFactory must not be null"); + .isThrownBy(() -> new ControllerAdviceBean("beanName", beanFactory, null)) + .withMessage("ControllerAdvice must not be null"); } @Test void equalsHashCodeAndToStringForBeanName() { - String beanName = "myBean"; - BeanFactory beanFactory = mock(); - given(beanFactory.containsBean(beanName)).willReturn(true); - - ControllerAdviceBean bean1 = new ControllerAdviceBean(beanName, beanFactory); - ControllerAdviceBean bean2 = new ControllerAdviceBean(beanName, beanFactory); + String beanName = SimpleControllerAdvice.class.getSimpleName(); + ControllerAdviceBean bean1 = createControllerAdviceBean(SimpleControllerAdvice.class); + ControllerAdviceBean bean2 = createControllerAdviceBean(SimpleControllerAdvice.class); assertEqualsHashCodeAndToString(bean1, bean2, beanName); } - @Test - void equalsHashCodeAndToStringForBeanInstance() { - String toString = "beanInstance"; - Object beanInstance = new Object() { - @Override - public String toString() { - return toString; - } - }; - ControllerAdviceBean bean1 = new ControllerAdviceBean(beanInstance); - ControllerAdviceBean bean2 = new ControllerAdviceBean(beanInstance); - assertEqualsHashCodeAndToString(bean1, bean2, toString); - } - @Test void orderedWithLowestPrecedenceByDefaultForBeanName() { assertOrder(SimpleControllerAdvice.class, Ordered.LOWEST_PRECEDENCE); @@ -103,7 +102,7 @@ class ControllerAdviceBeanTests { @Test void orderedWithLowestPrecedenceByDefaultForBeanInstance() { - assertOrder(new SimpleControllerAdvice(), Ordered.LOWEST_PRECEDENCE); + assertOrder(SimpleControllerAdvice.class, Ordered.LOWEST_PRECEDENCE); } @Test @@ -113,7 +112,7 @@ class ControllerAdviceBeanTests { @Test void orderedViaOrderedInterfaceForBeanInstance() { - assertOrder(new OrderedControllerAdvice(), 42); + assertOrder(OrderedControllerAdvice.class, 42); } @Test @@ -124,13 +123,13 @@ class ControllerAdviceBeanTests { @Test void orderedViaAnnotationForBeanInstance() { - assertOrder(new OrderAnnotationControllerAdvice(), 100); - assertOrder(new PriorityAnnotationControllerAdvice(), 200); + assertOrder(OrderAnnotationControllerAdvice.class, 100); + assertOrder(PriorityAnnotationControllerAdvice.class, 200); } @Test void shouldMatchAll() { - ControllerAdviceBean bean = new ControllerAdviceBean(new SimpleControllerAdvice()); + ControllerAdviceBean bean = createControllerAdviceBean(SimpleControllerAdvice.class); assertApplicable("should match all", bean, AnnotatedController.class); assertApplicable("should match all", bean, ImplementationController.class); assertApplicable("should match all", bean, InheritanceController.class); @@ -139,7 +138,7 @@ class ControllerAdviceBeanTests { @Test void basePackageSupport() { - ControllerAdviceBean bean = new ControllerAdviceBean(new BasePackageSupport()); + ControllerAdviceBean bean = createControllerAdviceBean(BasePackageSupport.class); assertApplicable("base package support", bean, AnnotatedController.class); assertApplicable("base package support", bean, ImplementationController.class); assertApplicable("base package support", bean, InheritanceController.class); @@ -148,7 +147,7 @@ class ControllerAdviceBeanTests { @Test void basePackageValueSupport() { - ControllerAdviceBean bean = new ControllerAdviceBean(new BasePackageValueSupport()); + ControllerAdviceBean bean = createControllerAdviceBean(BasePackageValueSupport.class); assertApplicable("base package support", bean, AnnotatedController.class); assertApplicable("base package support", bean, ImplementationController.class); assertApplicable("base package support", bean, InheritanceController.class); @@ -157,14 +156,14 @@ class ControllerAdviceBeanTests { @Test void annotationSupport() { - ControllerAdviceBean bean = new ControllerAdviceBean(new AnnotationSupport()); + ControllerAdviceBean bean = createControllerAdviceBean(AnnotationSupport.class); assertApplicable("annotation support", bean, AnnotatedController.class); assertNotApplicable("this bean is not annotated", bean, InheritanceController.class); } @Test void markerClassSupport() { - ControllerAdviceBean bean = new ControllerAdviceBean(new MarkerClassSupport()); + ControllerAdviceBean bean = createControllerAdviceBean(MarkerClassSupport.class); assertApplicable("base package class support", bean, AnnotatedController.class); assertApplicable("base package class support", bean, ImplementationController.class); assertApplicable("base package class support", bean, InheritanceController.class); @@ -173,7 +172,7 @@ class ControllerAdviceBeanTests { @Test void shouldNotMatch() { - ControllerAdviceBean bean = new ControllerAdviceBean(new ShouldNotMatch()); + ControllerAdviceBean bean = createControllerAdviceBean(ShouldNotMatch.class); assertNotApplicable("should not match", bean, AnnotatedController.class); assertNotApplicable("should not match", bean, ImplementationController.class); assertNotApplicable("should not match", bean, InheritanceController.class); @@ -182,7 +181,7 @@ class ControllerAdviceBeanTests { @Test void assignableTypesSupport() { - ControllerAdviceBean bean = new ControllerAdviceBean(new AssignableTypesSupport()); + ControllerAdviceBean bean = createControllerAdviceBean(AssignableTypesSupport.class); assertApplicable("controller implements assignable", bean, ImplementationController.class); assertApplicable("controller inherits assignable", bean, InheritanceController.class); assertNotApplicable("not assignable", bean, AnnotatedController.class); @@ -191,7 +190,7 @@ class ControllerAdviceBeanTests { @Test void multipleMatch() { - ControllerAdviceBean bean = new ControllerAdviceBean(new MultipleSelectorsSupport()); + ControllerAdviceBean bean = createControllerAdviceBean(MultipleSelectorsSupport.class); assertApplicable("controller implements assignable", bean, ImplementationController.class); assertApplicable("controller is annotated", bean, AnnotatedController.class); assertNotApplicable("should not match", bean, InheritanceController.class); @@ -201,14 +200,14 @@ class ControllerAdviceBeanTests { @SuppressWarnings({"rawtypes", "unchecked"}) public void findAnnotatedBeansSortsBeans() { Class[] expectedTypes = { - // Since ControllerAdviceBean currently treats PriorityOrdered the same as Ordered, - // OrderedControllerAdvice is sorted before PriorityOrderedControllerAdvice. - OrderedControllerAdvice.class, - PriorityOrderedControllerAdvice.class, - OrderAnnotationControllerAdvice.class, - PriorityAnnotationControllerAdvice.class, - SimpleControllerAdviceWithBeanOrder.class, - SimpleControllerAdvice.class, + // Since ControllerAdviceBean currently treats PriorityOrdered the same as Ordered, + // OrderedControllerAdvice is sorted before PriorityOrderedControllerAdvice. + OrderedControllerAdvice.class, + PriorityOrderedControllerAdvice.class, + OrderAnnotationControllerAdvice.class, + PriorityAnnotationControllerAdvice.class, + SimpleControllerAdviceWithBeanOrder.class, + SimpleControllerAdvice.class, }; AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); @@ -217,6 +216,13 @@ class ControllerAdviceBeanTests { assertThat(adviceBeans).extracting(ControllerAdviceBean::getBeanType).containsExactly(expectedTypes); } + private ControllerAdviceBean createControllerAdviceBean(Class beanType) { + String beanName = beanType.getSimpleName(); + this.applicationContext.registerSingleton(beanName, beanType); + ControllerAdvice controllerAdvice = AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class); + return new ControllerAdviceBean(beanName, this.applicationContext, controllerAdvice); + } + private void assertEqualsHashCodeAndToString(ControllerAdviceBean bean1, ControllerAdviceBean bean2, String toString) { assertThat(bean1).isEqualTo(bean2); assertThat(bean2).isEqualTo(bean1); @@ -225,24 +231,8 @@ class ControllerAdviceBeanTests { assertThat(bean2.toString()).isEqualTo(toString); } - private void assertOrder(Object bean, int expectedOrder) { - assertThat(new ControllerAdviceBean(bean).getOrder()).isEqualTo(expectedOrder); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - private void assertOrder(Class beanType, int expectedOrder) { - String beanName = "myBean"; - BeanFactory beanFactory = mock(); - given(beanFactory.containsBean(beanName)).willReturn(true); - given(beanFactory.getType(beanName)).willReturn(beanType); - given(beanFactory.getBean(beanName)).willReturn(BeanUtils.instantiateClass(beanType)); - - ControllerAdviceBean controllerAdviceBean = new ControllerAdviceBean(beanName, beanFactory); - - assertThat(controllerAdviceBean.getOrder()).isEqualTo(expectedOrder); - verify(beanFactory).containsBean(beanName); - verify(beanFactory).getType(beanName); - verify(beanFactory).getBean(beanName); + private void assertOrder(Class beanType, int expectedOrder) { + assertThat(createControllerAdviceBean(beanType).getOrder()).isEqualTo(expectedOrder); } private void assertApplicable(String message, ControllerAdviceBean controllerAdvice, Class controllerBeanType) { @@ -259,18 +249,22 @@ class ControllerAdviceBeanTests { // ControllerAdvice classes @ControllerAdvice - static class SimpleControllerAdvice {} + static class SimpleControllerAdvice { + } @ControllerAdvice - static class SimpleControllerAdviceWithBeanOrder {} + static class SimpleControllerAdviceWithBeanOrder { + } @ControllerAdvice @Order(100) - static class OrderAnnotationControllerAdvice {} + static class OrderAnnotationControllerAdvice { + } @ControllerAdvice @Priority(200) - static class PriorityAnnotationControllerAdvice {} + static class PriorityAnnotationControllerAdvice { + } @ControllerAdvice // @Order and @Priority should be ignored due to implementation of Ordered. @@ -297,45 +291,59 @@ class ControllerAdviceBeanTests { } @ControllerAdvice(annotations = ControllerAnnotation.class) - static class AnnotationSupport {} + static class AnnotationSupport { + } @ControllerAdvice(basePackageClasses = MarkerClass.class) - static class MarkerClassSupport {} + static class MarkerClassSupport { + } @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) - static class AssignableTypesSupport {} + static class AssignableTypesSupport { + } @ControllerAdvice(basePackages = "org.springframework.web.method") - static class BasePackageSupport {} + static class BasePackageSupport { + } @ControllerAdvice("org.springframework.web.method") - static class BasePackageValueSupport {} + static class BasePackageValueSupport { + } @ControllerAdvice(annotations = ControllerAnnotation.class, assignableTypes = ControllerInterface.class) - static class MultipleSelectorsSupport {} + static class MultipleSelectorsSupport { + } @ControllerAdvice(basePackages = "java.util", annotations = {RestController.class}) - static class ShouldNotMatch {} + static class ShouldNotMatch { + } // Support classes - static class MarkerClass {} + static class MarkerClass { + } @Retention(RetentionPolicy.RUNTIME) - @interface ControllerAnnotation {} + @interface ControllerAnnotation { + } @ControllerAnnotation - public static class AnnotatedController {} + public static class AnnotatedController { + } - interface ControllerInterface {} + interface ControllerInterface { + } - static class ImplementationController implements ControllerInterface {} + static class ImplementationController implements ControllerInterface { + } - abstract static class AbstractController {} + abstract static class AbstractController { + } - static class InheritanceController extends AbstractController {} + static class InheritanceController extends AbstractController { + } @Configuration(proxyBeanMethods = false) static class Config { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyAdviceChainTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyAdviceChainTests.java index 0c95016571..c2bbb5a577 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyAdviceChainTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyAdviceChainTests.java @@ -23,7 +23,9 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.http.HttpInputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; @@ -109,7 +111,7 @@ class RequestResponseBodyAdviceChainTests { @Test void controllerAdvice() { - Object adviceBean = new ControllerAdviceBean(new MyControllerAdvice()); + Object adviceBean = createControllerAdviceBean(MyControllerAdvice.class); RequestResponseBodyAdviceChain chain = new RequestResponseBodyAdviceChain(Collections.singletonList(adviceBean)); String actual = (String) chain.beforeBodyWrite(this.body, this.returnType, this.contentType, @@ -120,7 +122,7 @@ class RequestResponseBodyAdviceChainTests { @Test void controllerAdviceNotApplicable() { - Object adviceBean = new ControllerAdviceBean(new TargetedControllerAdvice()); + Object adviceBean = createControllerAdviceBean(TargetedControllerAdvice.class); RequestResponseBodyAdviceChain chain = new RequestResponseBodyAdviceChain(Collections.singletonList(adviceBean)); String actual = (String) chain.beforeBodyWrite(this.body, this.returnType, this.contentType, @@ -129,6 +131,13 @@ class RequestResponseBodyAdviceChainTests { assertThat(actual).isEqualTo(this.body); } + private ControllerAdviceBean createControllerAdviceBean(Class beanType) { + StaticApplicationContext applicationContext = new StaticApplicationContext(); + applicationContext.registerSingleton(beanType.getSimpleName(), beanType); + ControllerAdvice controllerAdvice = AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class); + return new ControllerAdviceBean(beanType.getSimpleName(), applicationContext, controllerAdvice); + } + @ControllerAdvice private static class MyControllerAdvice implements ResponseBodyAdvice {