/* * Copyright 2002-2008 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 * * http://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.util.LinkedHashSet; import java.util.Set; import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionDefaults; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.util.PatternMatchUtils; /** * A bean definition scanner that detects bean candidates on the classpath, * registering corresponding bean definitions with a given registry (BeanFactory * or ApplicationContext). * * <p>Candidate classes are detected through configurable type filters. The * default filters include classes that are annotated with Spring's * {@link org.springframework.stereotype.Component @Component}, * {@link org.springframework.stereotype.Repository @Repository}, * {@link org.springframework.stereotype.Service @Service}, or * {@link org.springframework.stereotype.Controller @Controller} stereotype. * * @author Mark Fisher * @author Juergen Hoeller * @since 2.5 * @see org.springframework.stereotype.Component * @see org.springframework.stereotype.Repository * @see org.springframework.stereotype.Service * @see org.springframework.stereotype.Controller */ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { private final BeanDefinitionRegistry registry; private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults(); private String[] autowireCandidatePatterns; private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver(); private boolean includeAnnotationConfig = true; /** * Create a new ClassPathBeanDefinitionScanner for the given bean factory. * @param registry the BeanFactory to load bean definitions into, * in the form of a BeanDefinitionRegistry */ public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) { this(registry, true); } /** * Create a new ClassPathBeanDefinitionScanner for the given bean factory. * <p>If the passed-in bean factory does not only implement the BeanDefinitionRegistry * interface but also the ResourceLoader interface, it will be used as default * ResourceLoader as well. This will usually be the case for * {@link org.springframework.context.ApplicationContext} implementations. * <p>If given a plain BeanDefinitionRegistry, the default ResourceLoader will be a * {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}. * @param registry the BeanFactory to load bean definitions into, * in the form of a BeanDefinitionRegistry * @param useDefaultFilters whether to include the default filters for the * {@link org.springframework.stereotype.Component @Component}, * {@link org.springframework.stereotype.Repository @Repository}, * {@link org.springframework.stereotype.Service @Service}, and * {@link org.springframework.stereotype.Controller @Controller} stereotype * annotations. * @see #setResourceLoader */ public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) { super(useDefaultFilters); Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; // Determine ResourceLoader to use. if (this.registry instanceof ResourceLoader) { setResourceLoader((ResourceLoader) this.registry); } } /** * Return the BeanDefinitionRegistry that this scanner operates on. */ public final BeanDefinitionRegistry getRegistry() { return this.registry; } /** * Set the defaults to use for detected beans. * @see BeanDefinitionDefaults */ public void setBeanDefinitionDefaults(BeanDefinitionDefaults beanDefinitionDefaults) { this.beanDefinitionDefaults = (beanDefinitionDefaults != null ? beanDefinitionDefaults : new BeanDefinitionDefaults()); } /** * Set the name-matching patterns for determining autowire candidates. * @param autowireCandidatePatterns the patterns to match against */ public void setAutowireCandidatePatterns(String[] autowireCandidatePatterns) { this.autowireCandidatePatterns = autowireCandidatePatterns; } /** * Set the BeanNameGenerator to use for detected bean classes. * <p>Default is a {@link AnnotationBeanNameGenerator}. */ public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { this.beanNameGenerator = (beanNameGenerator != null ? beanNameGenerator : new AnnotationBeanNameGenerator()); } /** * Set the ScopeMetadataResolver to use for detected bean classes. * Note that this will override any custom "scopedProxyMode" setting. * <p>The default is an {@link AnnotationScopeMetadataResolver}. * @see #setScopedProxyMode */ public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) { this.scopeMetadataResolver = scopeMetadataResolver; } /** * Specify the proxy behavior for non-singleton scoped beans. * Note that this will override any custom "scopeMetadataResolver" setting. * <p>The default is {@link ScopedProxyMode#NO}. * @see #setScopeMetadataResolver */ public void setScopedProxyMode(ScopedProxyMode scopedProxyMode) { this.scopeMetadataResolver = new AnnotationScopeMetadataResolver(scopedProxyMode); } /** * Specify whether to register annotation config post-processors. * <p>The default is to register the post-processors. Turn this off * to be able to ignore the annotations or to process them differently. */ public void setIncludeAnnotationConfig(boolean includeAnnotationConfig) { this.includeAnnotationConfig = includeAnnotationConfig; } /** * Perform a scan within the specified base packages. * @param basePackages the packages to check for annotated classes * @return number of beans registered */ public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); doScan(basePackages); // Register annotation config processors, if necessary. if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return this.registry.getBeanDefinitionCount() - beanCountAtScanStart; } /** * Perform a scan within the specified base packages, * returning the registered bean definitions. * <p>This method does <i>not</i> register an annotation config processor * but rather leaves this up to the caller. * @param basePackages the packages to check for annotated classes * @return number of beans registered */ protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(); for (int i = 0; i < basePackages.length; i++) { Set<BeanDefinition> candidates = findCandidateComponents(basePackages[i]); for (BeanDefinition candidate : candidates) { String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = applyScope(definitionHolder, scopeMetadata); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; } /** * Apply further settings to the given bean definition, * beyond the contents retrieved from scanning the component class. * @param beanDefinition the scanned bean definition * @param beanName the generated bean name for the given bean */ protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) { beanDefinition.applyDefaults(this.beanDefinitionDefaults); if (this.autowireCandidatePatterns != null) { beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName)); } } /** * Register the specified bean with the given registry. * <p>Can be overridden in subclasses, e.g. to adapt the registration * process or to register further bean definitions for each scanned bean. * @param definitionHolder the bean definition plus bean name for the bean * @param registry the BeanDefinitionRegistry to register the bean with */ protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) { BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry); } /** * Check the given candidate's bean name, determining whether the corresponding * bean definition needs to be registered or conflicts with an existing definition. * @param beanName the suggested name for the bean * @param beanDefinition the corresponding bean definition * @return <code>true</code> if the bean can be registered as-is; * <code>false</code> if it should be skipped because there is an * existing, compatible bean definition for the specified name * @throws IllegalStateException if an existing, incompatible * bean definition has been found for the specified name */ protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException { if (!this.registry.containsBeanDefinition(beanName)) { return true; } BeanDefinition existingDef = this.registry.getBeanDefinition(beanName); BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition(); if (originatingDef != null) { existingDef = originatingDef; } if (isCompatible(beanDefinition, existingDef)) { return false; } throw new IllegalStateException("Annotation-specified bean name '" + beanName + "' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " + "non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]"); } /** * Determine whether the given new bean definition is compatible with * the given existing bean definition. * <p>The default implementation simply considers them as compatible * when the bean class name matches. * @param newDefinition the new bean definition, originated from scanning * @param existingDefinition the existing bean definition, potentially an * explicitly defined one or a previously generated one from scanning * @return whether the definitions are considered as compatible, with the * new definition to be skipped in favor of the existing definition */ protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) { return (!(existingDefinition instanceof AnnotatedBeanDefinition) || // explicitly registered overriding bean newDefinition.getSource().equals(existingDefinition.getSource()) || // scanned same file twice newDefinition.equals(existingDefinition)); // scanned equivalent class twice } /** * Apply the specified scope to the given bean definition. * @param definitionHolder the bean definition to configure * @param scopeMetadata the corresponding scope metadata * @return the final bean definition to use (potentially a proxy) */ private BeanDefinitionHolder applyScope(BeanDefinitionHolder definitionHolder, ScopeMetadata scopeMetadata) { String scope = scopeMetadata.getScopeName(); ScopedProxyMode scopedProxyMode = scopeMetadata.getScopedProxyMode(); definitionHolder.getBeanDefinition().setScope(scope); if (BeanDefinition.SCOPE_SINGLETON.equals(scope) || BeanDefinition.SCOPE_PROTOTYPE.equals(scope) || scopedProxyMode.equals(ScopedProxyMode.NO)) { return definitionHolder; } boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS); return ScopedProxyCreator.createScopedProxy(definitionHolder, this.registry, proxyTargetClass); } /** * Inner factory class used to just introduce an AOP framework dependency * when actually creating a scoped proxy. */ private static class ScopedProxyCreator { public static BeanDefinitionHolder createScopedProxy( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry, boolean proxyTargetClass) { return ScopedProxyUtils.createScopedProxy(definitionHolder, registry, proxyTargetClass); } } }