SpringBoot之Class资源加载顺序

问题描述与猜想

在工作中遇到一个问题,代码和结构大致如下

如图所示,大致是一个目录下有A和B两个类,其中A类用@Bean注解实例化一个treeA的对象,B类使用@ConditionalOnBean注解判断spring是否有treeA对象,有的话才实例化,没有则跳过

在本地环境一直是没有问题的,但是当打成jar包放到测试环境中时,发现B类没有实例化,对此引发了我的探究。

我们知道,SpringBoot会先将带有@Compoment的类扫描出来,然后按顺序遍历,如果有条件注解则判断其是否符合条件,不符合直接跳过;符合则会将其类下使用@Import、@Bean等注解引入的Bean注册到bdm中。

上面两个类都用了@Configuration注解,肯定被扫描到了,所以可以肯定问题的根源是先加载了B类,发现没有treeA对象,所以跳过,然后再加载了A类。

在源代码中一番debug后,发现SpringBoot对于不同资源有不同的扫描实现,开发环境一般是target,是一个文件包,测试环境则是jar包。

scss 复制代码
PathMatchingResourcePatternResolver->findPathMatchingResources():
    if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
        result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
    }
    else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
        // jar实现
        result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
    }
    else {
        // target实现
        result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
    }

target实现

scss 复制代码
PathMatchingResourcePatternResolver->doFindPathMatchingFileResources():
    ......
    return doFindMatchingFileSystemResources(rootDir, subPattern);
PathMatchingResourcePatternResolver->doFindMatchingFileSystemResources():
    ......
    Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
PathMatchingResourcePatternResolver->retrieveMatchingFiles():
    ......
    doRetrieveMatchingFiles(fullPattern, rootDir, result);
PathMatchingResourcePatternResolver->doRetrieveMatchingFiles():
    for (File content : listDirectory(dir)) {
        ......
    }

重点在listDirectory(dir)这个方法中

ini 复制代码
PathMatchingResourcePatternResolver->listDirectory(File dir):
    File[] files = dir.listFiles();
    // 对于files是空的处理
    if (files == null) {
        ......
    }
    Arrays.sort(files, Comparator.comparing(File::getName));
    return files;

dir.listFiles(),列举目录中的所有文件,但不包含空目录。也就是说,如果有目录aa中有文件b.txt,则会把这个b.txt列举出来(无论有多少层级都会列出)。

Arrays.sort(files, Comparator.comparing(File::getName)),对文件名进行字典顺序排序,先数字,再大写字母,最后小写字母。

在开发环境中,A类总是排在B类的前面,所以不会出现B加载不到的问题。

jar实现

scss 复制代码
PathMatchingResourcePatternResolver->doFindPathMatchingJarResources():
    ......
    // 遍历文件
    for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
        JarEntry entry = entries.nextElement();
        ......
    }
JarFile->JarEntryIterator->nextElement():
    return next();
JarFile->JarEntryIterator->next():
    ZipEntry ze = e.nextElement();
    return new JarFileEntry(ze);

注意到这里的e是ZipEntry的子类,于是找ZipEntry的nextElement方法

scss 复制代码
ZipFile->ZipEntryIterator->nextElement():
    return next();
ZipFile->ZipEntryIterator->next():
    ......
    long jzentry = getNextEntry(jzfile, i++);
    ......

ZipEntry的底层实现都是native方法,debug到这里戛然而止了,但是得出了结论,jar遍历资源文件底层走的是ZipEntry的遍历方法。

而我们知道,ZipEntry遍历顺序是根据假如中央字典的顺序来的,与文件大小、修改时间、文件字典名都没有关系

在linux中,使用zip -l 目录名的命令可以将zip包的中央字典顺序排列出来,执行上述命令后,发现果然B.class排在了A.class的前面,这也就解释通了。

解决方法

在介绍OnBeanCondition的时候总结过,建议条件注解只判断@Component层面是否有相应的bean,在项目中发现A类和B类都是必须的,没有必须先有A类才能有B类的前提,因此将B类的条件注解去掉,即可解决问题

相关推荐
zdl6861 分钟前
springboot+全局异常处理
java·spring boot·spring
2301_771717218 分钟前
Jackson的使用方法详解
java·服务器·前端
Full Stack Developme21 分钟前
SpringBoot多线程池配置
spring boot·后端·firefox
立莹Sir1 小时前
Spring Bean生命周期设计思想与源码深度剖析:从表象到本质的全面升级
java·spring·rpc
计算机毕业论文辅导1 小时前
毕业设计避坑指南:工资信息管理系统的设计与实现(Java+SpringBoot实战)
java·spring boot·课程设计
你不是我我1 小时前
【Java 开发日记】为什么要有 time _wait 状态,服务端这个状态过多是什么原因?
java·网络·php
User_芊芊君子1 小时前
别再乱用 ArrayList 了!这 4 个隐藏坑,90% 的 Java 开发者都踩过
android·java·数据库
xcLeigh1 小时前
JAVA项目实战:用飞算 JavaAI 高效开发电商系统核心功能模块
java·ai编程·电商系统·java开发·飞算javaai炫技赛
xcLeigh1 小时前
IoTDB Java 原生 API 实战:SessionPool 从入门到精通
java·开发语言·数据库·api·iotdb·sessionpool
qq12_8115175151 小时前
Java Web 影城会员管理系统系统源码-SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0【含文档】
java·前端·mybatis