做为依赖使用的SpringBoot工程很容易出现自身静态资源被主工程忽略的状况。可是做为依赖而存在的Controller方法却不会失效,咱们知道,Spring MVC对于静态资源的处理也不外乎是路径匹配,读取资源封装到Response中响应给浏览器,因此,解决的途径就是本身写一个读取Classpath下静态文件并响应给客户端的方法。javascript
对于ClassPath下文件的读取,最容易出现的就是IDE运行ok,打成jar包就没法访问了,该问题的缘由仍是在于getResources()不如getResourceAsStream()方法靠谱。css
本就是SpringBoot的问题场景,何不用Spring现成的ClassPathResource类呢?html
ReadClasspathFile.javajava
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取个巧,代码以下:app
@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注解的这种方式不支持多级路径,也就是不支持包含“/”,为了支持匹配多级目录,咱们只能放弃这种方案,使用另外一种方案。ui
@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())); }