Extract HandlerTypePredicate from ControllerAdviceBean

Issue: SPR-16336
This commit is contained in:
Rossen Stoyanchev 2018-06-06 17:14:52 -04:00
parent 25559b9e44
commit 31159a8506
2 changed files with 217 additions and 69 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@ -16,25 +16,19 @@
package org.springframework.web.method;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.OrderUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
/**
@ -59,11 +53,7 @@ public class ControllerAdviceBean implements Ordered {
private final int order;
private final Set<String> basePackages;
private final List<Class<?>> assignableTypes;
private final List<Class<? extends Annotation>> annotations;
private final HandlerTypePredicate beanTypePredicate;
/**
@ -109,14 +99,15 @@ public class ControllerAdviceBean implements Ordered {
AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class) : null);
if (annotation != null) {
this.basePackages = initBasePackages(annotation);
this.assignableTypes = Arrays.asList(annotation.assignableTypes());
this.annotations = Arrays.asList(annotation.annotations());
this.beanTypePredicate = HandlerTypePredicate.builder()
.basePackage(annotation.basePackages())
.basePackageClass(annotation.basePackageClasses())
.assignableType(annotation.assignableTypes())
.annotation(annotation.annotations())
.build();
}
else {
this.basePackages = Collections.emptySet();
this.assignableTypes = Collections.emptyList();
this.annotations = Collections.emptyList();
this.beanTypePredicate = HandlerTypePredicate.forAnyHandlerType();
}
}
@ -162,31 +153,7 @@ public class ControllerAdviceBean implements Ordered {
* @since 4.0
*/
public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
if (!hasSelectors()) {
return true;
}
else if (beanType != null) {
for (String basePackage : this.basePackages) {
if (beanType.getName().startsWith(basePackage)) {
return true;
}
}
for (Class<?> clazz : this.assignableTypes) {
if (ClassUtils.isAssignable(clazz, beanType)) {
return true;
}
}
for (Class<? extends Annotation> annotationClass : this.annotations) {
if (AnnotationUtils.findAnnotation(beanType, annotationClass) != null) {
return true;
}
}
}
return false;
}
private boolean hasSelectors() {
return (!this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty());
return this.beanTypePredicate.test(beanType);
}
@ -218,14 +185,11 @@ public class ControllerAdviceBean implements Ordered {
* {@linkplain ControllerAdvice @ControllerAdvice} in the given
* ApplicationContext and wrap them as {@code ControllerAdviceBean} instances.
*/
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext applicationContext) {
List<ControllerAdviceBean> beans = new ArrayList<>();
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class)) {
if (applicationContext.findAnnotationOnBean(name, ControllerAdvice.class) != null) {
beans.add(new ControllerAdviceBean(name, applicationContext));
}
}
return beans;
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
return Arrays.stream(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class))
.filter(name -> context.findAnnotationOnBean(name, ControllerAdvice.class) != null)
.map(name -> new ControllerAdviceBean(name, context))
.collect(Collectors.toList());
}
private static int initOrderFromBean(Object bean) {
@ -240,21 +204,4 @@ public class ControllerAdviceBean implements Ordered {
return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
}
private static Set<String> initBasePackages(ControllerAdvice annotation) {
Set<String> basePackages = new LinkedHashSet<>();
for (String basePackage : annotation.basePackages()) {
if (StringUtils.hasText(basePackage)) {
basePackages.add(adaptBasePackage(basePackage));
}
}
for (Class<?> markerClass : annotation.basePackageClasses()) {
basePackages.add(adaptBasePackage(ClassUtils.getPackageName(markerClass)));
}
return basePackages;
}
private static String adaptBasePackage(String basePackage) {
return (basePackage.endsWith(".") ? basePackage : basePackage + ".");
}
}

View File

@ -0,0 +1,201 @@
/*
* Copyright 2002-2018 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.method;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* A {@code Predicate} to match request handling component types based on the
* following selectors:
* <ul>
* <li>Base packages -- for selecting handlers by their package.
* <li>Assignable types -- for selecting handlers by super type.
* <li>Annotations -- for selecting handlers annotated in a specific way.
* </ul>
* <p>Use static factory methods in this class to create a Predicate.
*
* @author Rossen Stoyanchev
* @since 5.1
*/
public class HandlerTypePredicate implements Predicate<Class<?>> {
private final Set<String> basePackages;
private final List<Class<?>> assignableTypes;
private final List<Class<? extends Annotation>> annotations;
/**
* Private constructor. See static factory methods.
*/
private HandlerTypePredicate(Set<String> basePackages, List<Class<?>> assignableTypes,
List<Class<? extends Annotation>> annotations) {
this.basePackages = Collections.unmodifiableSet(basePackages);
this.assignableTypes = Collections.unmodifiableList(assignableTypes);
this.annotations = Collections.unmodifiableList(annotations);
}
@Override
public boolean test(Class<?> controllerType) {
if (!hasSelectors()) {
return true;
}
else if (controllerType != null) {
for (String basePackage : this.basePackages) {
if (controllerType.getName().startsWith(basePackage)) {
return true;
}
}
for (Class<?> clazz : this.assignableTypes) {
if (ClassUtils.isAssignable(clazz, controllerType)) {
return true;
}
}
for (Class<? extends Annotation> annotationClass : this.annotations) {
if (AnnotationUtils.findAnnotation(controllerType, annotationClass) != null) {
return true;
}
}
}
return false;
}
private boolean hasSelectors() {
return !this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty();
}
/**
* {@code Predicate} that applies to any handlers.
*/
public static HandlerTypePredicate forAnyHandlerType() {
return new HandlerTypePredicate(
Collections.emptySet(), Collections.emptyList(), Collections.emptyList());
}
/**
* Match handlers declared under a base package, e.g. "org.example".
* @param packages one or more base package names
*/
public static HandlerTypePredicate forBasePackage(String... packages) {
return new Builder().basePackage(packages).build();
}
/**
* Type-safe alternative to {@link #forBasePackage(String...)} to specify a
* base package through a class.
* @param packageClasses one or more base package classes
*/
public static HandlerTypePredicate forBasePackageClass(Class<?>... packageClasses) {
return new Builder().basePackageClass(packageClasses).build();
}
/**
* Match handlers that are assignable to a given type.
* @param types one or more handler super types
*/
public static HandlerTypePredicate forAssignableType(Class<?>... types) {
return new Builder().assignableType(types).build();
}
/**
* Match handlers annotated with a specific annotation.
* @param annotations one or more annotations to check for
*/
@SafeVarargs
public static HandlerTypePredicate forAnnotation(Class<? extends Annotation>... annotations) {
return new Builder().annotation(annotations).build();
}
/**
* Return a builder for a {@code HandlerTypePredicate}.
*/
public static Builder builder() {
return new Builder();
}
public static class Builder {
private final Set<String> basePackages = new LinkedHashSet<>();
private final List<Class<?>> assignableTypes = new ArrayList<>();
private final List<Class<? extends Annotation>> annotations = new ArrayList<>();
/**
* Match handlers declared under a base package, e.g. "org.example".
* @param packages one or more base package classes
*/
public Builder basePackage(String... packages) {
Arrays.stream(packages).filter(StringUtils::hasText).forEach(this::addBasePackage);
return this;
}
/**
* Type-safe alternative to {@link #forBasePackage(String...)} to specify a
* base package through a class.
* @param packageClasses one or more base package names
*/
public Builder basePackageClass(Class<?>... packageClasses) {
Arrays.stream(packageClasses).forEach(clazz -> addBasePackage(ClassUtils.getPackageName(clazz)));
return this;
}
private void addBasePackage(String basePackage) {
this.basePackages.add(basePackage.endsWith(".") ? basePackage : basePackage + ".");
}
/**
* Match handlers that are assignable to a given type.
* @param types one or more handler super types
*/
public Builder assignableType(Class<?>... types) {
this.assignableTypes.addAll(Arrays.asList(types));
return this;
}
/**
* Match types that are annotated with one of the given annotations.
* @param annotations one or more annotations to check for
*/
@SuppressWarnings("unchecked")
public final Builder annotation(Class<? extends Annotation>... annotations) {
this.annotations.addAll(Arrays.asList(annotations));
return this;
}
public HandlerTypePredicate build() {
return new HandlerTypePredicate(this.basePackages, this.assignableTypes, this.annotations);
}
}
}