推荐阅读:
Spring官网阅读系列
彻底读懂Spring(一)读源码,我们可以从第一行读起
Spring执行流程图如下:
如果图片显示不清楚可以访问如下链接查看高清大图:
Spring执行流程图
这个流程图会随着我们的学习不断的变得越来越详细,也会越来越复杂,希望在这个过程中我们都能朝着精通Spring的目标不断前进!
在上篇文章我们学习了Spring中的第一行代码,我们已经知道了Spring中的第一行代码其实就是创建了一个AnnotatedBeanDefinitionReader对象,这个对象的主要作用就是注册bd(BeanDefinition)到容器中。并且在创建这个对象的过程中,Spring还为容器注册了开天辟地的几个bd,包括ConfigurationClassPostProcessor,AutowiredAnnotationBeanPostProcessor等等。
那么在本文中,我们就一起来看看Spring中的第二行代码又做了些什么?
Spring中的第二行代码
第二行代码在上面的流程图中已经标注的非常明白了,就是
this.scanner = new ClassPathBeanDefinitionScanner(this);
只是简单的创建了一个ClassPathBeanDefinitionScanner对象。**那么这个ClassPathBeanDefinitionScanner有什么作用呢?从名字上来看好像就是这个对象来完成Spring中的扫描的,真的是这样吗?**希望同学们能带着这两个问题往下看
ClassPathBeanDefinitionScanner源码分析
这个类名直译过来就是:类路径下的BeanDefinition的扫描器,所以我们就直接关注其扫描相关的方法,就是其中的doScan方法。其代码如下:
// 这个方法会完成对指定包名下的class文件的扫描 // basePackages:指定包名,是一个可变参数 protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { // 1.findCandidateComponents这个方法是实际完成扫描的方法,也是接下来我们要分析的方法 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) {、 // 上篇文章中我们已经分析过了,完成了@Scope注解的解析 // 参考《彻底读懂Spring(一)读源码,我们可以从第一行读起》 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { // 2.如果你对BeanDefinition有一定了解的话,你肯定会知道这个判断一定会成立的,这意味着 // 所有扫描出来的bd都会执行postProcessBeanDefinition方法进行一些后置处理 postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { // 3. 是不是一个AnnotatedBeanDefinition,如果是的话,还需要进行额外的处理 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // 4.检查容器中是否已经有这个bd了,如果有就不进行注册了 if (checkCandidate(beanName, candidate)) { // 下面这段逻辑在上篇文章中都已经分析过了,这里就直接跳过了 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
上面这段代码主要做了四件事
- 通过findCandidateComponents方法完成扫描
- 判断扫描出来的bd是否是一个AbstractBeanDefinition,如果是的话执行postProcessBeanDefinition方法
- 判断扫描出来的bd是否是一个AnnotatedBeanDefinition,如果是的话执行processCommonDefinitionAnnotations方法
- 检查容器中是否已经有这个bd了,如果有就不进行注册了
接下来我们就一步步分析这个方法,搞明白ClassPathBeanDefinitionScanner到底能起到什么作用
1、通过findCandidateComponents方法完成扫描
findCandidateComponents方法源码如下:
public Set<BeanDefinition> findCandidateComponents(String basePackage) { if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { // 正常情况下都是进入这个判断,对classpath下的class文件进行扫描 return scanCandidateComponents(basePackage); } }
- addCandidateComponentsFromIndex
不用过多关注这个方法。正常情况下Spring都是采用扫描classpath下的class文件来完成扫描,但是虽然基于classpath扫描速度非常快,但通过在编译时创建候选静态列表,可以提高大型应用程序的启动性能。在这种模式下,应用程序的所有模块都必须使用这种机制,因为当 ApplicationContext检测到这样的索引时,它将自动使用它而不是扫描类路径。
要生成索引,只需向包含组件扫描指令目标组件的每个模块添加附加依赖项即可:
Maven:
org.springframework spring-context-indexer 5.0.6.RELEASE true
大家有兴趣的话可以参考官网:https://docs.spring.io/spring/docs/5.1.14.BUILD-SNAPSHOT/spring-framework-reference/core.html#beans-scanning-index
这个依赖实在太大了,半天了拉不下来,我这里就不演示了
- scanCandidateComponents(basePackage)
正常情况下我们的应用都是通过这个方法完成扫描的,其代码如下:
private Set<BeanDefinition> scanCandidateComponents(String basePackage) { // 用来存储返回的bd的集合 Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { // 拼接成这种形式:classpath*:com.dmz.spring String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; // 获取到所有的class文件封装而成的Resource对象 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); // 遍历得到的所有class文件封装而成的Resource对象 for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } if (resource.isReadable()) { try { // 通过Resource构建一个MetadataReader对象,这个MetadataReader对象包含了对应class文件的解析出来的class的元信息以及注解元信息 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); // 并不是所有的class文件文件都要被解析成为bd,只有被添加了注解(@Component,@Controller等)才是Spring中的组件 if (isCandidateComponent(metadataReader)) { // 解析元信息(class元信息以及注解元信息)得到一个ScannedGenericBeanDefinition ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); } // 省略多余的代码 return candidates; }
在Spring官网阅读(一)容器及实例化 一文中,我画过这样一张图
从上图中可以看出,java class + configuration metadata 最终会转换为一个BenaDefinition,结合我们上面的代码分析可以知道,java class + configuration metadata实际上就是一个MetadataReader对象,而转换成一个BenaDefinition则是指通过这个MetadataReader对象创建一个ScannedGenericBeanDefinition。
2、执行postProcessBeanDefinition方法
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) { // 为bd中的属性设置默认值 beanDefinition.applyDefaults(this.beanDefinitionDefaults); // 注解模式下这个值必定为null,使用XML配置时, if (this.autowireCandidatePatterns != null) { beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName)); } } // 设置默认值 public void applyDefaults(BeanDefinitionDefaults defaults) { setLazyInit(defaults.isLazyInit()); setAutowireMode(defaults.getAutowireMode()); setDependencyCheck(defaults.getDependencyCheck()); setInitMethodName(defaults.getInitMethodName()); setEnforceInitMethod(false); setDestroyMethodName(defaults.getDestroyMethodName()); setEnforceDestroyMethod(false); }
可以看出,postProcessBeanDefinition方法最主要的功能就是给扫描出来的bd设置默认值,进一步填充bd中的属性
3、执行processCommonDefinitionAnnotations方法
这句代码将进一步解析class上的注解信息,Spring在创建这个abd的信息时候就已经将当前的class放入其中了,所有这行代码主要做的就是通过class对象获取到上面的注解(包括@Lazy,@Primary,@DependsOn注解等等),然后将得到注解中对应的配置信息并放入到bd中的属性中
4、注册BeanDefinition
跟**彻底读懂Spring(一)读源码,我们可以从第一行读起**的注册逻辑是一样的
通过上面的分析,我们已经知道了ClassPathBeanDefinitionScanner的作用,毋庸置疑,Spring肯定是通过这个类来完成扫描的,但是问题是,Spring是通过第二步创建的这个对象来完成扫描的吗?我们再来看看这个ClassPathBeanDefinitionScanner的创建过程:
// 第一步 public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) { this(registry, true); } // 第二步 public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) { this(registry, useDefaultFilters, getOrCreateEnvironment(registry)); } // 第三步 public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment) { this(registry, useDefaultFilters, environment, (registry instanceof ResourceLoader ? (ResourceLoader) registry : null)); } // 第四步 public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment, @Nullable ResourceLoader resourceLoader) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; if (useDefaultFilters) { // 注册默认的扫描过滤规则(要被@Component注解修饰) registerDefaultFilters(); } setEnvironment(environment); setResourceLoader(resourceLoader); }
在这个ClassPathBeanDefinitionScanner的创建过程中我们全程无法干涉,不能对这个ClassPathBeanDefinitionScanner进行任何配置。而我们在配置类上明明是可以对扫描的规则进行配置的,例如:
@ComponentScan(value = "com.spring.study.springfx.aop.service", useDefaultFilters = true, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {IndexService.class}))
所以Spring中肯定不是使用在这里创建的这个ClassPathBeanDefinitionScanner对象。
实际上真正完成扫描的时机是在我们流程图中的3-5-1步。完成扫描这个功能的类就是我们在上篇文章中所提到的ConfigurationClassPostProcessor。接下来我们就通过这个类,看看Spring到底是如何完成的扫描,这也是本文重点想要说明的问题