From 7a54ff2d21ad7e1900ce603ea1a814d8021bacb9 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 21 Oct 2021 14:07:17 +0200 Subject: [PATCH] Extract public TypeFilterUtils from ComponentScanAnnotationParser Prior to this commit, third parties using @ComponentScan's @Filter annotation had to implement their own parsing for @Filter AnnotationAttributes as well as instantiation of the corresponding TypeFilters. In such cases the various *Aware callbacks (BeanFactoryAware, EnvironmentAware, etc.) should also be supported. This commit therefore extracts a new public TypeFilterUtils class from ComponentScanAnnotationParser so that third parties can benefit from consistent TypeFilter creation from @ComponentScan @Filter annotations. Closes gh-27553 --- .../ComponentScanAnnotationParser.java | 65 ++-------- .../context/annotation/TypeFilterUtils.java | 119 ++++++++++++++++++ 2 files changed, 127 insertions(+), 57 deletions(-) create mode 100644 spring-context/src/main/java/org/springframework/context/annotation/TypeFilterUtils.java diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java index 90575862c6bd..27e879bfb338 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java @@ -16,13 +16,10 @@ package org.springframework.context.annotation; -import java.lang.annotation.Annotation; -import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import java.util.regex.Pattern; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -33,12 +30,7 @@ import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter; -import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.core.type.filter.AspectJTypeFilter; -import org.springframework.core.type.filter.AssignableTypeFilter; -import org.springframework.core.type.filter.RegexPatternTypeFilter; import org.springframework.core.type.filter.TypeFilter; -import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -93,13 +85,17 @@ public Set parse(AnnotationAttributes componentScan, final scanner.setResourcePattern(componentScan.getString("resourcePattern")); - for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { - for (TypeFilter typeFilter : typeFiltersFor(filter)) { + for (AnnotationAttributes includeFilterAttributes : componentScan.getAnnotationArray("includeFilters")) { + List typeFilters = TypeFilterUtils.createTypeFiltersFor(includeFilterAttributes, this.environment, + this.resourceLoader, this.registry); + for (TypeFilter typeFilter : typeFilters) { scanner.addIncludeFilter(typeFilter); } } - for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { - for (TypeFilter typeFilter : typeFiltersFor(filter)) { + for (AnnotationAttributes excludeFilterAttributes : componentScan.getAnnotationArray("excludeFilters")) { + List typeFilters = TypeFilterUtils.createTypeFiltersFor(excludeFilterAttributes, this.environment, + this.resourceLoader, this.registry); + for (TypeFilter typeFilter : typeFilters) { scanner.addExcludeFilter(typeFilter); } } @@ -132,49 +128,4 @@ protected boolean matchClassName(String className) { return scanner.doScan(StringUtils.toStringArray(basePackages)); } - private List typeFiltersFor(AnnotationAttributes filterAttributes) { - List typeFilters = new ArrayList<>(); - FilterType filterType = filterAttributes.getEnum("type"); - - for (Class filterClass : filterAttributes.getClassArray("classes")) { - switch (filterType) { - case ANNOTATION: - Assert.isAssignable(Annotation.class, filterClass, - "@ComponentScan ANNOTATION type filter requires an annotation type"); - @SuppressWarnings("unchecked") - Class annotationType = (Class) filterClass; - typeFilters.add(new AnnotationTypeFilter(annotationType)); - break; - case ASSIGNABLE_TYPE: - typeFilters.add(new AssignableTypeFilter(filterClass)); - break; - case CUSTOM: - Assert.isAssignable(TypeFilter.class, filterClass, - "@ComponentScan CUSTOM type filter requires a TypeFilter implementation"); - - TypeFilter filter = ParserStrategyUtils.instantiateClass(filterClass, TypeFilter.class, - this.environment, this.resourceLoader, this.registry); - typeFilters.add(filter); - break; - default: - throw new IllegalArgumentException("Filter type not supported with Class value: " + filterType); - } - } - - for (String expression : filterAttributes.getStringArray("pattern")) { - switch (filterType) { - case ASPECTJ: - typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader.getClassLoader())); - break; - case REGEX: - typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression))); - break; - default: - throw new IllegalArgumentException("Filter type not supported with String pattern: " + filterType); - } - } - - return typeFilters; - } - } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/TypeFilterUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/TypeFilterUtils.java new file mode 100644 index 000000000000..340c232f60f1 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/TypeFilterUtils.java @@ -0,0 +1,119 @@ +/* + * Copyright 2002-2021 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 + * + * https://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.context.annotation; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.AspectJTypeFilter; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.RegexPatternTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.util.Assert; + +/** + * Collection of utilities for working with {@link ComponentScan @ComponentScan} + * {@linkplain ComponentScan.Filter type filters}. + * + * @author Chris Beams + * @author Juergen Hoeller + * @author Sam Brannen + * @since 5.3.13 + * @see ComponentScan.Filter + * @see org.springframework.core.type.filter.TypeFilter + */ +public abstract class TypeFilterUtils { + + /** + * Create {@linkplain TypeFilter type filters} from the supplied + * {@link AnnotationAttributes}, such as those sourced from + * {@link ComponentScan#includeFilters()} or {@link ComponentScan#excludeFilters()}. + *

Each {@link TypeFilter} will be instantiated using an appropriate + * constructor, with {@code BeanClassLoaderAware}, {@code BeanFactoryAware}, + * {@code EnvironmentAware}, and {@code ResourceLoaderAware} contracts + * invoked if they are implemented by the type filter. + * @param filterAttributes {@code AnnotationAttributes} for a + * {@link ComponentScan.Filter @Filter} declaration + * @param environment the {@code Environment} to make available to filters + * @param resourceLoader the {@code ResourceLoader} to make available to filters + * @param registry the {@code BeanDefinitionRegistry} to make available to filters + * as a {@link org.springframework.beans.factory.BeanFactory} if applicable + * @return a list of instantiated and configured type filters + * @see TypeFilter + * @see AnnotationTypeFilter + * @see AssignableTypeFilter + * @see AspectJTypeFilter + * @see RegexPatternTypeFilter + * @see org.springframework.beans.factory.BeanClassLoaderAware + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.context.EnvironmentAware + * @see org.springframework.context.ResourceLoaderAware + */ + public static List createTypeFiltersFor(AnnotationAttributes filterAttributes, Environment environment, + ResourceLoader resourceLoader, BeanDefinitionRegistry registry) { + + List typeFilters = new ArrayList<>(); + FilterType filterType = filterAttributes.getEnum("type"); + + for (Class filterClass : filterAttributes.getClassArray("classes")) { + switch (filterType) { + case ANNOTATION: + Assert.isAssignable(Annotation.class, filterClass, + "@ComponentScan ANNOTATION type filter requires an annotation type"); + @SuppressWarnings("unchecked") + Class annotationType = (Class) filterClass; + typeFilters.add(new AnnotationTypeFilter(annotationType)); + break; + case ASSIGNABLE_TYPE: + typeFilters.add(new AssignableTypeFilter(filterClass)); + break; + case CUSTOM: + Assert.isAssignable(TypeFilter.class, filterClass, + "@ComponentScan CUSTOM type filter requires a TypeFilter implementation"); + TypeFilter filter = ParserStrategyUtils.instantiateClass(filterClass, TypeFilter.class, + environment, resourceLoader, registry); + typeFilters.add(filter); + break; + default: + throw new IllegalArgumentException("Filter type not supported with Class value: " + filterType); + } + } + + for (String expression : filterAttributes.getStringArray("pattern")) { + switch (filterType) { + case ASPECTJ: + typeFilters.add(new AspectJTypeFilter(expression, resourceLoader.getClassLoader())); + break; + case REGEX: + typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression))); + break; + default: + throw new IllegalArgumentException("Filter type not supported with String pattern: " + filterType); + } + } + + return typeFilters; + } + +}