前言

作为依赖使用的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. jvm默认的并行垃圾回收器和G1垃圾回收器性能对比

    http://www.importnew.com/13827.html 参数如下: JAVA_OPTS="-server -Xms1024m -Xmx1024m -Xss256k -XX:M ...

  2. [git] your branch is ahead of ... 解决办法

    出现该问题表示在次之前已经有X个commite了,执行命令git reset --hard HEAD~X解决其中X表示有多少次提交,此命令的意思是回退到x个commit之前.git reset --h ...

  3. Django框架操作数据库的两种方式

    Django操作数据库的前提操作是成功连接数据库,详情见上篇:https://www.cnblogs.com/kristin/p/10791358.html Django查询数据库的方式一 from ...

  4. InnoDB Multi-Versioning

    InnoDB 是一个数据多版本的存储引擎,它会保持它修改的数据的旧版本数据以此来支持事务特性,比如并发操作和事务的回滚.这些旧版本数据存储在一个叫做rollback segment的数据结构中(回滚段 ...

  5. 什么是证书透明度(Certificate Transparency)?

    SSL基础概念 什么是加密? 加密是一种新型的电子信息保护方式,就像过去使用保险箱和密码锁保护纸上信息一样.加密是密码学的一种技术实现方式:信息被转换为难以理解的形式(即编码),以便只有使用密钥才能将 ...

  6. cisco

    配置ntp conf t ntp server 172.28.10.10 clock timezone Beijing 8 show clock 配置端口组 interface Port-channe ...

  7. HDFS 分布式文件系统

    博客出处W3c:https://www.w3cschool.cn/hadoop/xvmi1hd6.html 简介 Hadoop Distributed File System,分布式文件系统 架构 B ...

  8. Python DataFrame 按条件筛选数据

    原始数据如下. 比如我想查看id等于11396的数据. pdata1[pdata1['id']==11396] 查看时间time小于25320的数据. pdata1[pdata1['time']< ...

  9. CF306C White, Black and White Again

    CF306C White, Black and White Again 洛谷评测传送门 题目描述 Polycarpus is sure that his life fits the descripti ...

  10. 启动hadoop后jps没有namenode,并且启动报错9000

    启动hadoop报错: 解决方法: 我发现没有9000端口被占用,也不知道9000到哪去了,但是也没有NameNode,于是,直接把NameNode格式化了,再重启HDFS即可. 格式化命令: