SpringBoot 學習筆記_整合 Web 開發(一)

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 處理器。

  1. 創建 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;
    }
    
  2. 創建 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 的組合註解

  3. 運行項目,測試返回結果

    https://localhost:8080/user

除了默認的 jackson-databind JSON 轉換器外,還有很多常見的轉換器,如 Gsonfastjson 等。

fastjson 是阿里巴巴的開源 JSON 解析框架,是目前 JSON 解析速度最快的開源框架。該框架也可以集成到 SpringBoot 中,不同的是, fastjson 集成完成後不能立馬使用,需要開發者提供相應的 HttpMessageConverter 之後纔可以使用。

具體步驟如下:

  1. 去除默認的 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>
    
  2. 配置 fastjsonHttpMessageConverter

    @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;
        }
    }
    
  3. 配置響應編碼,防止亂碼情況發生(在 application.properties 中)。

    spring.http.encoding.force-response=true
    

    如上配置會觸發警告:不建議使用該 key,建議如下:

    server.servlet.encoding.force-response=true
    
  4. 運行項目,測試返回結果

    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 中文件上傳一共涉及兩個組件。CommonsMultipartResolverStandardServletMultipartResolver,其中 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 中上傳文件甚至可以零配置。

文件上傳具體步驟如下:

  1. 添加依賴

    spring-boot-starter-web
    
  2. 創建文件上傳頁面(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>
    
  3. 創建文件上傳處理接口

    @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 "上傳失敗";
        }
    }
    
  4. 啓動項目,運行測試

    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 註解指定處理哪種異常。方法的參數可以有異常實例、HttpServletResponseHttpServletRequestModel 等,返回值可以是 JSONModelAndView、邏輯視圖名稱等。

@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 視圖也沒找到,就會返回默認的一個視圖。

基本配置

  1. 在 resource/static 目錄下創建 error 目錄
  2. 新建 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.html5xx.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 方法獲取的。該方法調用了 接口 ErrorAttributesgetErrorAttributes 方法,而 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 即可,而 DefaultErrorAttributesErrorAttributes 的子類,因此只需要繼承 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,那麼默認使用的 ErrorViewResolverDefaultErrorViewResolver,正是在 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 信息,所以跑到這裏來了");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章