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 1758bb57d8..4e7c670073 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 @@ -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 basePackages; - - private final List> assignableTypes; - - private final List> 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 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 findAnnotatedBeans(ApplicationContext applicationContext) { - List 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 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 initBasePackages(ControllerAdvice annotation) { - Set 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 + "."); - } - } diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerTypePredicate.java b/spring-web/src/main/java/org/springframework/web/method/HandlerTypePredicate.java new file mode 100644 index 0000000000..914fea9f46 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerTypePredicate.java @@ -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: + *
    + *
  • Base packages -- for selecting handlers by their package. + *
  • Assignable types -- for selecting handlers by super type. + *
  • Annotations -- for selecting handlers annotated in a specific way. + *
+ *

Use static factory methods in this class to create a Predicate. + * + * @author Rossen Stoyanchev + * @since 5.1 + */ +public class HandlerTypePredicate implements Predicate> { + + private final Set basePackages; + + private final List> assignableTypes; + + private final List> annotations; + + + /** + * Private constructor. See static factory methods. + */ + private HandlerTypePredicate(Set basePackages, List> assignableTypes, + List> 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 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... 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 basePackages = new LinkedHashSet<>(); + + private final List> assignableTypes = new ArrayList<>(); + + private final List> 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... annotations) { + this.annotations.addAll(Arrays.asList(annotations)); + return this; + } + + public HandlerTypePredicate build() { + return new HandlerTypePredicate(this.basePackages, this.assignableTypes, this.annotations); + } + } + +}