• 解决SpringBoot无法读取js|css静态资源的新方法


    【本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究。若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!】

    前言

    作为依赖使用的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+"
    ");
            }
            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("
    ");
                }
            } 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();
        }
    }
    

    自定义映射

    【本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究。若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!】

    接下来就是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()));
    }
    

    版权声明

    【本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究。若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!】

  • 相关阅读:
    Python学习————并发编程
    Python学习————作业
    Python学习————网络编程
    Python学习————异常处理
    Python学习————反射
    Python学习————绑定方法
    Python学习————继承
    1765 谷歌的恐龙
    2504 是子序列的个数
    51Nod2386 分则能成
  • 原文地址:https://www.cnblogs.com/onblog/p/13035752.html
Copyright © 2020-2023  润新知