我们在Spring源码-ApplicationContext中介绍了ApplicationContext都做了什么,其中会对容器中的BeanDefinition对应的class进行初始化,那么BeanDefinition是怎么产生的呢?其中ClassPathBeanDefinitionScanner是做什么用的呢?
ClassPathBeanDefinitionScanner类扫描器,作用通过对制定的basepackage进行扫描,创建、注册BeanDefinition。见类图:
见下面代码,在无参构造方法中初始化ClassPathBeanDefinitionScanner,之后调用scan(basePackages)将basePackages下的class文件生成BeanDefiniton同时注册
1 | public AnnotationConfigApplicationContext(String... basePackages) { |
scan的解析
ClassPathBeanDefinitionScanner.scan()–>ClassPathBeanDefinitionScanner.doScan(),doScan方法的逻辑如下
- 文件生成BeanDefinition
- 针对指定的Basepackage找对应的文件,即Resource
- 将Resource转换成BeanDefinition注册到容器中
- 用spring-asm将Resource生成MetatDataReader
- 判断class是否符合容器注入条件(利用typefilter.match)
- 生成BeanDefinition,加入到返回值中返回
- 处理BeanDefinition
1 | protected Set<BeanDefinitionHolder> doScan(String... basePackages) { |
查找并且生成BeanDefinition
主要的方法是findCandidateComponents,通过指定的basePackage生成BeanDefinition,其中关键之一是PathMatchingResourcePatternResolver,里面会有目录的匹配引擎来找打牌basePackage下所有符合规则的类。
下面的代码是整个findCandidateComponents的逻辑,其中getResourcePatternResolver()就是获取PathMatchingResourcePatternResolver
1 | public Set<BeanDefinition> findCandidateComponents(String basePackage) { |
文件生成Resouces
getResourcePatternResolver()对应的是PathMatchingResourcePatternResolver类,getResources流程如下
- findCandidateComponents()方法中,先给basePackage先加上classPath*:,调用getResources方法
- 走到findPathMatchingResources所在的分支处理locationPattern,分离出rootDirPath和subPattern
- 再次调用getResources(rootDirPath)方法回到上面的方法走到else分支调用findAllClassPathResources方法
- 在findAllClassPathResources中调用doFindAllClassPathResources,加载RootDir对应的rootDirResources
- 继续回到<2>中的findPathMatchingResources,遍历rootDirResources,这里主要执行else分支doFindPathMatchingFileResources2>
- 通过rootDirPath和subPattern(即 */**等)找到所有符合规则的Class对应的Resource,过程是:
- doFindPathMatchingFileResources–>doFindMatchingFileSystemResources
- doFindMatchingFileSystemResources–>retrieveMatchingFiles
- retrieveMatchingFiles–>doRetrieveMatchingFiles递归执行直到找到所有符合规则的File,并且在doFindMatchingFileSystemResources生成FileSystemResource
1 | //basePackage先加上 classPath*: |
1 | public Resource[] getResources(String locationPattern) throws IOException { |
1 | protected Resource[] findPathMatchingResources(String locationPattern) throws IOException { |
doRetrieveMatchingFiles查找符合规则的文件
1 | protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException { |
Reosurce生成BenDefinition
scanCandidateComponents方法中循环上面生成的Resource数组,生成BeanDefinition,其中需要
- 获取metadataReader
- 判断resource是否符合我们的注入条件
- 生成ScannedGenericBeanDefinition
1 | private Set<BeanDefinition> scanCandidateComponents(String basePackage) { |
获取Resource对应的MetadataReader
- 这里是CachingMetadataReaderFactory类,它继承了SimpleMetadataReaderFactory调用super.getMetadataReader得到SimpleMetadataReader
- SimpleMetadataReader构造方法SimpleMetadataReader(resource,defaultResourceLoader.getClassLoader)
- SimpleMetadataReader通过spring-asm来操作class文件字节码解析resource(resource通过io读成byte具体如何解析见jvm对class类的解析)
1 | //SimpleMetadataReaderFactory |
调用isCandidateComponent方法,通过MetaDataReader来判断其是否符合注入条件,我们通过ClassPathBeanDefinitionScanner的构造方法层层跟进到registerDefaultFilters方法时候就能知道spring的includeFilters支持那些默认的注解见下面,(如果要实现自定义注解要实现TypeFilter和matcher方法具体见Spring源码-实现自定义注解)
1 | protected void registerDefaultFilters() { |
isCandidateComponent的方法注释
1 | protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { |
生成BeanDefinition,这里是ScannedGenericBeanDefinition,设置Resource和Source属性,再次isCandidateComponent判断是否符合条件如果符合直接加入到结果candidates中(不是内部类同时 不是接口和抽象类,或者是接口但是方法有Lookup注解)
处理BeanDefinition
回到doScan方法,在执行完findCandidateComponents方法回去了需要注册的BeanDefiniton之后,我们要继续填充beanDefiniton的属性,并且注册到registry,见代码:
1 | protected Set<BeanDefinitionHolder> doScan(String... basePackages) { |