前言

作为依赖使用的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. ucoreOS_lab3 实验报告

    所有的实验报告将会在 Github 同步更新,更多内容请移步至Github:https://github.com/AngelKitty/review_the_national_post-graduat ...

  2. Android实用的Toast工具类封装

    Toast这个提示框大家都晓得,显示一段时间后自动消失,不能获得焦点.但是在使用中有些问题: 1)需要弹出一个新的Toast时,上一个Toast还没有显示完2)可能重复弹出相同的信息3)Toast具体 ...

  3. HeadFirst设计模式---简单工厂

    简单工厂的理解 简单工厂不是设计模式的一种,只是代码规范而且.也就是说构造一个披萨工厂出来,按不同味道生产不同的披萨. 类图 抽象披萨 public abstract class AbstractPi ...

  4. 转:oracle笔记

    oracle笔记1 卸载oracle developer server的方法: 1-1 oracle卸载工具中卸载对应的oracleds项目:在注册表中搜索ORACLEDS HOME对应的别名,删除对 ...

  5. JavaWeb中点赞功能的实现及完整实例

    实现原理1.功能描述:一个用户对同一文章只能点赞一次,第二次就是取消赞2.建立一个点赞表great,字段有文章ID(aid),点赞用户ID(uid)3.当有用户进行点赞行为时,使用aid和uid搜索点 ...

  6. tornado 之 RequestHandler(请求)

    RequestHandler from tornado.web import ReuqestHandler 一.利用HTTP协议想服务器传递参数 提取url的特定部分 http://127.0.0.1 ...

  7. Spring Boot 调度器

    Spring Boot 可以很简单的添加一个调度任务 首先需要添加maven依赖 <dependency> <groupId>org.springframework</g ...

  8. 201777010217-金云馨《面向对象程序设计(java)》第十四周学习总结

      项目 内容   这个作业属于哪个课程   https://www.cnblogs.com/nwnu-daizh/   这个作业的要求在哪里 https://www.cnblogs.com/nwnu ...

  9. zz目标检测

    deep learning分类 目标检测-HyperNet-论文笔记 06-06 基础DL模型-Deformable Convolutional Networks-论文笔记 06-05 基础DL模型- ...

  10. 【转】用C语言实现FFT算法

    傅里叶变换 快速傅里叶变换(Fast Fourier Transform,FFT)是一种可在  时间内完成的离散傅里叶变换(Discrete Fourier transform,DFT)算法. 在算法 ...