1.@ComponentScan注解是做什么的?
2.basePackages的方式和basePackageClasses的方式有什么区别?你建议用哪个?为什么?
3.useDefaultFilters有什么用?
4.常见的过滤器有哪些类型?
5.@ComponentScan是在哪个类中处理的?说一下大概的解析过程?
@ComponentScan
@ComponentScan用于批量注册bean,这个注解会让spring去扫描某些包及其子包中所有的类,然后将满足一定条件的类作为bean注册到spring容器容器中。
具体需要扫描哪些包?以及这些包中的类满足什么条件时被注册到容器中,这些都可以通过这个注解中的参数动态配置。
先来看一下这个注解的定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class) //@1
public @interface ComponentScan {
//指定需要扫描的包
@AliasFor("basePackages")
String[] value() default {};
//作用同value
@AliasFor("value")
String[] basePackages() default {};
//指定一些类,spring容器会扫描这些类所在的包及其子包中的类
Class<?>[] basePackageClasses() default {};
//自定义bean名称生成器
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
//需要扫描包中的那些资源
String resourcePattern() default "**/*.class";
//对扫描的类是否启用默认过滤器,默认为true
boolean useDefaultFilters() default true;
//过滤器:用来配置被扫描出来的那些类会被作为组件注册到容器中
Filter[] includeFilters() default {};
//过滤器,和includeFilters作用刚好相反,用来对扫描的类进行排除的,被排除的类不会被注册到容器中
Filter[] excludeFilters() default {};
//是否延迟初始化被注册的bean
boolean lazyInit() default false;
}
@ComponentScan 工作的过程:
- spring会扫描指定的包,且会递归下面子包,得到一批类的数组
- 然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中
使用这个注解,主要关注两个问题:
- 需要扫描哪些包?通过value,backPackages,basePackageClasses这3个参数来控制
- 过滤器有哪些?通过useDefaultFilters,includeFilters,excludeFilters这个3个参数来控制
默认情况下,任何参数都不设置的情况下,会将@ComponentScan修饰的类所在的包作为扫描包。默认情况下useDefaultFilters为ture,spring容器内部会使用默认的过滤器,规则是:凡是类上有@Repository、@Service、@Controller、@Component这几个注解中的任何一个的,那么这个类就会被作为bean注册到spring容器中。
案例1:默认配置
整体的结构:
对应包下生成不同的类:
//controller包下
@Controller
public class UserController {
}
//dao包下
@Repository
public interface UserDao {
}
//dao包下
@Repository
public class UserDao {
}
//service包下和其包下impl包实现类
public interface UserService {
}
@Service
public class UserServiceImpl implements UserService {
}
//model包下
@Component
public class User {
}
//最外层包下
@ComponentScan
public class ScanBean1 {
}
我们看到注解我们使用的有@Controller,@Service,@Repository,@Component。按照@ComponentScan扫描的默认规则:扫描其所在类,所在包,以及其子包下被这几个注解标注的类都会被注册到spring容器中。
测试:
@Test
public void testComponentScan(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean1.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
String[] aliases = context.getAliases(beanName);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s",
beanName,
Arrays.asList(aliases),
context.getBean(beanName)));
}
}
看运行结果:
bean名称:scanBean1,别名:[],bean对象:com.spring.componentScan.ScanBean1@338fc1d8
bean名称:userController,别名:[],bean对象:com.spring.componentScan.controller.UserController@4722ef0c
bean名称:userDao,别名:[],bean对象:com.spring.componentScan.dao.UserDao@48e1f6c7
bean名称:user,别名:[],bean对象:com.spring.componentScan.model.User@55cb6996
bean名称:userServiceImpl,别名:[],bean对象:com.spring.componentScan.service.impl.UserServiceImpl@1807e3f6
案例2:指定需要扫描的包
指定扫描的包,我们通过value或者basePackage来配置,二选一。
@ComponentScan({
"com.spring.componentScan.controller",
"com.spring.componentScan.dao"
})
public class ScanBean2 {
}
运行结果:
bean名称:scanBean2,别名:[],bean对象:com.spring.componentScan.ScanBean2@6f204a1a
bean名称:userController,别名:[],bean对象:com.spring.componentScan.controller.UserController@2de56eb2
bean名称:userDao,别名:[],bean对象:com.spring.componentScan.dao.UserDao@5f8e8a9d
案例3:basePackageClasses指定扫描范围
在config包下,我们建个Service包,下面有两个类:
@Service
public class Service1 {
}
@Service
public class Service2 {
}
config下有个接口类:
public interface ScanBeanInterface {
}
在componentScan包下新建ScanBean3:
@ComponentScan(basePackageClasses = {
ScanBeanInterface.class
})
public class ScanBean3 {
}
测试:
@Test
public void testComponentScan3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean3.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
String[] aliases = context.getAliases(beanName);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s",
beanName,
Arrays.asList(aliases),
context.getBean(beanName)));
}
}
运行结果:
bean名称:scanBean3,别名:[],bean对象:com.spring.componentScan.ScanBean3@eadd4fb
bean名称:service1,别名:[],bean对象:com.spring.componentScan.config.Service.Service1@740fb309
bean名称:service2,别名:[],bean对象:com.spring.componentScan.config.Service.Service2@7bd7d6d6
案例4:includeFilters的使用
看一个includeFilters的定义:
Filter[] includeFilters() default {};
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
可以看出Filter也是一个注解,参数:
type:过滤器的类型,是个枚举类型,5种类型
ANNOTATION:通过注解的方式来筛选候选者,即判断候选者是否有指定的注解
ASSIGNABLE_TYPE:通过指定的类型来筛选候选者,即判断候选者是否是指定的类型
ASPECTJ:ASPECTJ表达式方式,即判断候选者是否匹配ASPECTJ表达式
REGEX:正则表达式方式,即判断候选者的完整名称是否和正则表达式匹配
CUSTOM:用户自定义过滤器来筛选候选者,对候选者的筛选交给用户自己来判断
value:和参数classes效果一样,二选一
classes:3种情况如下
当type=FilterType.ANNOTATION时,通过classes参数可以指定一些注解,用来判断被扫描的类上是否有classes参数指定的注解
当type=FilterType.ASSIGNABLE_TYPE时,通过classes参数可以指定一些类型,用来判断被扫描的类是否是classes参数指定的类型
当type=FilterType.CUSTOM时,表示这个过滤器是用户自定义的,classes参数就是用来指定用户自定义的过滤器,自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter接口
pattern:2种情况如下
当type=FilterType.ASPECTJ时,通过pattern来指定需要匹配的ASPECTJ表达式的值
当type=FilterType.REGEX时,通过pattern来自正则表达式的值
需求,扫描包含指定注解的类
新建一个包,在其下面建几个类
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {
}
//使用我们自定义的注解,当有这个注解的时候,我们将其注册到spring容器中
@MyBean
public class Service1 {
}
@Component
public class Service2 {
}
//使用Filter过滤器,注意:需要把ComponentScan的默认过滤器设置为false,否则会有默认过滤器,会把@Component、@Repository、@Service、@Controller这几个注解的类也注册到容器中
@ComponentScan(useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyBean.class)
})
public class ScanBean {
}
测试:
@Test
public void testComponentScan4() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
String[] aliases = context.getAliases(beanName);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s",
beanName,
Arrays.asList(aliases),
context.getBean(beanName)));
}
}
结果:
bean名称:scanBean,别名:[],bean对象:com.spring.componentScan1.ScanBean@2a8448fa
bean名称:service1,别名:[],bean对象:com.spring.componentScan1.Service1@6f204a1a
扩展:自定义注解支持定义bean名称
我们对Mybean进行修改,使用@AliasFor
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component //添加一个@Component注解
public @interface MyBean {
//添加一个AliasFor注解和vaue
@AliasFor(annotation = Component.class)
String value() default "";
}
service1进行修改:
//定义bean名
@MyBean("serviceBean")
public class Service1 {
}
运行测试结果:
bean名称:scanBean,别名:[],bean对象:com.spring.componentScan1.ScanBean@26abb146
bean名称:serviceBean,别名:[],bean对象:com.spring.componentScan1.Service1@72c8e7b
案例:注册指定类型的类
新建包,在新包下建类
public interface IService {
}
public class ServiceA implements IService {
}
public class ServiceB implements IService {
}
@Service
public class ServiceC {
}
@ComponentScan(
useDefaultFilters = false, //不启用默认过滤器
includeFilters = {
//我们只注册实现了IService接口的类
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class)
}
)
public class ScanBean4 {
}
测试方法:
@Test
public void testComponentScan5() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean4.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
String[] aliases = context.getAliases(beanName);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s",
beanName,
Arrays.asList(aliases),
context.getBean(beanName)));
}
}
测试结果:
bean名称:scanBean4,别名:[],bean对象:com.spring.componentScan2.ScanBean4@1807e3f6
bean名称:serviceA,别名:[],bean对象:com.spring.componentScan2.ServiceA@480d3575
bean名称:serviceB,别名:[],bean对象:com.spring.componentScan2.ServiceB@f1da57d
尽管ServiceC有标记@Service,但是没有注册到容器中。
自定义Filter
用法
- 设置@Filter中type的类型为:FilterType.CUSTOM
- 自定义过滤器类,需要实现接口:TypeFilter
- 设置@Filter中的class为自定义的过滤器类型
看下TypeFilter这个接口:
@FunctionalInterface
public interface TypeFilter {
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException;
}
MetadataReader接口
类元数据读取器,可以读取一个类上的任意信息,如类上面的注解信息、类的磁盘路径信息、类的class对象的各种信息,spring进行了封装,提供了各种方便使用的方法:
public interface MetadataReader {
/**
* 返回类文件的资源引用
*/
Resource getResource();
/**
* 返回一个ClassMetadata对象,可以通过这个读想获取类的一些元数据信息,如类的class对象、是否是接口、是否有注解、是否是抽象类、父类名称、接口名称、内部包含的之类列表等等,可以去看一下源码
*/
ClassMetadata getClassMetadata();
/**
* 获取类上所有的注解信息
*/
AnnotationMetadata getAnnotationMetadata();
}
MetadataReaderFactory接口
类元数据读取器工厂,可以通过这个类获取任意一个类的MetadataReader对象:
public interface MetadataReaderFactory {
/**
* 返回给定类名的MetadataReader对象
*/
MetadataReader getMetadataReader(String className) throws IOException;
/**
* 返回指定资源的MetadataReader对象
*/
MetadataReader getMetadataReader(Resource resource) throws IOException;
}
案例
1.自定义TypeFilter类:
public class MyFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取类名
String className = metadataReader.getClassMetadata().getClassName();
try {
//获取类信息
Class<?> name = Class.forName(className);
//判断这个类是不是IService类型的
boolean assignable = IService.class.isAssignableFrom(name);
return assignable;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return false;
}
}
2.添加类:
public interface IService {
}
public class ServiceB implements IService {
}
@Service
public class ServiceA {
}
3.使用自定义过滤器:
@ComponentScan(useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyFilter.class)
})
public class ScanBean5 {
}
测试方法:
@Test
public void testComponentScan6() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean5.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
String[] aliases = context.getAliases(beanName);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s",
beanName,
Arrays.asList(aliases),
context.getBean(beanName)));
}
}
运行结果:
bean名称:scanBean5,别名:[],bean对象:com.spring.componentScan3.ScanBean5@480d3575
bean名称:serviceB,别名:[],bean对象:com.spring.componentScan3.ServiceB@f1da57d
excludeFilters用法和includeFilters一样
@CompontentScan注解是被下面这个类处理的:
org.springframework.context.annotation.ConfigurationClassPostProcessor
很重要,这个类建议阅读,好好研究。
总结
- @ComponentScan用于批量注册bean,spring会按照这个注解的配置,递归扫描指定包中的所有类,将满足条件的类批量注册到spring容器中
- 可以通过value、basePackages、basePackageClasses 这几个参数来配置包的扫描范围
- 可以通过useDefaultFilters、includeFilters、excludeFilters这几个参数来配置类的过滤器,被过滤器处理之后剩下的类会被注册到容器中
- 指定包名的方式配置扫描范围存在隐患,包名被重命名之后,会导致扫描实现,所以一般我们在需要扫描的包中可以创建一个标记的接口或者类,作为basePackageClasses的值,通过这个来控制包的扫描范围
- @CompontScan注解会被ConfigurationClassPostProcessor类递归处理,最终得到所有需要注册的类。