概述
-
功能作用
在某一个指定的包内,使用指定的包含过滤器和排斥过滤器扫描匹配的bean
组件定义。 -
主要功能函数
addIncludeFilter
– 添加一个包含过滤器addExcludeFilter
– 添加一个排除过滤器findCandidateComponents
– 核心任务 : 从指定的某个包内扫描目标bean
组件定义resetFilters
– 设置包含过滤器和排除过滤器为空clearCache
– 清除扫描过程中所记录的类的元数据信息缓存
-
两种工作模式
- 缺省过滤器工作模式
- 所使用的包含过滤器
Spring
@Component
(以及包含该注解的其他注解,比如@Controller
,@Service
等)JSR-250
@ManagedBean
JSR-330
@Named
- 所使用的排斥过滤器
- 无
- 所使用的包含过滤器
- 指定过滤器工作模式
- 使用者指定什么用什么
- 缺省过滤器工作模式
-
两类过滤器
- 包含过滤器 – 符合条件的
bean
组件才会被作为候选 - 排斥过滤器 – 符合条件的
bean
组件不会进入候选名单
如果某个包含过滤器和排斥过滤器冲突,排斥过滤器生效。
- 包含过滤器 – 符合条件的
-
支持过滤器的类型
- 注解型过滤器
- 接口型过滤器
-
一些常见重要的实现类
ClassPathScanningCandidateComponentProvider
ClassPathBeanDefinitionScanner
(Spring Context
)- 所使用的包含过滤器
- 缺省值 (同
ClassPathScanningCandidateComponentProvider
)
- 缺省值 (同
- 所使用的排斥过滤器
- 无
- 所使用的包含过滤器
RepositoryComponentProvider
(Spring Data Common
)- 所使用的包含过滤器
Repository
接口RepositoryDefinition
注解
如果使用者自己指定了过滤器,则不使用上面两个过滤器
- 所使用的排斥过滤器
NoRepositoryBean
注解
- 所使用的包含过滤器
源代码
源代码版本 :
spring-context-5.1.5.RELEASE
package org.springframework.context.annotation;
// 省略 import
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable,
ResourceLoaderAware {
// 所要扫描的资源的路径pattern缺省值
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
protected final Log logger = LogFactory.getLog(getClass());
// 所要扫描的资源的路径pattern,初始化为缺省值
private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
private final List<TypeFilter> includeFilters = new LinkedList<>();
private final List<TypeFilter> excludeFilters = new LinkedList<>();
@Nullable
private Environment environment;
// 条件表达式求值器/评估器
@Nullable
private ConditionEvaluator conditionEvaluator;
// 资源路径解析器,能从指定的包路径,根据指定的pattern,加载相应的Resource
@Nullable
private ResourcePatternResolver resourcePatternResolver;
// 工厂类,用于生成读取类元数据的 MetadataReader
@Nullable
private MetadataReaderFactory metadataReaderFactory;
@Nullable
private CandidateComponentsIndex componentsIndex;
/**
* Protected constructor for flexible subclass initialization.
* @since 4.3.6
*/
protected ClassPathScanningCandidateComponentProvider() {
}
/**
* Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}.
* @param useDefaultFilters whether to register the default filters for the
* {@link Component @Component}, {@link Repository @Repository},
* {@link Service @Service}, and {@link Controller @Controller}
* stereotype annotations
* @see #registerDefaultFilters()
*/
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
this(useDefaultFilters, new StandardEnvironment());
}
/**
* Create a ClassPathScanningCandidateComponentProvider with the given {@link Environment}.
* @param useDefaultFilters whether to register the default filters for the
* {@link Component @Component}, {@link Repository @Repository},
* {@link Service @Service}, and {@link Controller @Controller}
* stereotype annotations
* @param environment the Environment to use
* @see #registerDefaultFilters()
*/
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters,
Environment environment) {
if (useDefaultFilters) {
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(null);
}
/**
* Set the resource pattern to use when scanning the classpath.
* This value will be appended to each base package name.
* @see #findCandidateComponents(String)
* @see #DEFAULT_RESOURCE_PATTERN
*/
public void setResourcePattern(String resourcePattern) {
Assert.notNull(resourcePattern, "'resourcePattern' must not be null");
this.resourcePattern = resourcePattern;
}
/**
* Add an include type filter to the <i>end</i> of the inclusion list.
*/
public void addIncludeFilter(TypeFilter includeFilter) {
this.includeFilters.add(includeFilter);
}
/**
* Add an exclude type filter to the <i>front</i> of the exclusion list.
*/
public void addExcludeFilter(TypeFilter excludeFilter) {
this.excludeFilters.add(0, excludeFilter);
}
/**
* Reset the configured type filters.
* @param useDefaultFilters whether to re-register the default filters for
* the {@link Component @Component}, {@link Repository @Repository},
* {@link Service @Service}, and {@link Controller @Controller}
* stereotype annotations
* @see #registerDefaultFilters()
*/
public void resetFilters(boolean useDefaultFilters) {
this.includeFilters.clear();
this.excludeFilters.clear();
if (useDefaultFilters) {
registerDefaultFilters();
}
}
/**
* Register the default filter for {@link Component @Component}.
* <p>This will implicitly register all annotations that have the
* {@link Component @Component} meta-annotation including the
* {@link Repository @Repository}, {@link Service @Service}, and
* {@link Controller @Controller} stereotype annotations.
* <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
* JSR-330's {@link javax.inject.Named} annotations, if available.
*
*/
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>)
ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace(
"JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>)
ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace(
"JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
/**
* Set the Environment to use when resolving placeholders and evaluating
* {@link Conditional @Conditional}-annotated component classes.
* <p>The default is a {@link StandardEnvironment}.
* @param environment the Environment to use
*/
public void setEnvironment(Environment environment) {
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
this.conditionEvaluator = null;
}
@Override
public final Environment getEnvironment() {
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
return this.environment;
}
/**
* Return the {@link BeanDefinitionRegistry} used by this scanner, if any.
*/
@Nullable
protected BeanDefinitionRegistry getRegistry() {
return null;
}
/**
* Set the {@link ResourceLoader} to use for resource locations.
* This will typically be a {@link ResourcePatternResolver} implementation.
* <p>Default is a {@code PathMatchingResourcePatternResolver}, also capable of
* resource pattern resolving through the {@code ResourcePatternResolver} interface.
* @see org.springframework.core.io.support.ResourcePatternResolver
* @see org.springframework.core.io.support.PathMatchingResourcePatternResolver
*/
@Override
public void setResourceLoader(@Nullable ResourceLoader resourceLoader) {
this.resourcePatternResolver =
ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
this.componentsIndex =
andidateComponentsIndexLoader.loadIndex(
this.resourcePatternResolver.getClassLoader());
}
/**
* Return the ResourceLoader that this component provider uses.
*/
public final ResourceLoader getResourceLoader() {
return getResourcePatternResolver();
}
private ResourcePatternResolver getResourcePatternResolver() {
if (this.resourcePatternResolver == null) {
// 缺省使用 PathMatchingResourcePatternResolver
this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
}
return this.resourcePatternResolver;
}
/**
* Set the {@link MetadataReaderFactory} to use.
* <p>Default is a {@link CachingMetadataReaderFactory} for the specified
* {@linkplain #setResourceLoader resource loader}.
* <p>Call this setter method <i>after</i> {@link #setResourceLoader} in order
* for the given MetadataReaderFactory to override the default factory.
*/
public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) {
this.metadataReaderFactory = metadataReaderFactory;
}
/**
* Return the MetadataReaderFactory used by this component provider.
*/
public final MetadataReaderFactory getMetadataReaderFactory() {
if (this.metadataReaderFactory == null) {
// 缺省使用带有缓存能力的 CachingMetadataReaderFactory
this.metadataReaderFactory = new CachingMetadataReaderFactory();
}
return this.metadataReaderFactory;
}
/**
* Scan the class path for candidate components.
* @param basePackage the package to check for annotated classes
* @return a corresponding Set of autodetected bean definitions
*/
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
// 从指定的包内扫描目标组件bean
return scanCandidateComponents(basePackage);
}
}
/**
* Determine if the index can be used by this instance.
* @return {@code true} if the index is available and the configuration of this
* instance is supported by it, {@code false} otherwise
* @since 5.0
*/
private boolean indexSupportsIncludeFilters() {
for (TypeFilter includeFilter : this.includeFilters) {
if (!indexSupportsIncludeFilter(includeFilter)) {
return false;
}
}
return true;
}
/**
* Determine if the specified include {@link TypeFilter} is supported by the index.
* @param filter the filter to check
* @return whether the index supports this include filter
* @since 5.0
* @see #extractStereotype(TypeFilter)
*/
private boolean indexSupportsIncludeFilter(TypeFilter filter) {
if (filter instanceof AnnotationTypeFilter) {
Class<? extends Annotation> annotation =
((AnnotationTypeFilter) filter).getAnnotationType();
return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) ||
annotation.getName().startsWith("javax."));
}
if (filter instanceof AssignableTypeFilter) {
Class<?> target = ((AssignableTypeFilter) filter).getTargetType();
return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target);
}
return false;
}
/**
* Extract the stereotype to use for the specified compatible filter.
* @param filter the filter to handle
* @return the stereotype in the index matching this filter
* @since 5.0
* @see #indexSupportsIncludeFilter(TypeFilter)
*/
@Nullable
private String extractStereotype(TypeFilter filter) {
if (filter instanceof AnnotationTypeFilter) {
return ((AnnotationTypeFilter) filter).getAnnotationType().getName();
}
if (filter instanceof AssignableTypeFilter) {
return ((AssignableTypeFilter) filter).getTargetType().getName();
}
return null;
}
private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index,
String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
Set<String> types = new HashSet<>();
for (TypeFilter filter : this.includeFilters) {
String stereotype = extractStereotype(filter);
if (stereotype == null) {
throw new IllegalArgumentException("Failed to extract stereotype from "+ filter);
}
types.addAll(index.getCandidateTypes(basePackage, stereotype));
}
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (String type : types) {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(type);
if (isCandidateComponent(metadataReader)) {
AnnotatedGenericBeanDefinition sbd = new AnnotatedGenericBeanDefinition(
metadataReader.getAnnotationMetadata());
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Using candidate component class from index: " + type);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + type);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because matching an exclude filter: " + type);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// 初始化在指定包内扫描资源的pattern :packageSearchPath ,
// 缺省情况下,这里只关注所有的java类文件,也就是.class结尾的字节码文件
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 获取匹配 packageSearchPath pattern 的资源
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
// 遍历每个找到的类对应的Resource 对象,获取其 MetadataReader
// 判断这是否是一个符合包含过滤器,并且不在排斥过滤器内的bean组件定义类
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader =
getMetadataReaderFactory().getMetadataReader(resource);
// 判断这是否是一个符合候选条件的bean组件定义类
// 如果符合条件的话,将其添加到候选类集合 : candidates
// 第一次判断 : 判断这是否是一个符合包含过滤器,并且不在排斥过滤器内的bean组件定义类
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd =
new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
// 第二次判断 : 构造出BD之后再次检测是否符合候选条件
// 1. 独立类 + 具体实现类 或者
// 2. 独立类 + 抽象类 + 带有使用注解 Lookup 的方法
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug(
"Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
/**
* Resolve the specified base package into a pattern specification for
* the package search path.
* <p>The default implementation resolves placeholders against system properties,
* and converts a "."-based package path to a "/"-based resource path.
* @param basePackage the base package as specified by the user
* @return the pattern specification to be used for package searching
*/
protected String resolveBasePackage(String basePackage) {
return ClassUtils.convertClassNameToResourcePath(
getEnvironment().resolveRequiredPlaceholders(basePackage));
}
/**
* Determine whether the given class does not match any exclude filter
* and does match at least one include filter.
* @param metadataReader the ASM ClassReader for the class
* @return whether the class qualifies as a candidate component
*/
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
// 注意,这里排斥过滤器优先被使用
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
/**
* Determine whether the given class is a candidate component based on any
* {@code @Conditional} annotations.
* @param metadataReader the ASM ClassReader for the class
* @return whether the class qualifies as a candidate component
*/
private boolean isConditionMatch(MetadataReader metadataReader) {
if (this.conditionEvaluator == null) {
this.conditionEvaluator =
new ConditionEvaluator(getRegistry(), this.environment,
this.resourcePatternResolver);
}
return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
}
/**
* Determine whether the given bean definition qualifies as candidate.
* <p>The default implementation checks whether the class is not an interface
* and not dependent on an enclosing class.
* <p>Can be overridden in subclasses.
* @param beanDefinition the bean definition to check
* @return whether the bean definition qualifies as a candidate component
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
/**
* Clear the local metadata cache, if any, removing all cached class metadata.
*/
public void clearCache() {
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
}