Spring如何玩转扫描包?
当我们指定包名basePackages, 如@ComponentScan(basePackages = “cn.zhutan.myspringtest”), Spring会去加载该文件夹下面的class文件, 封装成MetadataReader元信息, 根据元信息判断哪些类需要创建bean, 哪些类不需要创建bean, 而这个判断条件, Spring提供了很强大的扩展, 有以下几种情况
- 根据贴上某注解判断创建或者不创建bean
- 指定某些类创建或者不创建bean
- 根据正则匹配哪些bean创建或者不创建
- 根据Aspectj判断bean的创建与否
- 根据用户写的规则判断bean的创建与否
Spring如何指定过滤规则?
在注解@ComponentScan中有几个核心属性
- includeFilters, 表示满足该过滤条件时, 要创建bean
- excludeFilters, 表示满足该过滤条件时, 不创建bean
- useDefaultFilters, 表示是否使用默认的过滤器, 默认是true, Spring内部则相当于在includeFilters加入了过滤规则, 即满足加了@Component的类会创建bean, 如果我们置为false, 即使我们加了@Component也不会创建bean
查看注解@ComponentScan源码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
// 省略其他...
// 是否使用默认过滤器
boolean useDefaultFilters() default true;
// 满足条件要注入的bean
ComponentScan.Filter[] includeFilters() default {};
// 满足条件不注入的bean
ComponentScan.Filter[] excludeFilters() default {};
// 省略其他...
@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 {};
}
}
includeFilters和excludeFilters需要参数为数组ComponentScan.Filter[], 表示可以配置多个过滤条件,
每个过滤条件都是ComponentScan.Filter对象, 而是过滤条件的种类是以type属性指定的
public enum FilterType {
// 1. 根据贴上某注解判断创建或者不创建bean, 解析类AnnotationTypeFilter
ANNOTATION,
// 2. 指定某些类创建或者不创建bean, 解析类AssignableTypeFilter
ASSIGNABLE_TYPE,
// 3. 根据正则匹配哪些bean创建或者不创建, 解析类AspectJTypeFilter
ASPECTJ,
// 4. 根据Aspectj判断bean的创建与否, 解析类RegexPatternTypeFilter
REGEX,
// 5. 根据用户写的规则判断bean的创建与否, 自定义实现TypeFilter
CUSTOM
}
useDefaultFilters为true时, Spring使用默认的过滤规则, 源码如下:
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
// 注册@Component注解
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
// 注册@ManagedBean注解, 找不到则不注入
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.
}
// 注册@Named注解, 找不到则不注入
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.
}
}
以下案例针对注解, 指定类, 正则, 自定义过滤这4种进行介绍
自定义注解创建bean
@MyAnnotation
public class User { }
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation { }
@Configuration
@ComponentScan(basePackages = {"cn.zhutan.myspringtest"},
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyAnnotation.class)},
useDefaultFilters = false)
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringTest.class);
System.out.println(context.getBean(User.class)); // 可创建bean
System.out.println(context.getBean(UserController.class)); // 不创建bean
}
}
指定类创建bean
@Configuration
@ComponentScan(basePackages = {"cn.zhutan.myspringtest"},
includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = User.class)},
useDefaultFilters = false)
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringTest.class);
System.out.println(context.getBean(User.class)); // 可创建bean
System.out.println(context.getBean(UserController.class)); // 不创建bean
}
}
正则匹配创建bean
如: 包cn.zhutan.myspringtest下面带User的都需要创建bean
@Configuration
@ComponentScan(basePackages = {"cn.zhutan.myspringtest"},
includeFilters = {@ComponentScan.Filter(type = FilterType.REGEX, pattern = "cn.zhutan.myspringtest.*User.*")},
useDefaultFilters = false)
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringTest.class);
// 以下这些类均位于cn.zhutan.myspringtest包或者子包下面, 普通的pojo, 无贴任何注解和无其他处理
System.out.println(context.getBean(User.class)); // 可创建bean
System.out.println(context.getBean(UserController.class)); // 可创建bean
System.out.println(context.getBean(UserService.class)); // 可创建bean
System.out.println(context.getBean(Employee.class)); // 无法创建bean
}
}
自定义过滤规则创建bean
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 全限定名包含User的就创建, 其它不创建
return classMetadata.getClassName().contains("User");
// return true // 理论上代表所有类都会创建, 但是Spring里面还会对一些抽象类, 接口等等进行二层过滤
}
}
@Configuration
@ComponentScan(basePackages = {"cn.zhutan.myspringtest"},
includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)},
useDefaultFilters = false)
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringTest.class);
System.out.println(context.getBean(User.class)); // 可创建bean
System.out.println(context.getBean(UserController.class)); // 可创建bean
System.out.println(context.getBean(UserService.class)); // 可创建bean
System.out.println(context.getBean(Employee.class)); // 无法创建bean
}
}
总结
在@ComponentScan或者xml配置标签中, 我们可以指定哪些包下面的类需要扫描, 并通过配置includeFilters和excludeFilters判断哪些类创建bean, 过滤的规则可以使用以下5种
- 根据贴上某注解判断创建或者不创建bean
- 指定某些类创建或者不创建bean
- 根据正则匹配哪些bean创建或者不创建
- 根据Aspectj判断bean的创建与否
- 根据用户写的规则判断bean的创建与否