前言

作为依赖使用的SpringBoot工程很容易出现自身静态资源被主工程忽略的情况。但是作为依赖而存在的Controller方法却不会失效,我们知道,Spring MVC对于静态资源的处理也不外乎是路径匹配,读取资源封装到Response中响应给浏览器,所以,解决的途径就是自己写一个读取Classpath下静态文件并响应给客户端的方法。

对于ClassPath下文件的读取,最容易出现的就是IDE运行ok,打成jar包就无法访问了,该问题的原因还是在于getResources()不如getResourceAsStream()方法靠谱。

读取classpath文件

本就是SpringBoot的问题场景,何不用Spring现成的ClassPathResource类呢?

ReadClasspathFile.java

public class ReadClasspathFile {
    public static String read(String classPath) throws IOException {
        ClassPathResource resource = new ClassPathResource(classPath);
        BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream(),"UTF-8"));
        StringBuilder builder = new StringBuilder();
        String line;
        while ((line = reader.readLine())!=null){
            builder.append(line+"\n");
        }
        return builder.toString();
    }
}

上面的代码并不是特别规范,存在多处漏洞。比如没有关闭IO流,没有判断文件是否存在,没有考虑到使用缓存进行优化。

这里为什么考虑缓存呢?如果不加缓存,那么每次请求都涉及IO操作,开销也比较大。关于缓存的设计,这里使用WeakHashMap,最终代码如下:

public class ReadClasspathFile {

    private static WeakHashMap<String, String> map = new WeakHashMap<>();

    public static String read(String classPath) {
        //考虑到数据的一致性,这里没有使用map的containsKey()
        String s = map.get(classPath);
        if (s != null) {
            return s;
        }
        //判空
        ClassPathResource resource = new ClassPathResource(classPath);
        if (!resource.exists()) {
            return null;
        }
        //读取
        StringBuilder builder = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), "UTF-8"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                builder.append(line).append("\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //DCL双检查锁
        if (!map.containsKey(classPath)) {
            synchronized (ReadClasspathFile.class) {
                if (!map.containsKey(classPath)) {
                    map.put(classPath, builder.toString());
                }
            }
        }
        return builder.toString();
    }
}

但这样就完美了吗?其实不然。对于html/css等文本文件,这样看起来似乎并没有什么错误,但对于一些二进制文件,就会导致浏览器解码出错。为了万无一失,服务端应该完全做到向客户端返回原生二进制流,也就是字节数组。具体的解码应由浏览器进行判断并实行。

public class ReadClasspathFile {

    private static WeakHashMap<String, byte[]> map = new WeakHashMap<>();

    public static byte[] read(String classPath) {
        //考虑到数据的一致性,这里没有使用map的containsKey()
        byte[] s = map.get(classPath);
        if (s != null) {
            return s;
        }
        //判空
        ClassPathResource resource = new ClassPathResource(classPath);
        if (!resource.exists()) {
            return null;
        }
        //读取
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        try (BufferedInputStream bufferedInputStream = new BufferedInputStream(resource.getInputStream());
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(stream)) {
            byte[] bytes = new byte[1024];
            int n;
            while ((n = bufferedInputStream.read(bytes))!=-1){
                bufferedOutputStream.write(bytes,0,n);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //DCL双检查锁
        if (!map.containsKey(classPath)) {
            synchronized (ReadClasspathFile.class) {
                if (!map.containsKey(classPath)) {
                    map.put(classPath, stream.toByteArray());
                }
            }
        }
        return stream.toByteArray();
    }
}

自定义映射

接下来就是Controller层进行映射匹配响应了,这里利用Spring MVC取个巧,代码如下:

    @ResponseBody
    @RequestMapping(value = "view/{path}.html",produces = {"text/html; charset=UTF-8"})
    public String view_html(@PathVariable String path) throws IOException {
        return ReadClasspathFile.read("view/"+path+".html");
    }

    @ResponseBody
    @RequestMapping(value = "view/{path}.js",produces = {"application/x-javascript; charset=UTF-8"})
    public String view_js(@PathVariable String path) throws IOException {
        return ReadClasspathFile.read("view/"+path+".js");
    }

    @ResponseBody
    @RequestMapping(value = "view/{path}.css",produces = {"text/css; charset=UTF-8"})
    public String view_html(@PathVariable String path) throws IOException {
        return ReadClasspathFile.read("view/"+path+".css");
    }

通过后戳(html、js)进行判断,以应对不同的Content-Type类型,静态资源的位置也显而易见,位于resources/view下。

但是,使用@PathVariable注解的这种方式不支持多级路径,也就是不支持包含“/”,为了支持匹配多级目录,我们只能放弃这种方案,使用另一种方案。

    @ResponseBody
    @RequestMapping(value = "/view/**",method = RequestMethod.GET)
    public void view_js(HttpServletResponse response, HttpServletRequest request) throws IOException {
        String uri = request.getRequestURI().trim();
        if (uri.endsWith(".js")){
            response.setContentType("application/javascript");
        }else if (uri.endsWith(".css")){
            response.setContentType("text/css");
        }else if (uri.endsWith(".ttf")||uri.endsWith(".woff")){
            response.setContentType("application/octet-stream");
        }else {
            String contentType = new MimetypesFileTypeMap().getContentType(uri);
            response.setContentType(contentType);
        }
        response.getWriter().print(ReadClasspathFile.read(uri));
    }

将读取文件的静态方法更换为我们最新的返回字节流的方法,最终代码为:

    @RequestMapping(value = "/tree/**",method = RequestMethod.GET)
    public void view_js(HttpServletResponse response, HttpServletRequest request) throws IOException {
        String uri = request.getRequestURI().trim();
        if (uri.endsWith(".js")){
            response.setContentType("application/javascript");
        }else if (uri.endsWith(".css")){
            response.setContentType("text/css");
        }else if (uri.endsWith(".woff")){
            response.setContentType("application/x-font-woff");
        }else if (uri.endsWith(".ttf")){
            response.setContentType("application/x-font-truetype");
        }else if (uri.endsWith(".html")){
            response.setContentType("text/html");
        }
        byte[] s = ReadClasspathFile.read(uri);
        response.getOutputStream().write(Optional.ofNullable(s).orElse("404".getBytes()));
    }

解决SpringBoot无法读取js/css静态资源的新方法的更多相关文章

  1. 使用Maven + Jetty时,如何不锁定js css 静态资源

    Jetty会使用内存映射文件来缓存静态文件,包括js,css文件. 在Windows下,使用内存映射文件会导致文件被锁定,所以当Jetty启动的时候无法在编辑器对js或者css文件进行编辑. 解决办法 ...

  2. springboot 项目中css js 等静态资源无法访问的问题

    目录 问题场景 问题分析 问题解决 问题场景 今天在开发一个springboot 项目的时候突然发现 css js 等静态资源竟然都报404找不到,折腾了好久终于把问题都解决了,决定写篇博客,纪录总结 ...

  3. 十二、springboot之web开发之静态资源处理

    springboot静态资源处理 Spring Boot 默认为我们提供了静态资源处理,使用 WebMvcAutoConfiguration 中的配置各种属性. 建议大家使用Spring Boot的默 ...

  4. SpringBoot第四集:静态资源与首页定(2020最新最易懂)

    SpringBoot第四集:静态资源与首页定(2020最新最易懂) 问题 SpringBoot构建的项目结构如下:没有webapp目录,没有WEB-INF等目录,那么如果开发web项目,项目资源放在那 ...

  5. 使用Node.js搭建静态资源服务器

    对于Node.js新手,搭建一个静态资源服务器是个不错的锻炼,从最简单的返回文件或错误开始,渐进增强,还可以逐步加深对http的理解.那就开始吧,让我们的双手沾满网络请求! Note: 当然在项目中如 ...

  6. [Asp.net Mvc]为js,css静态文件添加版本号

    方式一: 思路 string version = ViewBag.Version; @Scripts.RenderFormat("<script type=\"text/ja ...

  7. springboot 集成swagger2.x 后静态资源报404

    package com.bgs360.configuration; import org.springframework.context.EnvironmentAware; import org.sp ...

  8. springboot拦截器拦了静态资源css,js,png,jpeg,svg等等静态资源

    1.在SpringBoot中自己写的拦截器,居然把静态资源也拦截了,导致了页面加载失败. package com.bie.config; import com.bie.component.MyLoca ...

  9. 解决Tomcat无法加载css和js等静态资源文件

    解决思路有两个 一是,你使用了Apache服务器,html不交给Tomcat处理,所以你找不到Html等静态资源,所以你先停掉阿帕奇,然后只用Tomcat猫试试. 二是,像我一样,使用了Jetty开发 ...

随机推荐

  1. JavaScript BOM Cookie 的用法

    JavaScript Cookie Cookie是计算机上存储在小文本文件中的数据.当Web服务器将网页发送到浏览器时,连接将关闭,服务器将忘记用户的所有内容.发明Cookie是为了解决“如何记住用户 ...

  2. SAP MM MIGO过账报错 - 用本币计算的余额 - 之对策

    SAP MM MIGO过账报错 - 用本币计算的余额 - 之对策 使用MIGO事务代码对采购订单4500000191,执行收货,系统报错: 详细错误信息如下: 用本币计算的余额 消息号 F5703 诊 ...

  3. SVN Log命令常用组合【转】

    转自:https://blog.csdn.net/xuanwenchao/article/details/8875103 版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请 ...

  4. Python语言基础01-初识Python

    本文收录在Python从入门到精通系列文章系列 1. Python简介 1.1 Python的历史 Python的创始人为吉多·范罗苏姆(荷兰语:Guido van Rossum) 1989年的圣诞节 ...

  5. Jenkins参数化构建(七)

    一.配置参数化构建过程 主要用来区分分支,使用传参的方式,将分支名称传入脚本中进行拉取代码. 1.1 最常用的是:字符参数.文本参数.  1.2 添加字符参数和文本参数,并配置变量名称  1.3 配置 ...

  6. pytest怎么标记用例?

    pytest还有一个很强大的功能,那就是标记用例这个功能,这个功能可真的是很实用哒 首先,我们要实现标记功能,得分为3步走: 1.注册标记 2.标记用例 3.运行已经标记的用例. 那么第一步我们怎么实 ...

  7. 7. Transformer-XL原理介绍

    1. 语言模型 2. Attention Is All You Need(Transformer)算法原理解析 3. ELMo算法原理解析 4. OpenAI GPT算法原理解析 5. BERT算法原 ...

  8. 201871010105-曹玉中《面向对象程序设计(java)》第八周学习总结

    201871010105-曹玉中<面向对象程序设计(java)>第八周学习总结 项目 内容 <面向对象程序设计(java)> https://www.cnblogs.com/n ...

  9. HTML与CSS学习笔记(3)

    1.float浮动 脱离文档流:沿着父容器靠左或者靠右进行排列 文档流 文档流是文档可显示对象在排列时所占用的位置 float特性 left.right.none float注意点: 只会影响后面的元 ...

  10. day30_8.9 操作系统与并发编程

    一.操作系统相关 1.手工操作 1946年第一台计算机诞生--20世纪50年代中期,计算机工作还在采用手工操作方式.此时还没有操作系统的概念. 这时候的计算机是由人为将穿孔的纸带装入输入机,控制台获取 ...