View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.context.annotation;
18  
19  import java.io.IOException;
20  import java.lang.annotation.Annotation;
21  import java.util.LinkedHashSet;
22  import java.util.LinkedList;
23  import java.util.List;
24  import java.util.Set;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import org.springframework.beans.factory.BeanDefinitionStoreException;
30  import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
31  import org.springframework.beans.factory.config.BeanDefinition;
32  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
33  import org.springframework.context.ResourceLoaderAware;
34  import org.springframework.core.env.Environment;
35  import org.springframework.core.env.EnvironmentCapable;
36  import org.springframework.core.env.StandardEnvironment;
37  import org.springframework.core.io.Resource;
38  import org.springframework.core.io.ResourceLoader;
39  import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
40  import org.springframework.core.io.support.ResourcePatternResolver;
41  import org.springframework.core.io.support.ResourcePatternUtils;
42  import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
43  import org.springframework.core.type.classreading.MetadataReader;
44  import org.springframework.core.type.classreading.MetadataReaderFactory;
45  import org.springframework.core.type.filter.AnnotationTypeFilter;
46  import org.springframework.core.type.filter.TypeFilter;
47  import org.springframework.stereotype.Component;
48  import org.springframework.stereotype.Controller;
49  import org.springframework.stereotype.Repository;
50  import org.springframework.stereotype.Service;
51  import org.springframework.util.Assert;
52  import org.springframework.util.ClassUtils;
53  
54  /**
55   * A component provider that scans the classpath from a base package. It then
56   * applies exclude and include filters to the resulting classes to find candidates.
57   *
58   * <p>This implementation is based on Spring's
59   * {@link org.springframework.core.type.classreading.MetadataReader MetadataReader}
60   * facility, backed by an ASM {@link org.springframework.asm.ClassReader ClassReader}.
61   *
62   * @author Mark Fisher
63   * @author Juergen Hoeller
64   * @author Ramnivas Laddad
65   * @author Chris Beams
66   * @since 2.5
67   * @see org.springframework.core.type.classreading.MetadataReaderFactory
68   * @see org.springframework.core.type.AnnotationMetadata
69   * @see ScannedGenericBeanDefinition
70   */
71  public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
72  
73  	static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
74  
75  	protected final Log logger = LogFactory.getLog(getClass());
76  
77  	private Environment environment;
78  
79  	private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
80  
81  	private MetadataReaderFactory metadataReaderFactory =
82  			new CachingMetadataReaderFactory(this.resourcePatternResolver);
83  
84  	private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
85  
86  	private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
87  
88  	private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
89  
90  	private ConditionEvaluator conditionEvaluator;
91  
92  
93  	/**
94  	 * Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}.
95  	 * @param useDefaultFilters whether to register the default filters for the
96  	 * {@link Component @Component}, {@link Repository @Repository},
97  	 * {@link Service @Service}, and {@link Controller @Controller}
98  	 * stereotype annotations
99  	 * @see #registerDefaultFilters()
100 	 */
101 	public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
102 		this(useDefaultFilters, new StandardEnvironment());
103 	}
104 
105 	/**
106 	 * Create a ClassPathScanningCandidateComponentProvider with the given {@link Environment}.
107 	 * @param useDefaultFilters whether to register the default filters for the
108 	 * {@link Component @Component}, {@link Repository @Repository},
109 	 * {@link Service @Service}, and {@link Controller @Controller}
110 	 * stereotype annotations
111 	 * @param environment the Environment to use
112 	 * @see #registerDefaultFilters()
113 	 */
114 	public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) {
115 		if (useDefaultFilters) {
116 			registerDefaultFilters();
117 		}
118 		Assert.notNull(environment, "Environment must not be null");
119 		this.environment = environment;
120 	}
121 
122 
123 	/**
124 	 * Set the ResourceLoader to use for resource locations.
125 	 * This will typically be a ResourcePatternResolver implementation.
126 	 * <p>Default is PathMatchingResourcePatternResolver, also capable of
127 	 * resource pattern resolving through the ResourcePatternResolver interface.
128 	 * @see org.springframework.core.io.support.ResourcePatternResolver
129 	 * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver
130 	 */
131 	@Override
132 	public void setResourceLoader(ResourceLoader resourceLoader) {
133 		this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
134 		this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
135 	}
136 
137 	/**
138 	 * Return the ResourceLoader that this component provider uses.
139 	 */
140 	public final ResourceLoader getResourceLoader() {
141 		return this.resourcePatternResolver;
142 	}
143 
144 	/**
145 	 * Set the {@link MetadataReaderFactory} to use.
146 	 * <p>Default is a {@link CachingMetadataReaderFactory} for the specified
147 	 * {@linkplain #setResourceLoader resource loader}.
148 	 * <p>Call this setter method <i>after</i> {@link #setResourceLoader} in order
149 	 * for the given MetadataReaderFactory to override the default factory.
150 	 */
151 	public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) {
152 		this.metadataReaderFactory = metadataReaderFactory;
153 	}
154 
155 	/**
156 	 * Return the MetadataReaderFactory used by this component provider.
157 	 */
158 	public final MetadataReaderFactory getMetadataReaderFactory() {
159 		return this.metadataReaderFactory;
160 	}
161 
162 	/**
163 	 * Set the Environment to use when resolving placeholders and evaluating
164 	 * {@link Conditional @Conditional}-annotated component classes.
165 	 * <p>The default is a {@link StandardEnvironment}.
166 	 * @param environment the Environment to use
167 	 */
168 	public void setEnvironment(Environment environment) {
169 		Assert.notNull(environment, "Environment must not be null");
170 		this.environment = environment;
171 		this.conditionEvaluator = null;
172 	}
173 
174 	@Override
175 	public final Environment getEnvironment() {
176 		return this.environment;
177 	}
178 
179 	/**
180 	 * Returns the {@link BeanDefinitionRegistry} used by this scanner, if any.
181 	 */
182 	protected BeanDefinitionRegistry getRegistry() {
183 		return null;
184 	}
185 
186 	/**
187 	 * Set the resource pattern to use when scanning the classpath.
188 	 * This value will be appended to each base package name.
189 	 * @see #findCandidateComponents(String)
190 	 * @see #DEFAULT_RESOURCE_PATTERN
191 	 */
192 	public void setResourcePattern(String resourcePattern) {
193 		Assert.notNull(resourcePattern, "'resourcePattern' must not be null");
194 		this.resourcePattern = resourcePattern;
195 	}
196 
197 	/**
198 	 * Add an include type filter to the <i>end</i> of the inclusion list.
199 	 */
200 	public void addIncludeFilter(TypeFilter includeFilter) {
201 		this.includeFilters.add(includeFilter);
202 	}
203 
204 	/**
205 	 * Add an exclude type filter to the <i>front</i> of the exclusion list.
206 	 */
207 	public void addExcludeFilter(TypeFilter excludeFilter) {
208 		this.excludeFilters.add(0, excludeFilter);
209 	}
210 
211 	/**
212 	 * Reset the configured type filters.
213 	 * @param useDefaultFilters whether to re-register the default filters for
214 	 * the {@link Component @Component}, {@link Repository @Repository},
215 	 * {@link Service @Service}, and {@link Controller @Controller}
216 	 * stereotype annotations
217 	 * @see #registerDefaultFilters()
218 	 */
219 	public void resetFilters(boolean useDefaultFilters) {
220 		this.includeFilters.clear();
221 		this.excludeFilters.clear();
222 		if (useDefaultFilters) {
223 			registerDefaultFilters();
224 		}
225 	}
226 
227 	/**
228 	 * Register the default filter for {@link Component @Component}.
229 	 * <p>This will implicitly register all annotations that have the
230 	 * {@link Component @Component} meta-annotation including the
231 	 * {@link Repository @Repository}, {@link Service @Service}, and
232 	 * {@link Controller @Controller} stereotype annotations.
233 	 * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
234 	 * JSR-330's {@link javax.inject.Named} annotations, if available.
235 	 *
236 	 */
237 	@SuppressWarnings("unchecked")
238 	protected void registerDefaultFilters() {
239 		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
240 		ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
241 		try {
242 			this.includeFilters.add(new AnnotationTypeFilter(
243 					((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
244 			logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
245 		}
246 		catch (ClassNotFoundException ex) {
247 			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
248 		}
249 		try {
250 			this.includeFilters.add(new AnnotationTypeFilter(
251 					((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
252 			logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
253 		}
254 		catch (ClassNotFoundException ex) {
255 			// JSR-330 API not available - simply skip.
256 		}
257 	}
258 
259 
260 	/**
261 	 * Scan the class path for candidate components.
262 	 * @param basePackage the package to check for annotated classes
263 	 * @return a corresponding Set of autodetected bean definitions
264 	 */
265 	public Set<BeanDefinition> findCandidateComponents(String basePackage) {
266 		Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
267 		try {
268 			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
269 					resolveBasePackage(basePackage) + "/" + this.resourcePattern;
270 			Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
271 			boolean traceEnabled = logger.isTraceEnabled();
272 			boolean debugEnabled = logger.isDebugEnabled();
273 			for (Resource resource : resources) {
274 				if (traceEnabled) {
275 					logger.trace("Scanning " + resource);
276 				}
277 				if (resource.isReadable()) {
278 					try {
279 						MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
280 						if (isCandidateComponent(metadataReader)) {
281 							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
282 							sbd.setResource(resource);
283 							sbd.setSource(resource);
284 							if (isCandidateComponent(sbd)) {
285 								if (debugEnabled) {
286 									logger.debug("Identified candidate component class: " + resource);
287 								}
288 								candidates.add(sbd);
289 							}
290 							else {
291 								if (debugEnabled) {
292 									logger.debug("Ignored because not a concrete top-level class: " + resource);
293 								}
294 							}
295 						}
296 						else {
297 							if (traceEnabled) {
298 								logger.trace("Ignored because not matching any filter: " + resource);
299 							}
300 						}
301 					}
302 					catch (Throwable ex) {
303 						throw new BeanDefinitionStoreException(
304 								"Failed to read candidate component class: " + resource, ex);
305 					}
306 				}
307 				else {
308 					if (traceEnabled) {
309 						logger.trace("Ignored because not readable: " + resource);
310 					}
311 				}
312 			}
313 		}
314 		catch (IOException ex) {
315 			throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
316 		}
317 		return candidates;
318 	}
319 
320 
321 	/**
322 	 * Resolve the specified base package into a pattern specification for
323 	 * the package search path.
324 	 * <p>The default implementation resolves placeholders against system properties,
325 	 * and converts a "."-based package path to a "/"-based resource path.
326 	 * @param basePackage the base package as specified by the user
327 	 * @return the pattern specification to be used for package searching
328 	 */
329 	protected String resolveBasePackage(String basePackage) {
330 		return ClassUtils.convertClassNameToResourcePath(this.environment.resolveRequiredPlaceholders(basePackage));
331 	}
332 
333 	/**
334 	 * Determine whether the given class does not match any exclude filter
335 	 * and does match at least one include filter.
336 	 * @param metadataReader the ASM ClassReader for the class
337 	 * @return whether the class qualifies as a candidate component
338 	 */
339 	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
340 		for (TypeFilter tf : this.excludeFilters) {
341 			if (tf.match(metadataReader, this.metadataReaderFactory)) {
342 				return false;
343 			}
344 		}
345 		for (TypeFilter tf : this.includeFilters) {
346 			if (tf.match(metadataReader, this.metadataReaderFactory)) {
347 				return isConditionMatch(metadataReader);
348 			}
349 		}
350 		return false;
351 	}
352 
353 	/**
354 	 * Determine whether the given class is a candidate component based on any
355 	 * {@code @Conditional} annotations.
356 	 * @param metadataReader the ASM ClassReader for the class
357 	 * @return whether the class qualifies as a candidate component
358 	 */
359 	private boolean isConditionMatch(MetadataReader metadataReader) {
360 		if (this.conditionEvaluator == null) {
361 			this.conditionEvaluator = new ConditionEvaluator(getRegistry(), getEnvironment(), getResourceLoader());
362 		}
363 		return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
364 	}
365 
366 	/**
367 	 * Determine whether the given bean definition qualifies as candidate.
368 	 * <p>The default implementation checks whether the class is concrete
369 	 * (i.e. not abstract and not an interface). Can be overridden in subclasses.
370 	 * @param beanDefinition the bean definition to check
371 	 * @return whether the bean definition qualifies as a candidate component
372 	 */
373 	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
374 		return (beanDefinition.getMetadata().isConcrete() && beanDefinition.getMetadata().isIndependent());
375 	}
376 
377 
378 	/**
379 	 * Clear the underlying metadata cache, removing all cached class metadata.
380 	 */
381 	public void clearCache() {
382 		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
383 			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
384 		}
385 	}
386 
387 }