一、@Configuration 和 @Bean
在说@ComponentScan注解前,先来搞明白@Configuration 和 @Bean 这两个注解是干啥的。
在没有注解驱动开发前,要想在spring中注入一个bean,是通过 .xml 文件来实现的:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="people" class="fengge.DTO.CarDTO">
<property name="id" value=1></property>
<property name="brand" value="BMW"></property>
</bean>
</beans>
public class CarDTO {
private Integer id;
private String brand;
}
public class DemoTest {
public void test2() {
// 1、默认从src下加载.xml,根据配置文件创建 容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2、从容器对象中取得 需要的 bean对象
CarDTO carDTO = (CarDTO) context.getBean("carDTO");
System.out.println(carDTO);
}
}
CarDTO(id=1, brand=BMW)
有了注解后是这样实现的:
// 相当于原来的xml文件,告诉spring这是个配置类
public class TestConfig {
// 给spring注入一个bean,类型是返回值类型,id是默认是方法名
public CarDTO carDTO(){
return new CarDTO(1,"BMW");
}
}
/**
* 注解 @Configuration 和 @Bean
*/
public void test() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig.class);
CarDTO bean = applicationContext.getBean(CarDTO.class);
Console.log(bean);
}
CarDTO(id=1, brand=BMW)
这里可以看出:
- @Configuration :相当于原来的xml文件,告诉spring这是个配置类
- @Bean:给spring注入一个bean,类型是返回值类型,id 默认是方法名
二、容器的 getBeanDefinitionNames() 方法
applicationContext.getBeanDefinitionNames() 是获取容器中所有bean的name,通过这个可以判断 @ComponentScan()扫描配置是否正确。
这个是在这里介绍下这个方法的主要原因。
同时呢,上面我们说到了@Bean:注解生成的bean的 id 默认是方法名,若是指定了则为指定值,我们用getBeanDefinitionNames()来获取下就知道了,如下:
// 相当于原来的xml文件,告诉spring这是个配置类
public class TestConfig {
("car") // 给spring注入一个bean,类型是返回值类型,id是默认是方法名
public CarDTO carDTO(){
return new CarDTO(1,"BMW");
}
}
/**
* applicationContext.getBeanDefinitionNames() 获取容器中所有bean的name,
* 通过这个可以判断@ComponentScan()扫描配置是否正确
*/
public void test1() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig.class);
Console.log(applicationContext.getBeanDefinitionNames());
}
这里可以看到这个bean的name变成了“car”,而不是“carDTO”。
三、@ComponentScan
前面都是引子,现在开始介绍主角。
@ComponentScan(value = "XXX") 是用来告诉spring去哪扫描要注入的bean。为了兼容及灵活配置扫描路径,这个注解定义了很多的参数,具体的:
- basePackages与value: 用于指定包的路径,进行扫描
- basePackageClasses: 用于指定某个类的包的路径进行扫描
- nameGenerator: bean的名称的生成器
- useDefaultFilters: 是否开启对@Component,@Repository,@Service,@Controller的类进行检测
- includeFilters: 包含的过滤条件
FilterType.ANNOTATION:按照注解过滤
FilterType.ASSIGNABLE_TYPE:按照给定的类型
FilterType.ASPECTJ:使用ASPECTJ表达式 (不常用)
FilterType.REGEX:正则 (不常用)
FilterType.CUSTOM:自定义规则
- excludeFilters: 排除的过滤条件,用法和includeFilters一样
为了介绍下面的例子,先把文件路径及几个bean的定义列举一下:
public interface DeptDao {
}
public class DeptDaoClass_Component {
}
public class DeptDaoClass_Controller {
}
public class DeptDaoClass_Repository {
}
public class DeptDaoClass_Service {
}
可以看到fengge.dao路径下有1个接口、4个类,且分别用 @Component,@Repository,@Service,@Controller 注解标注。
下面我们开始举例。
举例一:value = "fengge.dao"
basePackages与value: 用于指定包的路径,进行扫描。这里扫描下fengge.dao路径下的bean。
(value = "fengge.dao")
public class TestConfig01 {
}
/**
* 扫描路径 @ComponentScan(value = "fengge.dao")
* 这个路径下有一个是接口类型DeptDao,不是具体的类,所以不会产生bean,控制台会打印 Ignored because not a concrete top-level 信息
* 同时可以看到,@Bean、@Controller、@Service、@Component、@Repository注解的类都会被扫描成bean,注册到容器
*/
public void test3() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig01.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
Stream.of(beanDefinitionNames).forEach(System.out::println);
}
testConfig01
deptDaoClass_Component
deptDaoClass_Controller
deptDaoClass_Repository
deptDaoClass_Service
当然真正结果打印不止这些bean,这里只展示了fengge.dao下的bean。
可以看到:
- @Controller、@Service、@Component、@Repository注解的类都会被扫描成bean,注册到容器
- 但接口类型即使加上@Component等注解,也不会实例化成bean,比如这里的 DeptDao 接口,可以看到并未生成对应的bean。
举例二:excludeFilters 排除某些范围
这里按注解类型排除了@Controller、@Service注解的bean。
(value = "fengge.dao", excludeFilters = {
.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
})
public class TestConfig02 {
}
/**
* excludeFilters排除某些范围
* 这里按注解类型排除了@Controller、@Service注解的bean
*/
public void test4() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig02.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
Stream.of(beanDefinitionNames).forEach(System.out::println);
}
testConfig02
deptDaoClass_Component
deptDaoClass_Repository
可以看到加了@Controller、@Service注解的bean不会被扫描到。
另外,主配置类(TestConfig、TestConfig01、TestConfig02)无论如何都会生成bean,不受扫描配置的影响。
举例三:includeFilters指定某些范围
(1)先看过滤类型为:FilterType.ANNOTATION:按照注解过滤
这里按注解类型指定@Controller、@Service注解的bean才能被扫描。
(value = "fengge.dao", includeFilters = {
.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})},
useDefaultFilters = false
)
public class TestConfig03 {
/**
* includeFilters指定某些范围
* 这里按注解类型排除了@Controller、@Service注解的bean
* useDefaultFilters默认是true。表示使用默认的过滤器。即默认Filter就会处理@Component、@Controller、@Service、@Repository这些注解的Bean。
* 所以useDefaultFilters = true,则不仅fengge.dao下的@Controller、@Service会扫描到,@Component、@Repository也会被扫描到
*/
public void test5() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig03.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
Stream.of(beanDefinitionNames).forEach(System.out::println);
}
testConfig03
deptDaoClass_Controller
deptDaoClass_Service
可以看到只有@Controller、@Service注解的bean被扫描并生成。
(2)再看过滤类型为:FilterType.ASSIGNABLE_TYPE:按照给定的类型
(value = "fengge.dao", includeFilters = {
.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {DeptDaoClass_Component.class})},
useDefaultFilters = false
)
public class TestConfig04 {
}
public void test6() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig04.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
Stream.of(beanDefinitionNames).forEach(System.out::println);
}
testConfig04
deptDaoClass_Component
可以看到只有指定类型的bean。
(3)最后看下过滤类型为: FilterType.CUSTOM:自定义规则
public class MyFilterType implements TypeFilter {
/**
* MetadataReader 读取到当前正在扫描类的信息
* MetadataReaderFactory 可以获取到其他任何类信息
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前正在扫描的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
System.out.println("===============>" + className);
if (className.contains("Co")) {
return true;
}
return false;
}
}
(value = "fengge.dao", includeFilters = {
.Filter(type = FilterType.CUSTOM, classes = {MyFilterType.class})},
useDefaultFilters = false
)
public class TestConfig05 {
}
/**
* includeFilters指定某些范围
* FilterType.CUSTOM是自定义扫描类型,即className包含“Co”类型
*/
public void test7() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig05.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
Stream.of(beanDefinitionNames).forEach(System.out::println);
}
testConfig05
deptDaoClass_Component
deptDaoClass_Controller
这里是说自定义扫描className包含“Co”类型的bean。
四、@ComponentScans
@ComponentScans可包含多个@ComponentScan,扫描范围取并集。
(value = {
(value = "fengge.dao", includeFilters = {
.Filter(type = FilterType.CUSTOM, classes = {MyFilterType.class})},
useDefaultFilters = false
),
(value = "fengge.dao", includeFilters = {
.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})},
useDefaultFilters = false
)
})
public class TestConfig06 {
}
/**
* 注解@ComponentScans可包含多个@ComponentScan,扫描范围取并集
*/
public void test8() throws ParseException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig06.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
Stream.of(beanDefinitionNames).forEach(System.out::println);
}
testConfig06
deptDaoClass_Component
deptDaoClass_Controller
deptDaoClass_Service
以上代码见: https://github.com/ImOk520/myspringcloud
五、源码
源码中到底是怎样把 @ComponentScan(value = "fengge.dao") 这样路径下的所有bean找到又转化成resouse的呢?
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
public Resource[] getResources(String locationPattern) throws IOException {
if (this.resourceLoader instanceof ResourcePatternResolver) {
return ((ResourcePatternResolver) this.resourceLoader).getResources(locationPattern);
}
return super.getResources(locationPattern);
}