21 錯誤處理機制
阿新 • • 發佈:2018-12-29
1 SpringBoot預設的錯誤處理機制
1.1 預設效果
1.1.1 瀏覽器,返回一個預設的錯誤頁面
1.1.2 非瀏覽器,預設響應一個json資料
1.2 原理
- org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
1.2.1 錯誤出現
系統出現4xx或5xx之類的錯誤時,ErrorPageCustomizer就會生效(定製錯誤的響應規則);就會來到/error請求;就會被BasicErrorController處理;
// 錯誤定製頁面
@Bean
public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
}
- ErrorPageCustomizer
private static class ErrorPageCustomizer implements ErrorPageRegistrar , Ordered {
private final ServerProperties properties;
private final DispatcherServletPath dispatcherServletPath;
protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
this.properties = properties;
this.dispatcherServletPath = dispatcherServletPath;
}
// 註冊錯誤頁面的響應規則
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
/*
系統出現錯誤後,來到error請求進行處理(web.xml註冊的錯誤頁面規則)
this.properties.getError().getPath() ----> /error
*/
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
}
public int getOrder() {
return 0;
}
}
1.2.2 接收並處理 /error 請求
@Bean
@ConditionalOnMissingBean(
value = {ErrorController.class},
search = SearchStrategy.CURRENT
)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
}
- BasicErrorController
/*
${server.error.path:${error.path:/error}}
如果 server.error.path 未配置,則使用 ${error.path:/error}
如果 error.path 未配置,則使用 "/error"
*/
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
// 產生html型別的資料,瀏覽器傳送的請求來到這個方法處理
@RequestMapping(
produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
// 錯誤狀態碼
HttpStatus status = this.getStatus(request);
// 請求資訊
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
// 去哪個頁面作為錯誤頁面;包含頁面地址和頁面內容
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
// 產生json資料,非瀏覽器來到這個方法處理
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
}
}
- 根據請求頭區分瀏覽器與非瀏覽器
- 錯誤頁面資訊
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
/*
getErrorAttributes --> ErrorAttributes(介面) --> DefaultErrorAttributes(實現)
DefaultErrorAttributes:
*/
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
- DefaultErrorAttributes --> getErrorAttributes
// 頁面資訊
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
// 時間戳
errorAttributes.put("timestamp", new Date());
// 狀態碼
this.addStatus(errorAttributes, webRequest);
// 錯誤詳情
this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
// 請求地址
this.addPath(errorAttributes, webRequest);
return errorAttributes;
}
- 檢視解析
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
// 獲取所有的檢視解析器
Iterator var5 = this.errorViewResolvers.iterator();
ModelAndView modelAndView;
do {
if (!var5.hasNext()) {
return null;
}
ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
// 返回相應錯誤的檢視
modelAndView = resolver.resolveErrorView(request, status, model);
} while(modelAndView == null);
return modelAndView;
}
1.2.3 解析檢視
- org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver
private ModelAndView resolve(String viewName, Map<String, Object> model) {
// 遇到錯誤時,SpringBoot根據errorViewName查詢頁面 eg:error/404.html
String errorViewName = "error/" + viewName;
// 模板引擎對頁面進行解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//解析成功,返回到errorViewName指定的檢視地址
//解析失敗,前往靜態資原始檔夾下找errorViewName對應的頁面 error/404.html
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
static {
Map<Series, String> views = new EnumMap(Series.class);
// 錯誤碼
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
2 自定義錯誤響應
2.1 自定義錯誤頁面
有模板引擎的情況下,將錯誤頁面命名為 錯誤狀態碼.html(404.html) 放在模板引擎資料夾裡面的 error資料夾下,發生此狀態碼的錯誤就會來到對應的頁面
2.1.1 使用4xx和5xx作為錯誤頁面的檔名來匹配這種型別的所有錯誤,精確優先(優先尋找精確的狀態碼.html);
- 頁面能獲取的資訊
timestamp:時間戳
status:狀態碼
error:錯誤提示
exception:異常物件
message:異常訊息
errors:JSR303資料校驗的錯誤都在這裡
2.1.2 沒有模板引擎(模板引擎找不到這個錯誤頁面),靜態資原始檔夾下找
2.1.3 以上都沒有錯誤頁面,就是預設來到SpringBoot預設的錯誤提示頁面
2.2 自定義json響應
2.2.1 自定義異常處理&返回定製json資料;
- 自定義異常
@package com.gp6.springboot18.exception;
public class UserNotExistException extends RuntimeException {
public UserNotExistException() {
super("使用者不存在!");
}
}
- 測試自定義異常
package com.gp6.springboot18.controller;
import com.gp6.springboot18.exception.UserNotExistException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
/*
訪問
當前專案(localhost:8080)
或
localhost:8080/index.htm
都會被模板引擎自動解析,查詢templates/index.html
*/
@RequestMapping({"/","/index.html"})
public String index(){
return "login";
}
/*
測試自定義異常
*/
@RequestMapping({"testException"})
@ResponseBody
public String testException(@RequestParam("userName") String userName){
if("gp6".equals(userName)){
throw new UserNotExistException();
}
return "測試自定義異常";
}
}
2.2.2 自定義異常處理器(不能自適應)
package com.gp6.springboot18.config;
import com.gp6.springboot18.exception.UserNotExistException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class MyExceptionHandler {
// 瀏覽器客戶端返回的都是json
@ResponseBody
// 捕獲UserNotExistException異常
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notExist");
map.put("message",e.getMessage());
return map;
}
}
- 瀏覽器和非瀏覽器都返回json資料,不能自動跳轉錯誤頁面
2.2.3 轉發到/error進行自適應響應效果處理
package com.gp6.springboot18.config;
import com.gp6.springboot18.exception.UserNotExistException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(UserNotExistException.class)
public String handleException2(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notExist");
map.put("message","該使用者不存在啊");
// 如果不設定狀態碼,不能跳轉自定義錯誤頁面
request.setAttribute("javax.servlet.error.status_code",500);
// 轉發到/error
return "forward:/error";
}
}
- 測試
2.2.4 將自定義資料返回
- 響應資料返回原理
出現錯誤後,會來到/error請求,被BasicErrorController處理,響應出去可以獲取的資料是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)規定的方法)
- 相應資料返回方法
1、編寫一個ErrorController的實現類【或者是編寫AbstractErrorController的子類】,放在容器中
2、頁面上能用的資料,或者是json返回能用的資料都是通過errorAttributes.getErrorAttributes()得到
容器中DefaultErrorAttributes.getErrorAttributes();預設進行資料處理的
- 自定義ErrorAttributes
package com.gp6.springboot18.config;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
//給容器中加入我們自己定義的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
//返回值的map就是頁面和json能獲取的所有欄位
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("company","gp6");
//我們的異常處理器攜帶的資料
Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
map.put("ext",ext);
return map;
}
}
- 測試