spring component-scan filter

时间:2023-01-21 21:34:58

(参考的Spring version : 4.1.6.RELEASE)

我们通常会使用component-scan来进行bean的加载,但是它里面的实现机制却是一知半解。根据原码来理解一下,可能会更加清晰。

例如,我们通常会使用如下的配置:

application.xml:

<context:component-scan base-package="com.cn.kvn.service,com.cn.kvn.config,com.baidu">
<context:include-filter type="annotation" expression="com.alibaba.dubbo.config.annotation.Service" />
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

spring-servlet.xml:

<context:component-scan base-package="com.cn.kvn.controller" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

看了下面的解释,你就能大体知道这些配置具体是怎么控制和生效的了。

  • componentScan解析bean的入口为:ComponentScanBeanDefinitionParser#parse(Element element, ParserContext parserContext)
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); // Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element); return null;
}

base-package属性告诉spring要扫描的包,use-default-filters="false"表示不要使用默认的过滤器

  • configureScanner会去配置scan时的使用的filter,其中use-default-filters属性是来控制是否要使用默认的过滤器的。

(默认的过滤器会去解析base-package下的含有@Component注解的类作为bean,其中@Repository, @Service, and @Controller都是@Component的子注解,故,默认的过滤器会将它们全部解析成bean)

原码中的英文注释:(ClassPathScanningCandidateComponentProvider#ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment))

useDefaultFilters whether to register the default filters for the @Component, @Repository, @Service, and @Controller stereotype annotations
  • 用户自定义的include-filter和exclude-filter会在ComponentScanBeanDefinitionParser#parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext)方法中被解析加载。
  • 在执行Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);的时候,使用过滤器的顺序是,exclude-filter优于include-filter。

也就是说,如果同时定义了exclude-filter排除了某类(某个)bean,但是include-filter又将其包含了,则该bean不会被加载到spring容器。

ClassPathScanningCandidateComponentProvider.java
/**
* 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, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return isConditionMatch(metadataReader);
}
}
return false;
}

附:过滤规则设置

context:component-scan节点允许有两个子节点<context:include-filter>和<context:exclude-filter>。filter标签的type和表达式说明如下:

Filter Type Examples Expression Description include-filter为例
annotation org.example.SomeAnnotation 符合SomeAnnoation的target class

<context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/>

表示扫描base-package下的类上加了Aspect注解的类,并注册到spring的bean容器

assignable org.example.SomeClass 指定class或interface的全名

<context:include-filter type="assignable" expression="com.test.scan.StuService"/>

指定扫描StuService类作为bean

aspectj org.example..*Service+ AspectJ語法  
regex org\.example\.Default.* Regelar Expression  
custom org.example.MyTypeFilter Spring3新增自訂Type,實作org.springframework.core.type.TypeFilter  

在我们的示例中,将filter的type设置成了正则表达式,regex,注意在正则里面.表示所有字符,而\.才表示真正的.字符。我们的正则表示以Dao或者Service结束的类。

我们也可以使用annotaion来限定,如下:

	<context:component-scan base-package="cn.outofmemory.spring" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> 
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> 
</context:component-scan>

这里我们指定的include-filter的type是annotation,expression则是注解类的全名。

另外context:conponent-scan节点还有<context:exclude-filter>可以用来指定要排除的类,其用法和include-filter一致。