當前位置:
首頁 > 知識 > 解決SpringBoot無法讀取js css靜態資源的新方法

解決SpringBoot無法讀取js css靜態資源的新方法

前言

作為依賴使用的SpringBoot工程很容易出現自身靜態資源被主工程忽略的情況。但是作為依賴而存在的Controller方法卻不會失效,我們知道,Spring MVC對於靜態資源的處理也不外乎是路徑匹配,讀取資源封裝到Response中響應給瀏覽器,所以,解決的途徑就是自己寫一個讀取Classpath下靜態文件並響應給客戶端的方法。

對於ClassPath下文件的讀取,最容易出現的就是IDE運行ok,打成jar包就無法訪問了,該問題的原因還是在於getResources()不如getResourceAsStream()方法靠譜。

讀取classpath文件

本就是SpringBoot的問題場景,何不用Spring現成的ClassPathResource類呢?

ReadClasspathFile.java

Copypublic 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,最終代碼如下:

Copypublic class ReadClasspathFile {

private static WeakHashMapmap = 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等文本文件,這樣看起來似乎並沒有什麼錯誤,但對於一些二進位文件,就會導致瀏覽器解碼出錯。為了萬無一失,服務端應該完全做到向客戶端返回原生二進位流,也就是位元組數組。具體的解碼應由瀏覽器進行判斷並實行。

Copypublic class ReadClasspathFile { private static WeakHashMapmap = 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取個巧,代碼如下:

Copy @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註解的這種方式不支持多級路徑,也就是不支持包含「/」,為了支持匹配多級目錄,我們只能放棄這種方案,使用另一種方案。

Copy @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));

}

將讀取文件的靜態方法更換為我們最新的返回位元組流的方法,最終代碼為:

Copy @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()));

}

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 千鋒JAVA開發學院 的精彩文章:

擴展Ribbon支持Nacos權重的三種方式
Presto安裝完成之後需要做的

TAG:千鋒JAVA開發學院 |