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
This commit is contained in:
Brian Clozel 2024-07-26 17:52:20 +02:00
parent 94e2bef9a3
commit b888f362e5
3 changed files with 138 additions and 175 deletions

View File

@ -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.
*
* <p>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.
* <p>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();
}
}

View File

@ -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 {

View File

@ -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<String> {