记一次上环境获取资源失败的案例
代码结构以及资源位置
测试代码
@RestController
@RequestMapping("/json")
public class JsonController {@GetMapping("/user/1")public String queryUserInfo() throws Exception {// 如果使用全路径, 必须使用/开头String path = JsonController.class.getPackage().getName().replace(".", File.separator);// 注意第一个字符不能是File.separator, 必须是/开头,String fileName = "/" + path + File.separator + "UserInfo.json";URL resource = JsonController.class.getResource(fileName);// 使用URL转换成uri可以兼容前面有/E:/idea-workspace/learning的windows盘符路径byte[] bytes = Files.readAllBytes(Paths.get(resource.toURI()));return new String(bytes, StandardCharsets.UTF_8);}@GetMapping("/user/2")public String queryUserInfo2() throws Exception {String path = JsonController.class.getResource("UserInfo.json").getPath();byte[] bytes = Files.readAllBytes(Paths.get(path.substring(1)));return new String(bytes, StandardCharsets.UTF_8);}@GetMapping("/user/3")public String queryUserInfo3() throws Exception {String path = JsonController.class.getPackage().getName().replace(".", File.separator);// classloader不能使用/开始获取文件, 因为文件不能包含/字符URL resource = JsonController.class.getClassLoader().getResource(path + File.separator + "UserInfo.json");// 很显然toUri的兼容性好些byte[] bytes = Files.readAllBytes(Paths.get(resource.toURI()));return new String(bytes, StandardCharsets.UTF_8);}@GetMapping("/user/4")public String queryUserInfo4() throws Exception {// 使用classloader获取资源和使用class的使用/开始获取资源本质逻辑一模一样URL resource = JsonController.class.getClassLoader().getResource("static/UserInfo.json");// 很显然toUri的兼容性好些byte[] bytes = Files.readAllBytes(Paths.get(resource.toURI()));return new String(bytes, StandardCharsets.UTF_8);}
}
本地调试一切正常, 但是上环境就报500了
{"timestamp": "2023-02-11T14:14:07.500+00:00","status": 500,"error": "Internal Server Error","path": "/json/user/4"
}
也可以通过 java -jar springboot-demo-0.0.1-SNAPSHOT.jar
启动也可以复现问题
说明: 查看jar包确认资源文件已经被正确的打到jar中
说明代码存在兼容性
使用org.springframework.core.io.ClassPathResource获取
// 在jar包运行之后仍然不能找到文件@GetMapping("/user/5")public String queryUserInfo5() {try {// 如果使用全路径, 必须使用/开头String path = JsonController.class.getPackage().getName().replace(".", File.separator);// 注意第一个字符不能是File.separator, 必须是/开头,String fileName = "/" + path + File.separator + "UserInfo.json";ClassPathResource pathResource = new ClassPathResource(fileName);byte[] bytes = Files.readAllBytes(Paths.get(pathResource.getURI()));return new String(bytes, StandardCharsets.UTF_8);} catch (IOException e) {e.printStackTrace();}return "";}// 必须使用ClassPathResource并且使用其getInputStream()方法才能获取的到数据@GetMapping("/user/6")public String queryUserInfo6() {try {// 如果使用全路径, 必须使用/开头String path = JsonController.class.getPackage().getName().replace(".", File.separator);// 注意第一个字符不能是File.separator, 必须是/开头,String fileName = "/" + path + File.separator + "UserInfo.json";ClassPathResource pathResource = new ClassPathResource(fileName);byte[] buf = new byte[1024];int len = -1;StringBuilder sb = new StringBuilder();try (InputStream is = pathResource.getInputStream()) {while ((len = is.read(buf)) != -1) {sb.append(new String(buf, 0, len, StandardCharsets.UTF_8));}}return sb.toString();} catch (IOException e) {e.printStackTrace();}return "";}
queryUserInfo5方法只是用了ClassPathResource来获取URI, jar运行依然不能获取到资源
ClassPathResource pathResource = new ClassPathResource(fileName);
byte[] bytes = Files.readAllBytes(Paths.get(pathResource.getURI()));
queryUserInfo6使用了ClassPathResource的getInputStream()方法, jar运行依然可以好获取到
ClassPathResource pathResource = new ClassPathResource(fileName);
...
try (InputStream is = pathResource.getInputStream()) {
...}
}
使用spring的ClassPathResource确实解决了问题, 但是为什么会出现这样的问题?
tomcat项目和springboot项目差异
传统的tomcat项目都是提供war包, 然后war就会解压到webapps或者配置的应用目录下面, 所以我们的资源是在普通的目录里面
但是随着springboot 的出现, 问题开始有点不同了, springboot通过内嵌tomcat容器, 是的我们可以直接运行jar包, 所以此时我们要获取的资源文件是在jar包里面, 显然我们要获取资源就必须解析jar
所以我们的问题就此出现了, 如果仅仅使用传统的resouce api, 是不能获取到jar包中的文件, 就是说不会解析jar。
查看ClassPathResource源码找不同
进入
使用的类加载器是: TomcatEmbeddedWebappClassLoader
调用getResourceAsStream方法在org.apache.catalina.loader.WebappClassLoaderBase中
WebappClassLoaderBase位于tocmat-ebed-core-xxx包中
getResourceAsStream的实现
public InputStream getResourceAsStream(String name) {
...// (1) Delegate to parent if requestedif (delegateFirst) {stream = parent.getResourceAsStream(name);if (stream != null) {return stream;}}// (2) Search local repositoriesString path = nameToPath(name);WebResource resource = resources.getClassLoaderResource(path);if (resource.exists()) {stream = resource.getInputStream();trackLastModified(path, resource);}try {if (hasExternalRepositories && stream == null) {URL url = super.findResource(name);if (url != null) {stream = url.openStream();}}} catch (IOException e) {// Ignore}if (stream != null) {return stream;}
...}
主要就是两个一个是委派/一个是搜索本地存储(还有个是无条件委派本质和委派一样)
所以不太点就是WebResourceRoot回去搜索本地存储的文件, 然后返回resource
然后这个代码就很明显了, 回去classes下搜索
@Overridepublic WebResource getClassLoaderResource(String path) {return getResource("/WEB-INF/classes" + path, true, true);}
直接使用getResourceAsStream方法也是可以获取
private static String getString(InputStream inputStream) throws IOException {byte[] buf = new byte[1024];int len = -1;StringBuilder sb = new StringBuilder();try (InputStream is = inputStream) {while ((len = is.read(buf)) != -1) {sb.append(new String(buf, 0, len, StandardCharsets.UTF_8));}}return sb.toString();}
@GetMapping("/user/0")
public String queryUserInfo0() throws Exception {// 使用classloader获取资源和使用class的使用/开始获取资源本质逻辑一模一样InputStream inputStream = JsonController.class.getClassLoader().getResourceAsStream("static/UserInfo.json");return getString(inputStream);
}
测试发现只要使用了InputStream就都可以获取到了
@GetMapping("/user/000")
public String queryUserInfo000() throws Exception {URL resource = JsonController.class.getResource("UserInfo.json");return getString(resource.openStream());
}@GetMapping("/user/00")
public String queryUserInfo00() throws Exception {URL resource = JsonController.class.getResource("UserInfo.json");BufferedReader reader =new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8));String line;StringBuilder sb = new StringBuilder();while ((line = reader.readLine()) != null) {sb.append(line);}return sb.toString();
}@GetMapping("/user/0")
public String queryUserInfo0() throws Exception {// 使用classloader获取资源和使用class的使用/开始获取资源本质逻辑一模一样InputStream inputStream = JsonController.class.getClassLoader().getResourceAsStream("static/UserInfo.json");return getString(inputStream);
}
resource.openStream()
搞半天问题其实出在这行代码上
byte[] bytes = Files.readAllBytes(Paths.get(path.substring(1)));
不具有jar包兼容性, 坑!!!
如果我们在编写获取文件的代码时候, 最好使用jar包本地测试一下, 防止出现不兼容问题
相关文章:

记一次上环境获取资源失败的案例
代码结构以及资源位置 测试代码 RestController RequestMapping("/json") public class JsonController {GetMapping("/user/1")public String queryUserInfo() throws Exception {// 如果使用全路径, 必须使用/开头String path JsonController.class.ge…...

实战超详细MySQL8离线安装
在RedHat中,RPM Bundle 方式安装MySQL8。建议一定要用 RPM Bndle 版本安装,包全。官网下载:https://dev.mysql.com/downloads/mysql/1.卸载mariadb,会与MySQL安装冲突。rpm -qa | grep mariadb 查看有无mariadb如果有࿰…...
依赖倒置原则|SOLID as a rock
文章目录 意图动机:违反依赖倒置原则解决方案:C++中依赖倒置原则的例子依赖倒置原则的优点1、可复用性2、可维护性在C++中用好DIP的标准总结本文是关于 SOLID as Rock 设计原则系列的五部分中的 最后一部分。 SOLID 设计原则侧重于开发 易于维护、可重用和可扩展的软件。 在…...

Webpack的知识要点
在前端开发中,一般情况下都使用 npm 和 webpack。 npm是一个非常流行的包管理工具,帮助开发者管理项目中使用的依赖库和工具。它可以方便地为项目安装第三方库,并在项目开发过程中进行版本控制。 webpack是一个模块打包工具ÿ…...

handler解析(2) -Handler源码解析
目录 基础了解: 相关概念解释 整体流程图: 源码解析 Looper 总结: sendMessage 总结: ThreadLocal 基础了解: Handler是一套 Android 消息传递机制,主要用于线程间通信。实际上handler其实就是主线程在起了一…...
【算法】kmp
KMP算法 名称由来 是由发明这个算法的三个科学家的名称首字母组成 作用 用于字符串的匹配问题 举例说明 字符串 aabaabaaf 模式串 aabaaf 传统匹配方法 第一步 aabaabaaf aabaaf 此时,b和f不一致,则把模式串从头和文本串的第二个字符开始比 第…...
git 常用命令之 git checkout
大家好,我是 17。 git checkout 是 git 中最重要最常用的命令之一,本文为大家详细解说一下。 恢复工作区 checkout 的用途之一是恢复工作区。 git checkout . checkout . 表示恢复工作区的所有更改,未跟踪的文件不会有变化。 恢复工作区的所有文件风…...

一些常见错误
500状态码: 代表服务器业务代码出错, 也就是执行controller里面的某个方法的过程中报错, 此时在IDEA的控制台中会显示具体的错误信息, 所以需要去看IDEA控制台的报错404状态码: 找不到资源找不到静态资源 检查请求地址是否拼写错误 检查静态资源的位置是否正确 如果以上都没有问…...

[单片机框架][调试功能] 回溯案发现场
程序莫名死机跑飞,不知道问题,那么下面教你回溯错误源 回溯案发现场一、修改HardFault_Handler1. xx.s 在启动文件,找到HardFault_Handler。并修改。2. 定义HardFault_Handler_C函数。(主要是打印信息并存储Flash)3. 根…...
MySQL主从同步-(二)搭建从机服务器
在docker中创建并启动MySQL从服务器:**端口3307docker run -d \-p 3307:3306 \-v /atguigu/mysql/slave1/conf:/etc/mysql/conf.d \-v /atguigu/mysql/slave1/data:/var/lib/mysql \-e MYSQL_ROOT_PASSWORD123456 \--name atguigu-mysql-slave1 \mysql:8.0.3创建MyS…...

Linux系列 备份与分享文档
作者简介:一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页 目录 前言 一.备份与分享文档 1.使用压缩和解压缩工具 (1&…...

SNI生效条件 - 补充nginx-host绕过实例复现中SNI绕过的先决条件
文章目录1.前置环境搭建2.测试SNI生效条件(时间)3. 证书对SNI的影响3.1 双方使用同一个证书:3.2 双方使用不同的证书与私钥4. 端口号区分测试4.1 端口号区分,证书区分:4.2 端口号区分,证书不区分:5.总结SNI运行机制6. SNI机制绕过…...

傻白探索Chiplet,Modular Routing Design for Chiplet-based Systems(十一)
阅读了Modular Routing Design for Chiplet-based Systems这篇论文,是关于多chiplet通信的,个人感觉核心贡献在于实现了 deadlock-freedom in multi-chiplet system,而不仅仅是考虑单个intra-chiplet的局部NoC可以通信,具体的一些…...

C语言静态库、动态库的封装和注意事项
1、动态库、静态库介绍 参考博客:《静态库和动态库介绍以及Makefile》; 2、代码目录结构和编译脚本 参考博客:《实际工作开发中C语言工程的目录结构分析》; 3、编写库的流程 (1)明确需求:需求是否合理、需求的使用场景、需求可能遇…...

MyBatis-Plus分页插件和MyBatisX插件
MyBatis-Plus分页插件和MyBatisX插件六、插件1、分页插件a>添加配置类b>测试八、代码生成器1、引入依赖2、快速生成十、MyBatisX插件1、新建spring boot工程a>引入依赖b>配置application.ymlc>连接MySQL数据库d>MybatisX逆向生成2、MyBatisX快速生成CRUD申明…...

年前无情被裁,面试大厂的这几个月…
2月份了,金三银四也即将来临,在这个招聘季,大厂也开始招人,但还是有很多人吐槽说投了很多简历,却迟迟没有回复… 另一面企业招人真的变得容易了吗?有企业HR吐槽,简历确实比以前多了好几倍&…...
基于Java的分片上传功能
起因:最近在工作中接到了一个大文件上传下载的需求,要求将文件上传到share盘中,下载的时候根据前端传的不同条件对单个或多个文件进行打包并设置目录下载。 一开始我想着就还是用老办法直接file.transferTo(newFile)就算是大文件,…...

KDS安装步骤
KDS kinetis design studio 软件 第一步官网(https://www.nxp.com/ 注册账号下载set成功下载软件。 随着AI,大数据这些技术的快速发展,与此有关的知识也普及开来。如何在众多网站中寻找最有价值的信息,如何在最短的时间内获得最新的技…...

JavaSE-线程池(1)- 线程池概念
JavaSE-线程池(1)- 线程池概念 前提 使用多线程可以并发处理任务,提高程序执行效率。但同时创建和销毁线程会消耗操作系统资源,虽然java 使用线程的方式有多种,但是在实际使用过程中并不建议使用 new Thread 的方式手…...

开源代码的寿命为何只有1年?
说实话,如果古希腊的西西弗斯是一个在2016年编写开源代码的开发者,那他会有宾至如归的感觉。著名的西西弗斯处罚,是神话流传下来的,他被迫推一块巨大的石头上山,当登顶之后,只能眼睁睁看着它滚下去…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...