解決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()));
}
※擴展Ribbon支持Nacos權重的三種方式
※Presto安裝完成之後需要做的
TAG:千鋒JAVA開發學院 |