文章目录

  • 1. 前言
  • 2. 源码分析
    • 2.1 主要入口
    • 2.2 scanCandidateComponents
    • 2.3 doRetrieveMatchingFiles
    • 2.4 问题总结
  • 3. 扫描Demo

1. 前言

周所周知,Spring 启动最重要的第一步,就是扫描需要由Spring管理的类信息,例如@Component、@Controller、@Service等等。

通过这篇文章,你将会了解类的扫描实现,并且,我们也会写一个Demo来模拟扫描

PS. 我的学习方法是针对性地查看源码,带着目的,挑重点代码学习。而不是一行一行看下去,这样很容易把自己弄晕了。

2. 源码分析

2.1 主要入口

扫描类的主入口是 ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry,其调用堆栈如下(这里了解一下就行,不用记,作为调试笔记):

1. main 方法
2. AnnotationConfigApplicationContext 构造函数
3. AbstractApplicationContext.refresh
4. AbstractApplicationContext.invokeBeanFactoryPostProcessors
5. PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
6. PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors

第6步 invokeBeanDefinitionRegistryPostProcessors 方法如下,内部就是调用实现 BeanDefinitionRegistryPostProcessor 的对象方法,而 ConfigurationClassPostProcessor 就是Spring框架自带的实现类。

 private static void invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {postProcessor.postProcessBeanDefinitionRegistry(registry);}}

2.2 scanCandidateComponents

核心方法: ClassPathScanningCandidateComponentProvider.scanCandidateComponents()。

基本内容就是这2点:

  • 扫描类并返回Resource[]数组
  • 遍历Resource[]数组并转换为 ScannedGenericBeanDefinition 数组
 private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {//获取扫描的包路径,我这里的测试用例。packageSearchPath = classpath*:com/train/**/*.classString packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;//重点!!!这里执行扫描,并返回Resource[]资源数组。等等会介绍里面的代码Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();//遍历扫描的结果for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}if (resource.isReadable()) {try {MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);if (isCandidateComponent(metadataReader)) {//将Resource对象转换为BeanDefinition,记录了SpringBean的描述信息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);}else {if (debugEnabled) {logger.debug("Ignored because not a concrete top-level class: " + resource);}}}else {if (traceEnabled) {logger.trace("Ignored because not matching any filter: " + resource);}}}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);}}else {if (traceEnabled) {logger.trace("Ignored because not readable: " + resource);}}}}catch (IOException ex) {throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);}return candidates;}

上面有个变量 packageSearchPath ,值是 “classpath*:com/train/**/*.class”。因此大胆猜测一下,难道扫描类的原理就是扫描编译后的 class 文件?

2.3 doRetrieveMatchingFiles

重点来了,完整的方法名是 PathMatchingResourcePatternResolver.doRetrieveMatchingFiles 。

根据测试用例,这里的方法参数如下:

  • fullPattern:F:/Source/spring-framework/learning/out/production/classes/com/train/**/*.class
  • dir:F:\Source\spring-framework\learning\out\production\classes\com\train

印证了我们上面的猜想,果然是读取编译后的 class 文件

 protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {if (logger.isTraceEnabled()) {logger.trace("Searching directory [" + dir.getAbsolutePath() +"] for files matching pattern [" + fullPattern + "]");}//递归算法,层层往下遍历,读取class文件并返回 Set<File> 集合for (File content : listDirectory(dir)) {String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {if (!content.canRead()) {if (logger.isDebugEnabled()) {logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +"] because the application is not allowed to read the directory");}}else {doRetrieveMatchingFiles(fullPattern, content, result);}}if (getPathMatcher().match(fullPattern, currPath)) {result.add(content);}}}

2.4 问题总结

问题一: class 文件的加载顺序是怎样的?

查看 2.3 注释描述的 listDirectory 方法:

 protected File[] listDirectory(File dir) {File[] files = dir.listFiles();if (files == null) {if (logger.isInfoEnabled()) {logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");}return new File[0];}Arrays.sort(files, Comparator.comparing(File::getName));return files;}

重点看倒数第二行,Arrays.sort(files, Comparator.comparing(File::getName));

结论:每个package目录分别按文件名排序加载

问题二: 2.2 我们有讲过,扫描的结果应该是返回Resource数组,这里是File集合是如何转换成Resource数组的?

答案很简单,调试进入PathMatchingResourcePatternResolver.doFindMatchingFileSystemResources 源码

 protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {if (logger.isTraceEnabled()) {logger.trace("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");}//扫描获取File集合Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);//声明Resource集合Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());for (File file : matchingFiles) {result.add(new FileSystemResource(file));}return result;}

重点查看上面的 for 循环,结论:

FileSystemResource 构造函数可以接收 File 类型对象,其内部依赖于 File

FileSystemResource 与 Resource 关系如下

问题三: Resource 又是如何转换为 ScannedGenericBeanDefinition?

回到2.2调试的源码,关键是这面这句代码:

MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

MetadataReader 是 spring 封装的元数据读取器,通过 MetadataReaderFactory.getMetadataReader(resource) 即可将Resource对象转换为元数据读取器。最后,我们就能直接取到需要的元数据了。

metadataReader 字段信息如下,其中很重要的一个就是className。只要有了类名,就能反射得到Java 的 Class,因此也就能进一步得到更多的信息。

3. 扫描Demo

假设,现在我们需要在Spring项目扫描指定package包含的所有类,该如何操作呢?

下面我直接贴出代码,模仿Spring底层代码的,看完不要觉得实在太简单…

     PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();try {//扫描得到Resource数组Resource[] resources = resourceResolver.getResources("classpath*:com/train/inherit/**/*.class");//遍历Resources,逐个获取信息for (Resource resource : resources) {MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);String className = metadataReader.getAnnotationMetadata().getClassName();Class<?> superClazz = Class.forName(className);//其他操作......}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}

Spring扫描类过程解析和案例相关推荐

  1. Spring AOP实现原理解析

    文章目录 1. 前言 2. 代理示例 3. 问题分析 4. 结尾 1. 前言 AOP,英文全称是Aspect Oriented Programming,也叫作面向切面编程.预先定义一个或多个切入点,当 ...

  2. Spring component-scan类扫描加载过程

    2019独角兽企业重金招聘Python工程师标准>>> https://github.com/javahongxi 有朋友最近问到了spring加载类的过程,尤其是基于annotat ...

  3. Spring 是解析配置类过程详解

    Spring执行流程图如下: Spring执行流程图 这个流程图会随着我们的学习不断的变得越来越详细,也会越来越复杂,希望在这个过程中我们都能朝着精通Spring的目标不断前进!  在上篇文章我们学 ...

  4. Spring Boot自动装配过程解析及简单Demo演示

    文章目录 1.约定大于配置 2.自动装配原理 2.1.`@SpringBootApplication` 2.2.`@EnableAutoConfiguration` 2.3.`@Import` 2.4 ...

  5. spring 调用python_Spring http服务远程调用实现过程解析

    最近公司有个新的需求,写了一个接口,想要把服务暴露出去,但是这个服务所在的进程是非web项目,(可以理解成schedule/batch等进程项目),所以没有tomcat等容器,而且只有这一个服务,无论 ...

  6. Spring AOP源码解析-拦截器链的执行过程

    一.简介 在前面的两篇文章中,分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在得到了 bean 的代理对象,且通知也以合适的方式插在了目标方 ...

  7. 我该如何学习spring源码以及解析bean定义的注册

    如何学习spring源码 前言 本文属于spring源码解析的系列文章之一,文章主要是介绍如何学习spring的源码,希望能够最大限度的帮助到有需要的人.文章总体难度不大,但比较繁重,学习时一定要耐住 ...

  8. Spring源码深度解析(郝佳)-学习-Spring Boot体系原理

      Spring Boot是由Pivotal团队提供的全新框架,其设计目的用来简化新Spring应用初始化搭建以及开发过程,该框架使用了我写的方式进行配置,从而开发人员不再需要定义样板化的配置,通过这 ...

  9. Spring Bean实例化过程,怎么解决循环依赖

    1.Spring是什么? Spring有很庞大的家族,Spring一般指的其实就是SpringFramework! Ioc和aop 包含在SpringFramework中! SpringFramewo ...

最新文章

  1. C# 参数按照ASCII码从小到大排序(字典序)
  2. 机械秒表的使用方法_瓦楞纸箱防水性能检测方法
  3. boost::graph模块实现读写graphviz的测试程序
  4. C#面向对象的三大特性
  5. P7099-[yLOI2020]灼【数学期望,结论】
  6. mysql 下载到其他盘中_MYSQL 如果把数据文件保存到其他磁盘里
  7. 实用工具网站(经纬度、短链接、图片格式转换、长微博、图片压缩、uuid、繁体字、md5破解)
  8. C# ASP.NET MVC:使用Cookie记住账号密码
  9. 多线程读取视频及深度学习推理
  10. 拼多多算法工程师笔试题之分配巧克力
  11. PHP学习九--会话控制session和cookie
  12. c语言从入门到放弃(初识)
  13. MATLAB FFT与fftshift
  14. PHP之GD图像处理
  15. 6. LaTeX 参考文献的排版与引用
  16. 神经网络能用来干什么_秒懂神经网络---震惊!!!神经网络原来可以这么简单!...
  17. 应用StarRocks实现存储引擎的收敛,保障高查询并发及低延迟要求
  18. 移动端300ms延迟_移动端300ms延迟原因及解决方案
  19. 小程序 wxml里时间戳转日期
  20. 如何将html表格导出到excel,html 页面导出到excel表格数据类型-如何将html里面的table导出成excel...

热门文章

  1. 解决MySql 数据库 提示:1045 access denied for user 'root'@'localhost' using password yes
  2. SAP_常用业务数据表
  3. QUETY使用标准数据源的报表传输到新系统没有数据取出
  4. 【ABAP】Field Symbol使用总结
  5. java的(PO,VO,TO,BO,DAO,POJO)解释
  6. 关于《资产负债表》中未分配利润项目的填列
  7. 后勤与FI集成的几个配置地方
  8. ABAP--如何将REUSE_ALV_GRID_DISPLAY的题头自适应居中显示
  9. 看似火热的民宿,实则凉透半边天
  10. 基于链表的快速排序C语言,双向链表的快速排序算法,编译通过,程序会崩溃,求助一下各位...