记一次上环境获取资源失败的案例

news2024/9/27 21:21:05

代码结构以及资源位置

在这里插入图片描述

测试代码

@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 requested
        if (delegateFirst) {
            stream = parent.getResourceAsStream(name);
            if (stream != null) {
                return stream;
            }
        }

        // (2) Search local repositories
        String 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下搜索

    @Override
    public 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包本地测试一下, 防止出现不兼容问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/339862.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《计算机组成与设计》03. 计算机的算术运算

文章目录整数运算加法与减法乘法普通十进制乘法硬件中实现步骤例子乘法器的设计除法普通十进制除法硬件中实现步骤例子除法器的设计浮点数运算科学计数法、规格化数浮点表示单精度浮点数双精度浮点数移码表示法IEEE 754指数偏移值(exponent bias)规格化的…

计算机网络4:计算机网络体系结构

目录计算机网络体系结构1.网络模型2.每一层的代表含义2.1 OSI7层模型2.2 五层协议2.3 TCP/IP 四层协议3.数据在各层之间的传输过程4.为什么要进行分层计算机网络体系结构 1.网络模型 2.每一层的代表含义 2.1 OSI7层模型 (1)物理层:比特流–…

STC15中断系统介绍

STC15中断系统介绍✨本篇参考来源于STC官方stc15系列手册:538页- 589页。(文末提供该摘取部分的文档资料) 🎉在官方提供的手册资料中,一个系列一份手册,手册内容涵盖了数据手册和参考手册以及例程案例。对于学习着来说…

彻底搞懂分布式系统服务注册与发现原理

目录 引入服务注册与发现组件的原因 单体架构 应用与数据分离

火遍全球的ChatGPT技术简介与主干网络代码

如果说当下最火的AI技术和话题是什么,恐怕很难绕开ChatGPT。各大厂商都在表示未来要跟进ChatGPT技术,开发在自然语言处理智能系统,可见其影响力。本篇博客追个热度,来简单的介绍下ChatGPT到底是一项什么技术,究竟如何完…

深入理解innodb存储格式,双写机制,buffer pool底层结构和淘汰策略

MySql系列整体栏目 内容链接地址【一】深入理解mysql索引本质https://blog.csdn.net/zhenghuishengq/article/details/121027025【二】深入理解mysql索引优化以及explain关键字https://blog.csdn.net/zhenghuishengq/article/details/124552080【三】深入理解mysql的索引分类&a…

【网络编程】Java中的Socket

文章目录前言socket是什么?Java中的SocketJava实现网络上传文件前言 所谓Socket(套接字),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用…

kafka入门篇

文章目录前言介绍概念与说明安装启动配置命令操作创建topic查看topic列表发送消息(启动一个生产者)消费消息(启动一个消费者)查询topic信息删除topic集群关机使用报错java连接示例前言 作为入门篇,主要是了解Kafka的概…

在windows下载安装netcat(nc)命令

参考文章 一、netcat(nc)下载 网盘下载 netcat(nc)下载地址:netcat 1.11 for Win32/Win64 二、配置环境变量 在Path里添加netcat的存放路径 参数 说明 -C 类似-L选项,一直不断连接[1.13版本新加的功能] -d 后台执行 -e prog 程序重定向&am…

能取代90%人工作的ChatGPT到底牛在哪?

📣📣📣📣📣📣📣 🎍大家好,我是慕枫 🎍前阿里巴巴高级工程师,InfoQ签约作者、阿里云专家博主,一直致力于用大白话讲解技术知识 &#x…

Web 框架 Flask 快速入门(二)表单

课程地址:Python Web 框架 Flask 快速入门 文章目录🌴 表单1、表单介绍2、表单的简单实现1. 代码2. 代码的执行逻辑3、使用wtf扩展实现4、bug记录:表单验证总是失败🌴 表单 1、表单介绍 当我们在网页上填写账号密码进行登录的时…

Spring 面试题(一):Spring 如何处理全局异常?

❤️ 博客首页:水滴技术 🚀 支持水滴:点赞👍 收藏⭐ 留言💬 🌸 订阅专栏:Spring 教程:从入门到精通 文章目录1、如何处理全局异常2、代码示例2.1、定义统一的“响应结果对象”2.2、…

Leetcode 回溯详解

回溯法 回溯法有“通用解题法”之称,用它可以系统地搜索问题的所有解。回溯法是一个既带有系统性又带有跳跃性的搜索算法。 在包含问题的所有解的解空间树中,按照深度优先搜索(DFS))的策略,从根结点出发深度探索解空间树。当探索…

MWORKS--同元软控MWORKS介绍、安装与使用

MWORKS--同元软控MWORKS介绍、安装与使用1 同元软控介绍1.1 同元软控简介1.2 同元软控发展历史2 MWORKS介绍2.1 MWORKS简介2.2 MWORKS产品描述3 装备数字化3.1 发展3.2 内涵3.3 系统模型发展成为产品的一部分3.4 MWORKS系统模型数据管理3.4 MWORKS为装备数字化提供的套件参考1 …

springcloud集成seata(AT)分布式事务

目录 一、 下载seata server和seata源码 二、配置启动seata 2.1 在nacos控制台,新建一个seata的名称空间,用于存放seata的专用配置 2.2 创建seata server的mysql库 2.3 在nacos上配置seata相关配置 (seata名称空间) 2.4 启动…

家政服务小程序实战教程08-宫格导航

小程序一般会在首页显示商品的分类,这类需求我们在微搭中是使用宫格导航组件来实现。 01 组件说明 宫格导航组件可以在导航配置里设置菜单,可以手动添加,也可以变量绑定 因为我们一般的分类是动态变化的,品类会不断的调整&#…

阿里代码规范插件中,Apache Beanutils为什么被禁止使用?

在实际的项目开发中,对象间赋值普遍存在,随着双十一、秒杀等电商过程愈加复杂,数据量也在不断攀升,效率问题,浮出水面。 问:如果是你来写对象间赋值的代码,你会怎么做? 答&#xf…

[Java 进阶面试题] CAS 和 Synchronized 优化过程

最有用的东西,是你手里的钱,有钱就有底气,还不快去挣钱~ 文章目录CAS 和 Synchronized 优化过程1. CAS1.1 CAS的原理1.2 CAS实现自增自减的原子性1.3 CAS实现自旋锁1.4 CAS针对ABA问题的优化2. synchronized2.1 synchronized加锁阶段分析2.2 synchronized优化CAS 和 Synchroniz…

nodejs+vue大学生在线选课系统vscode - Visual Studio Code

3、数据库进行设计,建立约束和联系。 4、创建程序框架,代码分成三层结构:接口层、业务层、表示层,设计窗口和主窗口,主窗口菜单项依照系统模块图设计。 5、设计数据访问的接口,供各模块调用。完成登录功能…

【JavaWeb项目】简单搭建一个前端的博客系统

博客系统项目 本项目主要分成四个页面: 博客列表页博客详情页登录页面博客编辑页 该系统公共的CSS样式 common.css /* 放置一些各个页面都会用到的公共样式 */* {margin: 0;padding: 0;box-sizing: 0; }/* 给整个页面加上背景 */ html, body{height: 100%; }body {backgrou…