001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.context.annotation;
018:
019: import java.io.IOException;
020: import java.util.LinkedHashSet;
021: import java.util.LinkedList;
022: import java.util.List;
023: import java.util.Set;
024:
025: import org.springframework.beans.factory.BeanDefinitionStoreException;
026: import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
027: import org.springframework.beans.factory.config.BeanDefinition;
028: import org.springframework.context.ResourceLoaderAware;
029: import org.springframework.core.io.Resource;
030: import org.springframework.core.io.ResourceLoader;
031: import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
032: import org.springframework.core.io.support.ResourcePatternResolver;
033: import org.springframework.core.io.support.ResourcePatternUtils;
034: import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
035: import org.springframework.core.type.classreading.MetadataReader;
036: import org.springframework.core.type.classreading.MetadataReaderFactory;
037: import org.springframework.core.type.filter.AnnotationTypeFilter;
038: import org.springframework.core.type.filter.TypeFilter;
039: import org.springframework.stereotype.Component;
040: import org.springframework.stereotype.Controller;
041: import org.springframework.stereotype.Repository;
042: import org.springframework.stereotype.Service;
043: import org.springframework.util.Assert;
044: import org.springframework.util.ClassUtils;
045:
046: /**
047: * A component provider that scans the classpath from a base package. It then
048: * applies exclude and include filters to the resulting classes to find
049: * candidates.
050: *
051: * <p>This implementation is based on Spring's
052: * {@link org.springframework.core.type.classreading.MetadataReader MetadataReader}
053: * facility, backed by an ASM {@link org.objectweb.asm.ClassReader ClassReader}.
054: *
055: * @author Mark Fisher
056: * @author Juergen Hoeller
057: * @author Ramnivas Laddad
058: * @since 2.5
059: * @see org.springframework.core.type.classreading.MetadataReaderFactory
060: * @see org.springframework.core.type.AnnotationMetadata
061: * @see ScannedGenericBeanDefinition
062: */
063: public class ClassPathScanningCandidateComponentProvider implements
064: ResourceLoaderAware {
065:
066: protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
067:
068: private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
069:
070: private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
071: this .resourcePatternResolver);
072:
073: private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
074:
075: private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
076:
077: private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
078:
079: /**
080: * Create a ClassPathScanningCandidateComponentProvider.
081: *
082: * @param useDefaultFilters whether to register the default filters for the
083: * {@link Component @Component}, {@link Repository @Repository},
084: * {@link Service @Service}, and {@link Controller @Controller}
085: * stereotype annotations.
086: * @see #registerDefaultFilters()
087: */
088: public ClassPathScanningCandidateComponentProvider(
089: boolean useDefaultFilters) {
090: if (useDefaultFilters) {
091: registerDefaultFilters();
092: }
093: }
094:
095: /**
096: * Set the ResourceLoader to use for resource locations.
097: * This will typically be a ResourcePatternResolver implementation.
098: * <p>Default is PathMatchingResourcePatternResolver, also capable of
099: * resource pattern resolving through the ResourcePatternResolver interface.
100: * @see org.springframework.core.io.support.ResourcePatternResolver
101: * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver
102: */
103: public void setResourceLoader(ResourceLoader resourceLoader) {
104: this .resourcePatternResolver = ResourcePatternUtils
105: .getResourcePatternResolver(resourceLoader);
106: this .metadataReaderFactory = new CachingMetadataReaderFactory(
107: resourceLoader);
108: }
109:
110: /**
111: * Set the resource pattern to use when scanning the classpath.
112: * This value will be appended to each base package name.
113: * @see #findCandidateComponents(String)
114: * @see #DEFAULT_RESOURCE_PATTERN
115: */
116: public void setResourcePattern(String resourcePattern) {
117: Assert.notNull(resourcePattern,
118: "'resourcePattern' must not be null");
119: this .resourcePattern = resourcePattern;
120: }
121:
122: /**
123: * Add an include type filter to the <i>end</i> of the inclusion list.
124: */
125: public void addIncludeFilter(TypeFilter includeFilter) {
126: this .includeFilters.add(includeFilter);
127: }
128:
129: /**
130: * Add an exclude type filter to the <i>front</i> of the exclusion list.
131: */
132: public void addExcludeFilter(TypeFilter excludeFilter) {
133: this .excludeFilters.add(0, excludeFilter);
134: }
135:
136: /**
137: * Reset the configured type filters.
138: *
139: * @param useDefaultFilters whether to re-register the default filters for
140: * the {@link Component @Component}, {@link Repository @Repository},
141: * {@link Service @Service}, and {@link Controller @Controller}
142: * stereotype annotations.
143: * @see #registerDefaultFilters()
144: */
145: public void resetFilters(boolean useDefaultFilters) {
146: this .includeFilters.clear();
147: this .excludeFilters.clear();
148: if (useDefaultFilters) {
149: registerDefaultFilters();
150: }
151: }
152:
153: /**
154: * Register the default filter for {@link Component @Component}.
155: * This will implicitly register all annotations that have the
156: * {@link Component @Component} meta-annotation including the
157: * {@link Repository @Repository}, {@link Service @Service}, and
158: * {@link Controller @Controller} stereotype annotations.
159: */
160: protected void registerDefaultFilters() {
161: this .includeFilters.add(new AnnotationTypeFilter(
162: Component.class));
163: }
164:
165: /**
166: * Scan the class path for candidate components.
167: * @param basePackage the package to check for annotated classes
168: * @return a corresponding Set of autodetected bean definitions
169: */
170: public Set<BeanDefinition> findCandidateComponents(
171: String basePackage) {
172: Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
173: try {
174: String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
175: + ClassUtils
176: .convertClassNameToResourcePath(basePackage)
177: + "/" + this .resourcePattern;
178: Resource[] resources = this .resourcePatternResolver
179: .getResources(packageSearchPath);
180: for (int i = 0; i < resources.length; i++) {
181: Resource resource = resources[i];
182: MetadataReader metadataReader = this .metadataReaderFactory
183: .getMetadataReader(resource);
184: if (isCandidateComponent(metadataReader)) {
185: ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(
186: metadataReader);
187: sbd.setSource(resource);
188: if (isCandidateComponent(sbd)) {
189: candidates.add(sbd);
190: }
191: }
192: }
193: } catch (IOException ex) {
194: throw new BeanDefinitionStoreException(
195: "I/O failure during classpath scanning", ex);
196: }
197: return candidates;
198: }
199:
200: /**
201: * Determine whether the given class does not match any exclude filter
202: * and does match at least one include filter.
203: * @param metadataReader the ASM ClassReader for the class
204: * @return whether the class qualifies as a candidate component
205: */
206: protected boolean isCandidateComponent(MetadataReader metadataReader)
207: throws IOException {
208: for (TypeFilter tf : this .excludeFilters) {
209: if (tf.match(metadataReader, this .metadataReaderFactory)) {
210: return false;
211: }
212: }
213: for (TypeFilter tf : this .includeFilters) {
214: if (tf.match(metadataReader, this .metadataReaderFactory)) {
215: return true;
216: }
217: }
218: return false;
219: }
220:
221: /**
222: * Determine whether the given bean definition qualifies as candidate.
223: * <p>The default implementation checks whether the class is concrete
224: * (i.e. not abstract and not an interface). Can be overridden in subclasses.
225: * @param beanDefinition the bean definition to check
226: * @return whether the bean definition qualifies as a candidate component
227: */
228: protected boolean isCandidateComponent(
229: AnnotatedBeanDefinition beanDefinition) {
230: return beanDefinition.getMetadata().isConcrete();
231: }
232:
233: }
|