基于@ComponentScan注解及其XML配置方式
作者:kosamino
@ComponentScan注解及其XML配置
开发中会经常使用包扫描,只要标注了@Controller、@Service、@Repository,@Component 注解的类会自动加入到容器中,ComponentScan有注解和xml配置两种方式。
注解
@ComponentScan 包含过滤和排除过滤
- ComponentScan.Filter[] includeFilters() default {}; 按照某些规则排除组件
- ComponentScan.Filter[] excludeFilters() default {}; 指定扫描的时候只需要包含哪些组件
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class; ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; String resourcePattern() default "**/*.class"; boolean useDefaultFilters() default true; ComponentScan.Filter[] includeFilters() default {}; ComponentScan.Filter[] excludeFilters() default {}; boolean lazyInit() default false; @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Filter { FilterType type() default FilterType.ANNOTATION; @AliasFor("classes") Class<?>[] value() default {}; @AliasFor("value") Class<?>[] classes() default {}; String[] pattern() default {}; } }
FilterType 指定不同的包含/排除规则:
package org.springframework.context.annotation; public enum FilterType { ANNOTATION, ASSIGNABLE_TYPE, ASPECTJ, REGEX, CUSTOM; private FilterType() { } }
1、Spring使用注解 包扫描 @ComponentScan
@ComponentScan("com.spring.annotation") @Configuration public class MainConfig { }
注意:mainConfig 配置类也是一个组件 因为@Configuration 注解中标有@Component。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { String value() default ""; }
2、按照注解类型排除
例如排除以下类型:Controller.class, Service.class, Repository.class:
@ComponentScan(value = "com.spring.annotation", excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class, Repository.class}) }) @Configuration public class MainConfig { }
3、包含过滤includeFilters
如果想要只包含 Controller 注解的bean,如下配置:
@ComponentScan(value = "com.spring.annotation", includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},useDefaultFilters = false) @Configuration public class MainConfig { }
注意:需要添加 useDefaultFilters = false。
4、使用@ComponentScans 来指定扫描策略
ComponentScans 注解结构如下:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented public @interface ComponentScans { ComponentScan[] value(); }
可以看到其内部是一个ComponentScan[] 数组,所以我们可以在其中指定多个ComponentScan。
例如:
指定不同的类型,包含Controller 注解的bean 和 BookService类型的bean:
@ComponentScans(value = { @ComponentScan(value = "com.spring.annotation", includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}), @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}) },useDefaultFilters = false) }) @Configuration public class MainConfig { }
5、FilterType.CUSTOM:使用自定义规则
1>. 编写MyTypeFilter 并实现 TypeFilter 接口;
2>. match方法中 实现自定义规则。
/** * 自定义过滤规则 */ public class MyTypeFilter implements TypeFilter { /** * * @param metadataReader * @param metadataReaderFactory * @return * @throws IOException * metadataReader:读取到的当前正在扫描的类的信息 * metadataReaderFactory:可以获取到其他任何类信息的 * * */ public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { // TODO Auto-generated method stub //获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //获取当前正在扫描的类的类信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //获取当前类资源(类的路径) Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName(); System.out.println("--->"+className); if(className.contains("er")){ return true; } return false; } }
3>. 使用实例(当前扫描到的类,类名中包含er,就会注入到容器中):
@ComponentScans(value = { @ComponentScan(value = "com.spring.annotation", includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}), @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}), @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) },useDefaultFilters = false) }) @Configuration public class MainConfig { }
注解小结:
@ComponentScan value
:指定要扫描的包excludeFilters = Filter[]
:指定扫描的时候按照什么规则排除那些组件includeFilters = Filter[]
:指定扫描的时候只需要包含哪些组件FilterType.ANNOTATION
:按照注解FilterType.ASSIGNABLE_TYPE
:按照给定的类型;FilterType.ASPECTJ
:使用ASPECTJ表达式FilterType.REGEX
:使用正则指定FilterType.CUSTOM
:使用自定义规则
XML配置
我们使用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; }
附:过滤规则设置
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 |
注意:如果通过regex将filter的type设置成了正则表达式,注意在正则里面.表示所有字符,而\.才表示真正的.字符。例如我们的正则表示以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>
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。