ClassPathScanningCandidateComponentProvider#findCandidateComponents
public Set<BeanDefinition> findCandidateComponents(String basePackage) { // 上面说过了CandidateComponentsIndex是Spring5提供的优化扫描的功能 // 显然这里编译器我们没有写META-INF/spring.components索引文件,所以此处不会执行Spring5 的扫描方式,所以我暂时不看了(超大型项目才会使用Spring5的方式) if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { // Spring 5之前的方式(绝大多数情况下,都是此方式) return scanCandidateComponents(basePackage); } }
scanCandidateComponents:根据basePackage
扫描候选的组件们(非常重要)
private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { // 1.根据指定包名 生成包搜索路径 //通过观察resolveBasePackage()方法的实现, 我们可以在设置basePackage时, 使用形如${}的占位符, Spring会在这里进行替换 只要在Enviroment里面就行 // 本次值为:classpath*:com/fsx/config/**/*.class String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; //2. 资源加载器 加载搜索路径下的 所有class 转换为 Resource[] // 拿着上面的路径,就可以getResources获取出所有的.class类,这个很强大~~~ // 真正干事的为:PathMatchingResourcePatternResolver#getResources方法 // 此处能扫描到两个类AppConfig(普通类,没任何注解标注)和RootConfig。所以接下里就是要解析类上的注解,以及过滤掉不是候选的类(比如AppConfig) // 注意:这里会拿到类路径下(不包含jar包内的)的所有的.class文件 可能有上百个,然后后面再交给后面进行筛选~~~~~~~~~~~~~~~~(这个方法,其实我们也可以使用) // 当然和getResourcePatternResolver和这个模版有关 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); // 记录日志(下面我把打印日志地方都删除) boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); // 接下来的这个for循环:就是把一个个的resource组装成 for (Resource resource : resources) { //文件必须可读 否则直接返回空了 if (resource.isReadable()) { try { //读取类的 注解信息 和 类信息 ,两大信息储存到 MetadataReader MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); // 根据TypeFilter过滤排除组件。因为AppConfig没有标准@Component或者子注解,所以肯定不属于候选组件 返回false // 注意:这里一般(默认处理的情况下)标注了默认注解的才会true,什么叫默认注解呢?就是@Component或者派生注解。还有javax....的,这里省略啦 if (isCandidateComponent(metadataReader)) { //把符合条件的 类转换成 BeanDefinition ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); // 再次判断 如果是实体类 返回true,如果是抽象类,但是抽象方法 被 @Lookup 注解注释返回true (注意 这个和上面那个是重载的方法) // 这和上面是个重载方法 个人觉得旨在处理循环引用以及@Lookup上 if (isCandidateComponent(sbd)) { candidates.add(sbd); } } } } } } return candidates; } // 备注:此时ComponentScan这个注解还并没有解析
ClassPathBeanDefinitionScanner#postProcessBeanDefinition
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) { // 位Bean定义 执行些默认的信息 // BeanDefinitionDefaults是个标准的javaBean,有一些默认值 beanDefinition.applyDefaults(this.beanDefinitionDefaults); // 自动依赖注入 匹配路径(此处为null,不进来) if (this.autowireCandidatePatterns != null) { beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName)); } } AbstractBeanDefinition#applyDefaults: public void applyDefaults(BeanDefinitionDefaults defaults) { setLazyInit(defaults.isLazyInit()); setAutowireMode(defaults.getAutowireMode()); setDependencyCheck(defaults.getDependencyCheck()); setInitMethodName(defaults.getInitMethodName()); setEnforceInitMethod(false); setDestroyMethodName(defaults.getDestroyMethodName()); setEnforceDestroyMethod(false); }
就这样,扫描的Bean就全部注册完成了(若你的Config比较多的情况下,用扫描的方式更加简单些~)
总结
观看Spring源码这么久,最大的感受就是:组件繁多,但管理有序。它完美的实现了单一职责的设计原则,谁的事情谁去干,这点体现得非常好。只有遵循了这个原则,才能做到无状态和组件化,达到随意装配的效果~
另外,还有一种设计思想就是预加载。什么组件在什么时候加载,什么时候可以预加载进去,都是设计的精妙之处
理解ClassPathBeanDefinitionScanner的工作原理,可以帮助理解Spring IOC 容器的初始化过程。
同时对理解MyBatis 的 Mapper 扫描 也是有很大的帮助。
因为 MyBatis 的MapperScannerConfigurer的底层实现也是一个ClassPathBeanDefinitionScanner的子类。就像我们自定义扫描器那样,自定定义了 过滤器的过滤规则。
这两名大将还提供了比如扫描包的核心方法,这个在讲解具体IOC容器流程的时候再具体分析~~~