001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.context.annotation;
018
019import java.io.IOException;
020import java.lang.annotation.Annotation;
021import java.util.HashSet;
022import java.util.LinkedHashSet;
023import java.util.LinkedList;
024import java.util.List;
025import java.util.Set;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.beans.factory.BeanDefinitionStoreException;
031import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
032import org.springframework.beans.factory.annotation.Lookup;
033import org.springframework.beans.factory.config.BeanDefinition;
034import org.springframework.beans.factory.support.BeanDefinitionRegistry;
035import org.springframework.context.ResourceLoaderAware;
036import org.springframework.context.index.CandidateComponentsIndex;
037import org.springframework.context.index.CandidateComponentsIndexLoader;
038import org.springframework.core.annotation.AnnotationUtils;
039import org.springframework.core.env.Environment;
040import org.springframework.core.env.EnvironmentCapable;
041import org.springframework.core.env.StandardEnvironment;
042import org.springframework.core.io.Resource;
043import org.springframework.core.io.ResourceLoader;
044import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
045import org.springframework.core.io.support.ResourcePatternResolver;
046import org.springframework.core.io.support.ResourcePatternUtils;
047import org.springframework.core.type.AnnotationMetadata;
048import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
049import org.springframework.core.type.classreading.MetadataReader;
050import org.springframework.core.type.classreading.MetadataReaderFactory;
051import org.springframework.core.type.filter.AnnotationTypeFilter;
052import org.springframework.core.type.filter.AssignableTypeFilter;
053import org.springframework.core.type.filter.TypeFilter;
054import org.springframework.lang.Nullable;
055import org.springframework.stereotype.Component;
056import org.springframework.stereotype.Controller;
057import org.springframework.stereotype.Indexed;
058import org.springframework.stereotype.Repository;
059import org.springframework.stereotype.Service;
060import org.springframework.util.Assert;
061import org.springframework.util.ClassUtils;
062
063/**
064 * A component provider that provides candidate components from a base package. Can
065 * use {@link CandidateComponentsIndex the index} if it is available of scans the
066 * classpath otherwise. Candidate components are identified by applying exclude and
067 * include filters. {@link AnnotationTypeFilter}, {@link AssignableTypeFilter} include
068 * filters on an annotation/superclass that are annotated with {@link Indexed} are
069 * supported: if any other include filter is specified, the index is ignored and
070 * classpath scanning is used instead.
071 *
072 * <p>This implementation is based on Spring's
073 * {@link org.springframework.core.type.classreading.MetadataReader MetadataReader}
074 * facility, backed by an ASM {@link org.springframework.asm.ClassReader ClassReader}.
075 *
076 * @author Mark Fisher
077 * @author Juergen Hoeller
078 * @author Ramnivas Laddad
079 * @author Chris Beams
080 * @author Stephane Nicoll
081 * @since 2.5
082 * @see org.springframework.core.type.classreading.MetadataReaderFactory
083 * @see org.springframework.core.type.AnnotationMetadata
084 * @see ScannedGenericBeanDefinition
085 * @see CandidateComponentsIndex
086 */
087public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
088
089        static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
090
091
092        protected final Log logger = LogFactory.getLog(getClass());
093
094        private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
095
096        private final List<TypeFilter> includeFilters = new LinkedList<>();
097
098        private final List<TypeFilter> excludeFilters = new LinkedList<>();
099
100        @Nullable
101        private Environment environment;
102
103        @Nullable
104        private ConditionEvaluator conditionEvaluator;
105
106        @Nullable
107        private ResourcePatternResolver resourcePatternResolver;
108
109        @Nullable
110        private MetadataReaderFactory metadataReaderFactory;
111
112        @Nullable
113        private CandidateComponentsIndex componentsIndex;
114
115
116        /**
117         * Protected constructor for flexible subclass initialization.
118         * @since 4.3.6
119         */
120        protected ClassPathScanningCandidateComponentProvider() {
121        }
122
123        /**
124         * Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}.
125         * @param useDefaultFilters whether to register the default filters for the
126         * {@link Component @Component}, {@link Repository @Repository},
127         * {@link Service @Service}, and {@link Controller @Controller}
128         * stereotype annotations
129         * @see #registerDefaultFilters()
130         */
131        public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
132                this(useDefaultFilters, new StandardEnvironment());
133        }
134
135        /**
136         * Create a ClassPathScanningCandidateComponentProvider with the given {@link Environment}.
137         * @param useDefaultFilters whether to register the default filters for the
138         * {@link Component @Component}, {@link Repository @Repository},
139         * {@link Service @Service}, and {@link Controller @Controller}
140         * stereotype annotations
141         * @param environment the Environment to use
142         * @see #registerDefaultFilters()
143         */
144        public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) {
145                if (useDefaultFilters) {
146                        registerDefaultFilters();
147                }
148                setEnvironment(environment);
149                setResourceLoader(null);
150        }
151
152
153        /**
154         * Set the resource pattern to use when scanning the classpath.
155         * This value will be appended to each base package name.
156         * @see #findCandidateComponents(String)
157         * @see #DEFAULT_RESOURCE_PATTERN
158         */
159        public void setResourcePattern(String resourcePattern) {
160                Assert.notNull(resourcePattern, "'resourcePattern' must not be null");
161                this.resourcePattern = resourcePattern;
162        }
163
164        /**
165         * Add an include type filter to the <i>end</i> of the inclusion list.
166         */
167        public void addIncludeFilter(TypeFilter includeFilter) {
168                this.includeFilters.add(includeFilter);
169        }
170
171        /**
172         * Add an exclude type filter to the <i>front</i> of the exclusion list.
173         */
174        public void addExcludeFilter(TypeFilter excludeFilter) {
175                this.excludeFilters.add(0, excludeFilter);
176        }
177
178        /**
179         * Reset the configured type filters.
180         * @param useDefaultFilters whether to re-register the default filters for
181         * the {@link Component @Component}, {@link Repository @Repository},
182         * {@link Service @Service}, and {@link Controller @Controller}
183         * stereotype annotations
184         * @see #registerDefaultFilters()
185         */
186        public void resetFilters(boolean useDefaultFilters) {
187                this.includeFilters.clear();
188                this.excludeFilters.clear();
189                if (useDefaultFilters) {
190                        registerDefaultFilters();
191                }
192        }
193
194        /**
195         * Register the default filter for {@link Component @Component}.
196         * <p>This will implicitly register all annotations that have the
197         * {@link Component @Component} meta-annotation including the
198         * {@link Repository @Repository}, {@link Service @Service}, and
199         * {@link Controller @Controller} stereotype annotations.
200         * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
201         * JSR-330's {@link javax.inject.Named} annotations, if available.
202         *
203         */
204        @SuppressWarnings("unchecked")
205        protected void registerDefaultFilters() {
206                this.includeFilters.add(new AnnotationTypeFilter(Component.class));
207                ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
208                try {
209                        this.includeFilters.add(new AnnotationTypeFilter(
210                                        ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
211                        logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
212                }
213                catch (ClassNotFoundException ex) {
214                        // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
215                }
216                try {
217                        this.includeFilters.add(new AnnotationTypeFilter(
218                                        ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
219                        logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
220                }
221                catch (ClassNotFoundException ex) {
222                        // JSR-330 API not available - simply skip.
223                }
224        }
225
226        /**
227         * Set the Environment to use when resolving placeholders and evaluating
228         * {@link Conditional @Conditional}-annotated component classes.
229         * <p>The default is a {@link StandardEnvironment}.
230         * @param environment the Environment to use
231         */
232        public void setEnvironment(Environment environment) {
233                Assert.notNull(environment, "Environment must not be null");
234                this.environment = environment;
235                this.conditionEvaluator = null;
236        }
237
238        @Override
239        public final Environment getEnvironment() {
240                if (this.environment == null) {
241                        this.environment = new StandardEnvironment();
242                }
243                return this.environment;
244        }
245
246        /**
247         * Return the {@link BeanDefinitionRegistry} used by this scanner, if any.
248         */
249        @Nullable
250        protected BeanDefinitionRegistry getRegistry() {
251                return null;
252        }
253
254        /**
255         * Set the {@link ResourceLoader} to use for resource locations.
256         * This will typically be a {@link ResourcePatternResolver} implementation.
257         * <p>Default is a {@code PathMatchingResourcePatternResolver}, also capable of
258         * resource pattern resolving through the {@code ResourcePatternResolver} interface.
259         * @see org.springframework.core.io.support.ResourcePatternResolver
260         * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver
261         */
262        @Override
263        public void setResourceLoader(@Nullable ResourceLoader resourceLoader) {
264                this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
265                this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
266                this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader());
267        }
268
269        /**
270         * Return the ResourceLoader that this component provider uses.
271         */
272        public final ResourceLoader getResourceLoader() {
273                return getResourcePatternResolver();
274        }
275
276        private ResourcePatternResolver getResourcePatternResolver() {
277                if (this.resourcePatternResolver == null) {
278                        this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
279                }
280                return this.resourcePatternResolver;
281        }
282
283        /**
284         * Set the {@link MetadataReaderFactory} to use.
285         * <p>Default is a {@link CachingMetadataReaderFactory} for the specified
286         * {@linkplain #setResourceLoader resource loader}.
287         * <p>Call this setter method <i>after</i> {@link #setResourceLoader} in order
288         * for the given MetadataReaderFactory to override the default factory.
289         */
290        public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) {
291                this.metadataReaderFactory = metadataReaderFactory;
292        }
293
294        /**
295         * Return the MetadataReaderFactory used by this component provider.
296         */
297        public final MetadataReaderFactory getMetadataReaderFactory() {
298                if (this.metadataReaderFactory == null) {
299                        this.metadataReaderFactory = new CachingMetadataReaderFactory();
300                }
301                return this.metadataReaderFactory;
302        }
303
304
305        /**
306         * Scan the class path for candidate components.
307         * @param basePackage the package to check for annotated classes
308         * @return a corresponding Set of autodetected bean definitions
309         */
310        public Set<BeanDefinition> findCandidateComponents(String basePackage) {
311                if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
312                        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
313                }
314                else {
315                        return scanCandidateComponents(basePackage);
316                }
317        }
318
319        /**
320         * Determine if the index can be used by this instance.
321         * @return {@code true} if the index is available and the configuration of this
322         * instance is supported by it, {@code false} otherwise
323         * @since 5.0
324         */
325        private boolean indexSupportsIncludeFilters() {
326                for (TypeFilter includeFilter : this.includeFilters) {
327                        if (!indexSupportsIncludeFilter(includeFilter)) {
328                                return false;
329                        }
330                }
331                return true;
332        }
333
334        /**
335         * Determine if the specified include {@link TypeFilter} is supported by the index.
336         * @param filter the filter to check
337         * @return whether the index supports this include filter
338         * @since 5.0
339         * @see #extractStereotype(TypeFilter)
340         */
341        private boolean indexSupportsIncludeFilter(TypeFilter filter) {
342                if (filter instanceof AnnotationTypeFilter) {
343                        Class<? extends Annotation> annotation = ((AnnotationTypeFilter) filter).getAnnotationType();
344                        return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) ||
345                                        annotation.getName().startsWith("javax."));
346                }
347                if (filter instanceof AssignableTypeFilter) {
348                        Class<?> target = ((AssignableTypeFilter) filter).getTargetType();
349                        return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target);
350                }
351                return false;
352        }
353
354        /**
355         * Extract the stereotype to use for the specified compatible filter.
356         * @param filter the filter to handle
357         * @return the stereotype in the index matching this filter
358         * @since 5.0
359         * @see #indexSupportsIncludeFilter(TypeFilter)
360         */
361        @Nullable
362        private String extractStereotype(TypeFilter filter) {
363                if (filter instanceof AnnotationTypeFilter) {
364                        return ((AnnotationTypeFilter) filter).getAnnotationType().getName();
365                }
366                if (filter instanceof AssignableTypeFilter) {
367                        return ((AssignableTypeFilter) filter).getTargetType().getName();
368                }
369                return null;
370        }
371
372        private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) {
373                Set<BeanDefinition> candidates = new LinkedHashSet<>();
374                try {
375                        Set<String> types = new HashSet<>();
376                        for (TypeFilter filter : this.includeFilters) {
377                                String stereotype = extractStereotype(filter);
378                                if (stereotype == null) {
379                                        throw new IllegalArgumentException("Failed to extract stereotype from " + filter);
380                                }
381                                types.addAll(index.getCandidateTypes(basePackage, stereotype));
382                        }
383                        boolean traceEnabled = logger.isTraceEnabled();
384                        boolean debugEnabled = logger.isDebugEnabled();
385                        for (String type : types) {
386                                MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(type);
387                                if (isCandidateComponent(metadataReader)) {
388                                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
389                                        sbd.setSource(metadataReader.getResource());
390                                        if (isCandidateComponent(sbd)) {
391                                                if (debugEnabled) {
392                                                        logger.debug("Using candidate component class from index: " + type);
393                                                }
394                                                candidates.add(sbd);
395                                        }
396                                        else {
397                                                if (debugEnabled) {
398                                                        logger.debug("Ignored because not a concrete top-level class: " + type);
399                                                }
400                                        }
401                                }
402                                else {
403                                        if (traceEnabled) {
404                                                logger.trace("Ignored because matching an exclude filter: " + type);
405                                        }
406                                }
407                        }
408                }
409                catch (IOException ex) {
410                        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
411                }
412                return candidates;
413        }
414
415        private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
416                Set<BeanDefinition> candidates = new LinkedHashSet<>();
417                try {
418                        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
419                                        resolveBasePackage(basePackage) + '/' + this.resourcePattern;
420                        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
421                        boolean traceEnabled = logger.isTraceEnabled();
422                        boolean debugEnabled = logger.isDebugEnabled();
423                        for (Resource resource : resources) {
424                                if (traceEnabled) {
425                                        logger.trace("Scanning " + resource);
426                                }
427                                if (resource.isReadable()) {
428                                        try {
429                                                MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
430                                                if (isCandidateComponent(metadataReader)) {
431                                                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
432                                                        sbd.setSource(resource);
433                                                        if (isCandidateComponent(sbd)) {
434                                                                if (debugEnabled) {
435                                                                        logger.debug("Identified candidate component class: " + resource);
436                                                                }
437                                                                candidates.add(sbd);
438                                                        }
439                                                        else {
440                                                                if (debugEnabled) {
441                                                                        logger.debug("Ignored because not a concrete top-level class: " + resource);
442                                                                }
443                                                        }
444                                                }
445                                                else {
446                                                        if (traceEnabled) {
447                                                                logger.trace("Ignored because not matching any filter: " + resource);
448                                                        }
449                                                }
450                                        }
451                                        catch (Throwable ex) {
452                                                throw new BeanDefinitionStoreException(
453                                                                "Failed to read candidate component class: " + resource, ex);
454                                        }
455                                }
456                                else {
457                                        if (traceEnabled) {
458                                                logger.trace("Ignored because not readable: " + resource);
459                                        }
460                                }
461                        }
462                }
463                catch (IOException ex) {
464                        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
465                }
466                return candidates;
467        }
468
469
470        /**
471         * Resolve the specified base package into a pattern specification for
472         * the package search path.
473         * <p>The default implementation resolves placeholders against system properties,
474         * and converts a "."-based package path to a "/"-based resource path.
475         * @param basePackage the base package as specified by the user
476         * @return the pattern specification to be used for package searching
477         */
478        protected String resolveBasePackage(String basePackage) {
479                return ClassUtils.convertClassNameToResourcePath(getEnvironment().resolveRequiredPlaceholders(basePackage));
480        }
481
482        /**
483         * Determine whether the given class does not match any exclude filter
484         * and does match at least one include filter.
485         * @param metadataReader the ASM ClassReader for the class
486         * @return whether the class qualifies as a candidate component
487         */
488        protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
489                for (TypeFilter tf : this.excludeFilters) {
490                        if (tf.match(metadataReader, getMetadataReaderFactory())) {
491                                return false;
492                        }
493                }
494                for (TypeFilter tf : this.includeFilters) {
495                        if (tf.match(metadataReader, getMetadataReaderFactory())) {
496                                return isConditionMatch(metadataReader);
497                        }
498                }
499                return false;
500        }
501
502        /**
503         * Determine whether the given class is a candidate component based on any
504         * {@code @Conditional} annotations.
505         * @param metadataReader the ASM ClassReader for the class
506         * @return whether the class qualifies as a candidate component
507         */
508        private boolean isConditionMatch(MetadataReader metadataReader) {
509                if (this.conditionEvaluator == null) {
510                        this.conditionEvaluator =
511                                        new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
512                }
513                return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
514        }
515
516        /**
517         * Determine whether the given bean definition qualifies as candidate.
518         * <p>The default implementation checks whether the class is not an interface
519         * and not dependent on an enclosing class.
520         * <p>Can be overridden in subclasses.
521         * @param beanDefinition the bean definition to check
522         * @return whether the bean definition qualifies as a candidate component
523         */
524        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
525                AnnotationMetadata metadata = beanDefinition.getMetadata();
526                return (metadata.isIndependent() && (metadata.isConcrete() ||
527                                (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
528        }
529
530
531        /**
532         * Clear the local metadata cache, if any, removing all cached class metadata.
533         */
534        public void clearCache() {
535                if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
536                        // Clear cache in externally provided MetadataReaderFactory; this is a no-op
537                        // for a shared cache since it'll be cleared by the ApplicationContext.
538                        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
539                }
540        }
541
542}