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

3 篇文章 0 订阅

代码结构以及资源位置

在这里插入图片描述

测试代码

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
WebSocket是一种在Web浏览器和服务器之间进行全双工通信的技术,它可以通过一个长久的连接,在浏览器和服务器之间进行实时的双向数据传输。在视频资源获取案例中,我们可以使用WebSocket来获取视频资源。 首先,我们需要在服务器端部署一个WebSocket服务器,该服务器可以接收客户端(浏览器)的连接请求并建立连接。在服务器端,我们需要编写逻辑来处理视频资源获取请求。 当客户端(浏览器)发送视频资源获取请求时,服务器会根据请求的内容和参数,寻找并读取对应的视频资源文件。服务器将读取的视频资源文件分块传输给客户端(浏览器),并通过WebSocket连接将这些分块数据发送给浏览器。 在客户端(浏览器)上,我们需要通过JavaScript代码来建立与服务器的WebSocket连接,并监听连接的状态和接收到的数据。当连接建立成功后,客户端可以向服务器发送视频资源获取请求,并接收服务器传输的视频资源分块数据。 客户端接收到视频资源分块数据后,可以将这些数据进行缓存,并根据需要进行解码和显示。客户端可以通过WebSocket连接持续接收服务器传输的视频资源分块数据,直到视频资源获取完成。 通过WebSocket获取视频资源案例使用了WebSocket的实时传输特性,使得视频资源可以在获取的同时进行解码和显示,提供了更好的用户体验。同时,由于WebSocket可以提供双向通信的能力,示例中的视频资源获取可以实现从服务器向客户端的主动推送,使得客户端可以获取到最新的视频资源数据。 总的来说,WebSocket可以通过建立长久的连接,在浏览器和服务器之间实现实时的视频资源获取和传输。它为视频资源的实时获取和展示提供了便捷而高效的方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岁月玲珑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值