001/*
002 * Copyright 2002-2017 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.LinkedHashSet;
022import java.util.LinkedList;
023import java.util.List;
024import java.util.Set;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028
029import org.springframework.beans.factory.BeanDefinitionStoreException;
030import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
031import org.springframework.beans.factory.annotation.Lookup;
032import org.springframework.beans.factory.config.BeanDefinition;
033import org.springframework.beans.factory.support.BeanDefinitionRegistry;
034import org.springframework.context.ResourceLoaderAware;
035import org.springframework.core.env.Environment;
036import org.springframework.core.env.EnvironmentCapable;
037import org.springframework.core.env.StandardEnvironment;
038import org.springframework.core.io.Resource;
039import org.springframework.core.io.ResourceLoader;
040import org.springframework.core.io.support.ResourcePatternResolver;
041import org.springframework.core.io.support.ResourcePatternUtils;
042import org.springframework.core.type.AnnotationMetadata;
043import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
044import org.springframework.core.type.classreading.MetadataReader;
045import org.springframework.core.type.classreading.MetadataReaderFactory;
046import org.springframework.core.type.filter.AnnotationTypeFilter;
047import org.springframework.core.type.filter.TypeFilter;
048import org.springframework.stereotype.Component;
049import org.springframework.stereotype.Controller;
050import org.springframework.stereotype.Repository;
051import org.springframework.stereotype.Service;
052import org.springframework.util.Assert;
053import org.springframework.util.ClassUtils;
054
055/**
056 * A component provider that scans the classpath from a base package. It then
057 * applies exclude and include filters to the resulting classes to find candidates.
058 *
059 * <p>This implementation is based on Spring's
060 * {@link org.springframework.core.type.classreading.MetadataReader MetadataReader}
061 * facility, backed by an ASM {@link org.springframework.asm.ClassReader ClassReader}.
062 *
063 * @author Mark Fisher
064 * @author Juergen Hoeller
065 * @author Ramnivas Laddad
066 * @author Chris Beams
067 * @since 2.5
068 * @see org.springframework.core.type.classreading.MetadataReaderFactory
069 * @see org.springframework.core.type.AnnotationMetadata
070 * @see ScannedGenericBeanDefinition
071 */
072public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
073
074        static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
075
076
077        protected final Log logger = LogFactory.getLog(getClass());
078
079        private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
080
081        private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
082
083        private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
084
085        private Environment environment;
086
087        private ConditionEvaluator conditionEvaluator;
088
089        private ResourcePatternResolver resourcePatternResolver;
090
091        private MetadataReaderFactory metadataReaderFactory;
092
093
094        /**
095         * Protected constructor for flexible subclass initialization.
096         * @since 4.3.6
097         */
098        protected ClassPathScanningCandidateComponentProvider() {
099        }
100
101        /**
102         * Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}.
103         * @param useDefaultFilters whether to register the default filters for the
104         * {@link Component @Component}, {@link Repository @Repository},
105         * {@link Service @Service}, and {@link Controller @Controller}
106         * stereotype annotations
107         * @see #registerDefaultFilters()
108         */
109        public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
110                this(useDefaultFilters, new StandardEnvironment());
111        }
112
113        /**
114         * Create a ClassPathScanningCandidateComponentProvider with the given {@link Environment}.
115         * @param useDefaultFilters whether to register the default filters for the
116         * {@link Component @Component}, {@link Repository @Repository},
117         * {@link Service @Service}, and {@link Controller @Controller}
118         * stereotype annotations
119         * @param environment the Environment to use
120         * @see #registerDefaultFilters()
121         */
122        public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) {
123                if (useDefaultFilters) {
124                        registerDefaultFilters();
125                }
126                setEnvironment(environment);
127                setResourceLoader(null);
128        }
129
130
131        /**
132         * Set the resource pattern to use when scanning the classpath.
133         * This value will be appended to each base package name.
134         * @see #findCandidateComponents(String)
135         * @see #DEFAULT_RESOURCE_PATTERN
136         */
137        public void setResourcePattern(String resourcePattern) {
138                Assert.notNull(resourcePattern, "'resourcePattern' must not be null");
139                this.resourcePattern = resourcePattern;
140        }
141
142        /**
143         * Add an include type filter to the <i>end</i> of the inclusion list.
144         */
145        public void addIncludeFilter(TypeFilter includeFilter) {
146                this.includeFilters.add(includeFilter);
147        }
148
149        /**
150         * Add an exclude type filter to the <i>front</i> of the exclusion list.
151         */
152        public void addExcludeFilter(TypeFilter excludeFilter) {
153                this.excludeFilters.add(0, excludeFilter);
154        }
155
156        /**
157         * Reset the configured type filters.
158         * @param useDefaultFilters whether to re-register the default filters for
159         * the {@link Component @Component}, {@link Repository @Repository},
160         * {@link Service @Service}, and {@link Controller @Controller}
161         * stereotype annotations
162         * @see #registerDefaultFilters()
163         */
164        public void resetFilters(boolean useDefaultFilters) {
165                this.includeFilters.clear();
166                this.excludeFilters.clear();
167                if (useDefaultFilters) {
168                        registerDefaultFilters();
169                }
170        }
171
172        /**
173         * Register the default filter for {@link Component @Component}.
174         * <p>This will implicitly register all annotations that have the
175         * {@link Component @Component} meta-annotation including the
176         * {@link Repository @Repository}, {@link Service @Service}, and
177         * {@link Controller @Controller} stereotype annotations.
178         * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
179         * JSR-330's {@link javax.inject.Named} annotations, if available.
180         *
181         */
182        @SuppressWarnings("unchecked")
183        protected void registerDefaultFilters() {
184                this.includeFilters.add(new AnnotationTypeFilter(Component.class));
185                ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
186                try {
187                        this.includeFilters.add(new AnnotationTypeFilter(
188                                        ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
189                        logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
190                }
191                catch (ClassNotFoundException ex) {
192                        // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
193                }
194                try {
195                        this.includeFilters.add(new AnnotationTypeFilter(
196                                        ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
197                        logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
198                }
199                catch (ClassNotFoundException ex) {
200                        // JSR-330 API not available - simply skip.
201                }
202        }
203
204        /**
205         * Set the Environment to use when resolving placeholders and evaluating
206         * {@link Conditional @Conditional}-annotated component classes.
207         * <p>The default is a {@link StandardEnvironment}.
208         * @param environment the Environment to use
209         */
210        public void setEnvironment(Environment environment) {
211                Assert.notNull(environment, "Environment must not be null");
212                this.environment = environment;
213                this.conditionEvaluator = null;
214        }
215
216        @Override
217        public final Environment getEnvironment() {
218                return this.environment;
219        }
220
221        /**
222         * Return the {@link BeanDefinitionRegistry} used by this scanner, if any.
223         */
224        protected BeanDefinitionRegistry getRegistry() {
225                return null;
226        }
227
228        /**
229         * Set the {@link ResourceLoader} to use for resource locations.
230         * This will typically be a {@link ResourcePatternResolver} implementation.
231         * <p>Default is a {@code PathMatchingResourcePatternResolver}, also capable of
232         * resource pattern resolving through the {@code ResourcePatternResolver} interface.
233         * @see org.springframework.core.io.support.ResourcePatternResolver
234         * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver
235         */
236        @Override
237        public void setResourceLoader(ResourceLoader resourceLoader) {
238                this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
239                this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
240        }
241
242        /**
243         * Return the ResourceLoader that this component provider uses.
244         */
245        public final ResourceLoader getResourceLoader() {
246                return this.resourcePatternResolver;
247        }
248
249        /**
250         * Set the {@link MetadataReaderFactory} to use.
251         * <p>Default is a {@link CachingMetadataReaderFactory} for the specified
252         * {@linkplain #setResourceLoader resource loader}.
253         * <p>Call this setter method <i>after</i> {@link #setResourceLoader} in order
254         * for the given MetadataReaderFactory to override the default factory.
255         */
256        public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) {
257                this.metadataReaderFactory = metadataReaderFactory;
258        }
259
260        /**
261         * Return the MetadataReaderFactory used by this component provider.
262         */
263        public final MetadataReaderFactory getMetadataReaderFactory() {
264                return this.metadataReaderFactory;
265        }
266
267
268        /**
269         * Scan the class path for candidate components.
270         * @param basePackage the package to check for annotated classes
271         * @return a corresponding Set of autodetected bean definitions
272         */
273        public Set<BeanDefinition> findCandidateComponents(String basePackage) {
274                Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
275                try {
276                        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
277                                        resolveBasePackage(basePackage) + '/' + this.resourcePattern;
278                        Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
279                        boolean traceEnabled = logger.isTraceEnabled();
280                        boolean debugEnabled = logger.isDebugEnabled();
281                        for (Resource resource : resources) {
282                                if (traceEnabled) {
283                                        logger.trace("Scanning " + resource);
284                                }
285                                if (resource.isReadable()) {
286                                        try {
287                                                MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
288                                                if (isCandidateComponent(metadataReader)) {
289                                                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
290                                                        sbd.setResource(resource);
291                                                        sbd.setSource(resource);
292                                                        if (isCandidateComponent(sbd)) {
293                                                                if (debugEnabled) {
294                                                                        logger.debug("Identified candidate component class: " + resource);
295                                                                }
296                                                                candidates.add(sbd);
297                                                        }
298                                                        else {
299                                                                if (debugEnabled) {
300                                                                        logger.debug("Ignored because not a concrete top-level class: " + resource);
301                                                                }
302                                                        }
303                                                }
304                                                else {
305                                                        if (traceEnabled) {
306                                                                logger.trace("Ignored because not matching any filter: " + resource);
307                                                        }
308                                                }
309                                        }
310                                        catch (Throwable ex) {
311                                                throw new BeanDefinitionStoreException(
312                                                                "Failed to read candidate component class: " + resource, ex);
313                                        }
314                                }
315                                else {
316                                        if (traceEnabled) {
317                                                logger.trace("Ignored because not readable: " + resource);
318                                        }
319                                }
320                        }
321                }
322                catch (IOException ex) {
323                        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
324                }
325                return candidates;
326        }
327
328
329        /**
330         * Resolve the specified base package into a pattern specification for
331         * the package search path.
332         * <p>The default implementation resolves placeholders against system properties,
333         * and converts a "."-based package path to a "/"-based resource path.
334         * @param basePackage the base package as specified by the user
335         * @return the pattern specification to be used for package searching
336         */
337        protected String resolveBasePackage(String basePackage) {
338                return ClassUtils.convertClassNameToResourcePath(this.environment.resolveRequiredPlaceholders(basePackage));
339        }
340
341        /**
342         * Determine whether the given class does not match any exclude filter
343         * and does match at least one include filter.
344         * @param metadataReader the ASM ClassReader for the class
345         * @return whether the class qualifies as a candidate component
346         */
347        protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
348                for (TypeFilter tf : this.excludeFilters) {
349                        if (tf.match(metadataReader, this.metadataReaderFactory)) {
350                                return false;
351                        }
352                }
353                for (TypeFilter tf : this.includeFilters) {
354                        if (tf.match(metadataReader, this.metadataReaderFactory)) {
355                                return isConditionMatch(metadataReader);
356                        }
357                }
358                return false;
359        }
360
361        /**
362         * Determine whether the given class is a candidate component based on any
363         * {@code @Conditional} annotations.
364         * @param metadataReader the ASM ClassReader for the class
365         * @return whether the class qualifies as a candidate component
366         */
367        private boolean isConditionMatch(MetadataReader metadataReader) {
368                if (this.conditionEvaluator == null) {
369                        this.conditionEvaluator = new ConditionEvaluator(getRegistry(), getEnvironment(), getResourceLoader());
370                }
371                return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
372        }
373
374        /**
375         * Determine whether the given bean definition qualifies as candidate.
376         * <p>The default implementation checks whether the class is not an interface
377         * and not dependent on an enclosing class.
378         * <p>Can be overridden in subclasses.
379         * @param beanDefinition the bean definition to check
380         * @return whether the bean definition qualifies as a candidate component
381         */
382        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
383                AnnotationMetadata metadata = beanDefinition.getMetadata();
384                return (metadata.isIndependent() && (metadata.isConcrete() ||
385                                (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
386        }
387
388
389        /**
390         * Clear the underlying metadata cache, removing all cached class metadata.
391         */
392        public void clearCache() {
393                if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
394                        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
395                }
396        }
397
398}