SpringBoot 學習筆記_整合 Web 開發(一)
聲明:
本次學習參考 《SpringBoot + Vue 開發實戰》 · 王松(著) 一書。
本文的目的是記錄我學習的過程和遇到的一些問題以及解決辦法,其內容主要來源於原書。
如有侵權,請聯繫我刪除
文章目錄
SpringBoot 整合 Web 開發
返回 JSON 數據
JSON 是目前主流的前後端數據傳輸方式, Spring MVC 中使用消息轉換器
HttpMessageConverter
對 JSON 的轉換提供了很好的支持。在 SpringBoot 中,對相關配置做了進一步優化。
SpringBoot 環境搭建時,默認添加的 Web 依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
該依賴中默認加入了 jackson-databind
作爲 JSON 處理器。
-
創建 User 實體類
/** * jackson-databind JSON 處理器使用 */ public class User { private String username; private String password; @JsonIgnore private String address; @JsonFormat(pattern = "yyyy-MM-dd") private Date birthday; }
-
創建 UserController 控制器,返回 User 對象
@Controller public class UserController { @GetMapping("/user") @ResponseBody public User user(){ User user = new User(); user.setUsername("Ambrose"); user.setPassword("123456"); user.setAddress("廣東省深圳市"); user.setBirthday(new Date()); return user; } }
依稀記得,在 Book 類中,我們使用了 @RestController 註解,但在這裏卻使用了 @Controller 註解,這兩個有什麼關係呢?
其實, @RestController 是 @Controller 和 @ResponseBody 的組合註解
-
運行項目,測試返回結果
https://localhost:8080/user
除了默認的
jackson-databind
JSON 轉換器外,還有很多常見的轉換器,如Gson
、fastjson
等。
fastjson
是阿里巴巴的開源 JSON 解析框架,是目前 JSON 解析速度最快的開源框架。該框架也可以集成到 SpringBoot 中,不同的是,fastjson
集成完成後不能立馬使用,需要開發者提供相應的HttpMessageConverter
之後纔可以使用。
具體步驟如下:
-
去除默認的
jackson-databind
依賴,引入fastjson
依賴。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 去除該依賴中默認的 jackson-databind 依賴,再在後面引入 fastjson 依賴 --> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </exclusion> </exclusions> </dependency> <!-- 引入 fastjson 依賴 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency>
-
配置
fastjson
的HttpMessageConverter
。@Configuration public class MyFastJsonConfig { @Bean FastJsonHttpMessageConverter fastJsonHttpMessageConverter(){ FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); FastJsonConfig config = new FastJsonConfig(); config.setDateFormat("yyyy-MM-dd"); config.setCharset(Charset.forName("UTF-8")); config.setSerializerFeatures( SerializerFeature.WriteClassName, SerializerFeature.WriteMapNullValue, SerializerFeature.PrettyFormat, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteNullStringAsEmpty ); converter.setFastJsonConfig(config); return converter; } }
-
配置響應編碼,防止亂碼情況發生(在
application.properties
中)。spring.http.encoding.force-response=true
如上配置會觸發警告:不建議使用該 key,建議如下:
server.servlet.encoding.force-response=true
-
運行項目,測試返回結果
https://localhost:8080/user
或者也可以這樣進入:
在 SpringBoot 項目中,引入
spring-boot-starter-web
依賴,該依賴又依賴了spring-boot-autoconfig
,而在這個自動化配置中,有一個WebMvcAutoConfiguration
的類提供了對SpringMVC
的基本配置,如果某一自動化配置無法滿足開發需求,可以通過重新實現WebMvcConfigurer
接口來自定義:
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig config = new FastJsonConfig();
config.setDateFormat("yyyy-MM-dd");
config.setCharset(Charset.forName("UTF-8"));
config.setSerializerFeatures(
SerializerFeature.WriteClassName,
SerializerFeature.WriteMapNullValue,
SerializerFeature.PrettyFormat,
SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.WriteNullStringAsEmpty
);
converter.setFastJsonConfig(config);
converters.add(converter);
}
}
靜態資源訪問
SpringBoot
中對於Spring MVC
的自動化配置都在WebMvcAutoConfiguration
類中。
在WebMvcAutoConfiguration
類中有一個靜態內部類WebMvcAutoConfigurationAdapter
,實現了WebMvcConfigurer
接口,WebMvcConfigurer
接口中有一個方法addResourceHandlers
就是用來配置靜態資源過濾的。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
springBoot 就是在這裏進行了默認的靜態資源默認配置,其中 staticPathPattern
默認定義 WebMvcProperties
中,定義如下:
private String staticPathPattern = "/**";
this.resourceProperties.getStaticLocations()
獲取到的默認靜態資源位置定義在 ResourceProperties
中:
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
};
在 getResourceLocations
方法中,對這 4 個靜態資源位置做了擴充:
static String[] getResourceLocations(String[] staticLocations) {
String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
return locations;
}
其中,SERVLET_LOCATIONS
的定義如下:
private static final String[] SERVLET_LOCATIONS = { "/" };
也就是說,開發者可以將靜態資源放到這 5 個位置中的任意一個(注意:按照定義的順序, 5 個位置優先級依次降低,但是一般情況下,SpringBoot 項目不需要 webapp
目錄,所以第 5 個 “/” 可以不用考慮)
如果默認的靜態資源過濾策略不能滿足要求,也可以自定義靜態資源過濾策略
自定義靜態資源過濾策略有兩種方式:
-
配置文件中定義:
在
application.properties
中直接定義過濾規則和靜態資源位置# 過濾規則 spring.mvc.static-path-pattern=/static/** # 靜態資源位置 spring.resources.static-location=classpath:/static/
-
Java
編碼定義需要實現
WebMvcConfigurer
接口,然後實現接口的addResourceHandlers
方法@Configuration public class MyWebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHanlderRegistry registry) { registry .addResourceHandler("/static/**") .addResourceLocations("classpath:/static/") } }
文件上傳
Java 中文件上傳一共涉及兩個組件。
CommonsMultipartResolver
和StandardServletMultipartResolver
,其中CommonsMultipartResolver
使用commons-fileupload
來處理multipart
請求,而StandardServletMultipartResolver
則是基於 Servlet 3.0 來處理multipart
請求的。因此,如果使用
StandardServletMultipartResolver
,則不需要添加額外的 jar 包。Tomcat 7.0 就已經支持 Servlet 3.0 了,而 SpringBoot 2.0.4 內嵌 Tomcat 爲 Tomcat 8.5.32,所以可以直接使用
StandardServletMultipartResolver
,而在 SpringBoot 提供的文件上傳自動化配置類MultipartAutoConfiguration
中,默認也是採用的StandardServletMultipartResolver
。
從 MultipartAutoConfiguration
的源碼可以看出:如果開發者沒有提供 MultipartResolver
,那麼默認採用的 MultipartResolver
就是 StandardServletMultipartResolver
。因此,在 SpringBoot 中上傳文件甚至可以零配置。
文件上傳具體步驟如下:
-
添加依賴
spring-boot-starter-web
-
創建文件上傳頁面(
upload.html
)在
resources
目錄下的static
目錄中創建一個upload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>文件上傳測試</title> </head> <body> <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="uploadFile" value="請選擇文件"> <input type="submit" value="上傳"> </form> </body> </html>
-
創建文件上傳處理接口
@RestController public class FileUploadController { SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); @PostMapping("/upload") public String upload(MultipartFile uploadFile, HttpServletRequest request) { /* 保存上傳文件的目錄 */ String realPath = request.getSession().getServletContext().getRealPath("/uploadFile/"); String format = sdf.format(new Date()); /* 在保存文件目錄下準備創建日期相關文件夾,以日期歸檔 */ File folder = new File(realPath + format); if (!folder.isDirectory()) folder.mkdirs(); /* 原始文件名 */ String oldName = uploadFile.getOriginalFilename(); /* 新文件名 */ String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."), oldName.length()); try { /* 文件保存 */ uploadFile.transferTo(new File(folder, newName)); /* 返回文件訪問路徑 */ String filePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() +"/uploadFile/" + format + newName; return filePath; }catch (IOException e){ e.printStackTrace(); } return "上傳失敗"; } }
-
啓動項目,運行測試
https://localhost:8080/upload
問題記錄:
這裏,遇到一個問題: 上傳成功並返回路徑,但項目中並未看到文件
首先,通過斷點排查,發現
String realPath = request.getSession().getServletContext().getRealPath("/uploadFile/");
返回的結果並不是期望的項目根目錄,而是一個位於 tomcat.xxxx.port(服務端口號) 的臨時目錄下,如下:
C:\Users\Administrator\AppData\Local\Temp\tomcat-docbase.6275041815373391706.8080\uploadFile\2020\06\22
查閱資料後發現這樣一行代碼
private static final String[] COMMON_DOC_ROOTS = { "src/main/webapp", "public","static" };
SpringBoot 在獲取路徑時會掃描該配置中的文件夾,所以嘗試在項目根目錄中新建一個 public 目錄,再次測試,發現成功,
realPath
也返回了正確的地址。
根目錄 ‘/’ 是靜態資源地址,可以直接訪問,但是像上面加了 pulbic 或者 static 目錄之後,如果還想要通過 URL 靜態訪問這個地址,就需要將該目錄頁加入靜態資源目錄列表
全局處理
@ControllerAdvice
註解,是@Controller
的增強版,其主要作用,就是用來做全局處理,一般搭配@ExceptionHandler
、@ModelAttribute
和@InitBinder
使用。
全局異常
只需要定義一個類,添加 @ControllerAdvice
註解。當系統啓動時,該類就會被掃描到 Spring 容器,然後定義 uploadException
方法,該方法上可以添加 @ExceptionHandler
註解指定處理哪種異常。方法的參數可以有異常實例、HttpServletResponse
與 HttpServletRequest
、Model
等,返回值可以是 JSON
、ModelAndView
、邏輯視圖名稱等。
@ControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ModelAndView uploadException(MaxUploadSizeExceededException e, HttpServletResponse resp) throws IOException{
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", "上傳文件大小超出");
modelAndView.setViewName("error");
return modelAndView;
}
}
全局數據
@ControllerAdvice
作爲一個全局數據處理組件,處理全局異常只是最常用的場景之一,當然也可以用來配合@ModelAttribute
處理全局數據。
@ControllerAdvice
public class GlobalConfig {
// value 屬性表示該條數據的 key, 返回值爲該條數據的 value
// 此時,在任意 Controller 下都可以通過參數中的 Model 獲取到 info 的數據。
@ModelAttribute(value = "info")
public Map<String, String> userinfo(){
HashMap<String, String> map = new HashMap<>();
map.put("username", "AmbroseCdMeng");
map.put("gender","男");
return map;
}
}
@RestController
public class HelloController {
public void hello(Model model) {
Map<String, Object> map = model.asMap();
Set<String> keySet = map.keySet();
Iterator<String> iterator = keySet.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
Object value = map.get(key);
System.out.println(key + " >>>>>>>>>> " + value);
}
}
}
請求參數預處理
@ControllerAdvice
還有另一個比較常用的場景,就是結合@InitBinder
實現請求參數預處理,即將表單中的數據綁定到實體類上時進行一些額外處理。
比如,兩個實體類都有 name
屬性,控制器中需要接受兩個實體類,在參數傳遞時,兩個 name
就會混淆。使用 @ControllerAdvice
結合 @InitBinder
就可以順利解決該問題。
@GetMapping("/book")
@ResponseBody
public String book(@ModelAttribute("b") Book book, @ModelAttribute("a")Author author){
return book.toString() + " >>>>>>>>>> " + author.toString();
}
訪問測試:
https://localhost:8080/book?b.name=bookname&a.name=authorname
自定義錯誤頁
全局異常雖然可以根據實際情況返回不同的頁面,但是這種異常處理方式一般用來處理應用級別的異常,有一些容器級別的錯誤處理不了。
而這些情況,默認是由 SpringBoot 的
BasicErrorController
類來處理的。
BasicErrorController
類的核心方法有兩個:errorHtml
(用來返回錯誤 HTML 頁面) 和error
(用來返回錯誤 JSON)
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
SpringBoot 默認會在 error 目錄下查找 4xx、5xx 的文件作爲錯誤視圖,如果找不到會回到 errorHtml 方法中,然後使用 error 作爲默認的錯誤視圖,如果 error 視圖也沒找到,就會返回默認的一個視圖。
基本配置
- 在 resource/static 目錄下創建 error 目錄
- 新建 4xx.html、5xx.html 頁面,或者更詳細一些直接以響應碼命名,如:404.html、500.html
由於這種定義都是靜態 HTML 頁面,無法向用戶展示詳細的錯誤信息。
所以,還可以採用 HTML 模板,以 Thymeleaf 爲例, Thymeleaf 的模板位於 classpath:/templates
目錄下,因此,在該目錄下創建錯誤展示頁。
製造一個異常來測試
@GetMapping("/errorHello")
public int errorHello(){
/* 拋出異常:測試錯誤頁面效果 */
return 1 / 0 ;
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1">
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>message</td>
<td th:text="${message}"></td>
</tr>
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
</table>
</body>
</html>
- 第一次測試:返回了全局異常中對應的
error
頁面,這說明在全局異常能處理的情況下,其優先級是最高的; - 將全局異常屏蔽,第二次測試:返回
resouce/static/error
目錄下的 500.html 頁面; - 在
resource/static/error
目錄下新增一個 5xx.html 頁面,第三次測試:返回resouce/static/error
500.html 頁面,這說明,同目錄下,響應碼.html
優先於4xx.html
或者5xx.html
- 在
template/error
目錄中新建4xx.html
和5xx.html
頁面,第四次測試:返回resouce/static/error
500.html 頁面,這說明,不同目錄下,響應碼.html
依然優先於4xx.html
或者5xx.html
- 刪除
resouce/static/error
目錄下的500.html
頁面,第五次測試:返回template/error
下的5xx.html
頁面,這說明,文件名相同時,動態頁面優先級高於靜態頁面
綜上,優先級從高到低依次爲:全局異常(能處理的範圍內) > 動態響應碼.html > 靜態響應碼.html > 動態4xx/5xx.html > 靜態4xx/5xx.html
複雜配置
所謂動態模板頁面,其實也只返回了 5 條固定信息而已。如果想自定義 Error 數據,該如何操作?
我們知道,errorHtml 和 error 放在在 BasicErrorController
方法中,進入該方法,可以看到,Error 信息都是通過 getErrorAttributes
方法獲取的。該方法調用了 接口 ErrorAttributes
的 getErrorAttributes
方法,而 ErrorAttributes
接口的 getErrorAttributes
方法在 DefaultErrorAttributes
中實現的。
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = this.getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
if (this.includeException != null) {
options = options.including(new Include[]{Include.EXCEPTION});
}
if (!options.isIncluded(Include.EXCEPTION)) {
errorAttributes.remove("exception");
}
if (!options.isIncluded(Include.STACK_TRACE)) {
errorAttributes.remove("trace");
}
if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
errorAttributes.put("message", "");
}
if (!options.isIncluded(Include.BINDING_ERRORS)) {
errorAttributes.remove("errors");
}
return errorAttributes;
}
繼續追查
DefaultErrorAttributes
, 發現是在ErrorMvcAutoConfiguration
中使用的
...
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
...
public class ErrorMvcAutoConfiguration {
...
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
...
}
@ConditionalOnMissingBean
可以看出,當系統沒有提供ErrorAttributes
纔會採用DefaultErrorAttributes
。因此,自定義錯誤提示時,只需要自己提供一個
ErrorAttributes
即可,而DefaultErrorAttributes
是ErrorAttributes
的子類,因此只需要繼承DefaultErrorAttributes
即可。
@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
errorAttributes.put("message", "我在 MyErrorAttribute 中定義了自定義 Error 頁面,所以到這裏來了");
errorAttributes.remove("error");
return errorAttributes;
}
}
另外,同樣,可以從源碼中看到,如果用戶沒有定義
ErrorViewResolver
,那麼默認使用的ErrorViewResolver
是DefaultErrorViewResolver
,正是在DefaultErrorViewResolver
中配置了默認取 error 目錄下尋找 4xx.html、5xx.html。如果開發者想要自定義 Error 視圖,只需要提供自己的 ErrorViewResolver 即可
@Component
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView mv = new ModelAndView("errorPage");
mv.addObject("message", "我在 MyErrorViewResolver 中定義了自定義 Error 頁面,所以跑到這裏來了");
mv.addAllObjects(model);
return mv;
}
}
接下來在 resources/templates 目錄下定義 errorPage.html
即可
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>這裏是自定義的 Error 頁面</h3>
<table border="1">
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>message</td>
<td th:text="${message}"></td>
</tr>
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
</table>
</body>
</html>
如果自定義 Error 數據和自定義 Error 頁面仍然不足以滿足開發需求,那麼還可以完全自定義 Error 內容
可以發現
BasicErrorController
本身只是一個默認的配置,若開發這沒有提供自己的ErrorController
,則 SpringBoot 將提供BasicErrorController
作爲默認的ErrorController
。如果想要靈活的自定義,只需要提供自己的ErrorController
即可。
提供自定義的 ErrorController
有兩種方式:一種是實現 ErrorController
接口,另一個是直接繼承 BasicErrorController
。
接下來,自定義 ErrorController
繼承 BasicErrorController
來實現:
public class MyErrorController extends BasicErrorController {
public MyErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
super(errorAttributes, errorProperties);
}
public MyErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorProperties, errorViewResolvers);
}
@Override
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
model.put("message", "我在 MyErrorController 中定義了自定義 ErrorHtml 信息,所以跑到這裏來了");
ModelAndView modelAndView = new ModelAndView("myErrorPage", model, status);
return modelAndView;
}
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
body.put("message","我在 MyErrorController 中定義了自定義 Error 信息,所以跑到這裏來了");
return new ResponseEntity<>(body, status);
}
}
最後在 resources/templates
目錄下提供 myErrorPage.html
頁面作爲視圖頁面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>這裏是自定義的 Error 頁面</h3>
<table border="1">
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>message</td>
<td th:text="${message}"></td>
</tr>
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
</table>
</body>
</html>
以上在 `MyErrorController` 中
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
一行提示已經過時,會建議修改如下
Map<String, Object> body = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)));
但是 Colloections.unmodifiabelMap
獲取到的 Map
對象是禁止修改的,所以如下代碼會拋出 UnsupportedOperationException
異常
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
model.put("message", "我在 MyErrorController 中定義了自定義 ErrorHtml 信息,所以跑到這裏來了");