@ComponentScan
其实简单理解@ComponentScan的作用,就是根据定义好的扫描路径,把符合规则的类装配到Spring容器中。我们可以看一下@ComponentScan的源码。
@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 {};
}
}
上述的代码内容,其中:
- basepackages和value值:用于指定扫描路径。
- basePackageClasses:用于指定某个类的包路径扫描。
- useDefaultFilters:是否开启对@Component,@Repository,@Service,@Controller的类进行扫描检测。
- includeFilters:包含的过滤条件。
- excludeFilters: 排除的过滤条件,用法和includeFilters一样。
Demo
接下来我们来进行简单的测试:
新建一个SpringBoot项目,也可以是普通的maven项目,这里用SpringBoot项目作为演示,如果是maven项目,需要添加Spring的maven依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
如果是SpringBoot项目,直接使用下面的依赖即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
简单创建几个类,包含dao、service、controller等。
在项目的主配置类上贴上注解@ComponentScan,并制定其扫描的包路径。
@SpringBootApplication
@ComponentScan(value = "com.caiyq.spring.annotation")
public class SpringAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAnnotationApplication.class, args);
}
}
接下来写一个测试类,测试被Spring扫描的类有哪些。
@Test
public void testComponentScan() {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(SpringAnnotationApplication.class);
//getBeanDefinitionNames方法的作用是:获取容器中的Bean的名称
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
}
测试结果如下(只截取其中一部分)
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springAnnotationApplication
bookController
bookDao
bookServiceImpl
org.springframework.boot.autoconfigure.AutoConfigurationPackages
其实在测试结果中有很多,大部分是Spring内部需要装配的Bean,而我们自己定义的也在其中,说明了在启动Spring容器的时候,Spring已经去扫描了指定包下的贴有符合规则注解的类,并装配到容器中了。
excludeFilters
excludeFilters的作用是,指定排除规则,让Spring不要去扫描指定规则的类。
@SpringBootApplication
@ComponentScan(value = "com.caiyq.spring.annotation", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class SpringAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAnnotationApplication.class, args);
}
}
在excludeFilters里面通过@Filter注解,可以指定排除规则,那么在@Filter里面,关于过滤的规则类型,我们可以点进去看看FilterType,有如下几种:
public enum FilterType {
ANNOTATION,
ASSIGNABLE_TYPE,
ASPECTJ,
REGEX,
CUSTOM;
private FilterType() {
}
}
- ANNOTATION:注解,这也是默认的方式
- ASSIGNABLE_TYPE:指定类型
- ASPECTJ:AspectJ表达式
- REGEX:正则
- CUSTOM:自定义
上面的测试,是通过注解的方式过滤掉了贴有@Controller注解的类,那么接下来测试一下结果:
bookDao
bookServiceImpl
......
在控制台中,我们看到了只有dao和service,controller已经被过滤掉,即不会被装配到Spring容器中。那么接着继续看,因为classes中可以是数组,是可以过滤多个的。如下就是过滤了@Controller和@Service的类。
@SpringBootApplication
@ComponentScan(value = "com.caiyq.spring.annotation", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
})
public class SpringAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAnnotationApplication.class, args);
}
}
还可以用ASSIGNABLE_TYPE指定类型的方式,如下:
@SpringBootApplication
@ComponentScan(value = "com.caiyq.spring.annotation", excludeFilters = {
// @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookDao.class})
})
public class SpringAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAnnotationApplication.class, args);
}
}
BookDao是我们自己创建的类,用ASSIGNABLE_TYPE的方式,指定需要过滤掉的类。测试一下结果:
bookController
bookServiceImpl
......
除了使用以上这两种方式,还可以使用AspectJ表达式的方式和正则的方式,实际上这两种方式在实际应用中用到的比较少,所以在这里不做过多介绍,有兴趣的朋友可以去试试。
接着介绍一下CUSTOM自定义的方式。它其实是自己定义一个Filter规则,然后通过该规则实行过滤。做法就是创建一个自定义的过滤器,实现TypeFilter接口。
public class MyTypeFilter implements TypeFilter {
/**
*
* @param metadataReader 读取到当前正在扫描的类的信息
* @param metadataReaderFactory 可以获取到其他任何类的信息
* @return
* @throws IOException
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取当前类的注解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前正在扫描的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前的资源信息
Resource resource = metadataReader.getResource();
//自定义规则:如果当前类名包含Service或者Controller,就被过滤掉
String className = classMetadata.getClassName();
if (className.contains("Service") || className.contains("Controller")) {
return true;
}
return false;
}
}
在主配置类中做如下修改:
@SpringBootApplication
@ComponentScan(value = "com.caiyq.spring.annotation", excludeFilters = {
// @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
// @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookDao.class})
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
})
public class SpringAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAnnotationApplication.class, args);
}
}
测试一下结果:
bookDao
......
includeFilters
includeFilters的意思是扫描包含指定规则的类,其用法其实和excludeFilters的用法是差不多的,这里不再做重复阐述,我们用一个ANNOTATION的方式来测试一下。
@ComponentScan(value = "com.caiyq.spring.annotation", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class SpringAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAnnotationApplication.class, args);
}
}
当我们运行测试的时候,可想而知,其结果应该是只有@Controller的类被加载到了Spring容器中,那么看一下结果:
bookController
bookDao
bookServiceImpl
......
往往程序的执行结果和我们想的并不是完全一样,但是这样的结果,必定有其原因:
其实@Component,@Controller,@Service和@Repository三者功能相同,后三个注解是为了对应后端代码的三层结构由第一个注解"复制"而来的,查看源码可以发现,后三个注解上面都有一个@Component。也就是说@Controller,@Service和@Repository除了是它们自己,也是一个@Component。在进行includeFilters过滤时,如果useDefaultFilters =true,它会默认把所有包含@Component注解的类都进行扫描。所以上述程序,还需要加上useDefaultFilters =false,才能真正执行到如我们期望的结果。
@ComponentScan(value = "com.caiyq.spring.annotation", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)
public class SpringAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAnnotationApplication.class, args);
}
}