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