https://blog.csdn.net/jwf111/article/details/88571067
ErrorController
在springboot項目中當我們訪問一個不存在的url時經常會出現以下頁面
在postman訪問時則是以下情況
image
對於上面的情況究竟是什么原因造成呢,實際上當springboot項目出現異常時,默認會跳轉到/error,而/error則是由BasicErrorController進行處理,其代碼如下
-
-
-
public class BasicErrorController extends AbstractErrorController {
-
private final ErrorProperties errorProperties;
-
-
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
-
this(errorAttributes, errorProperties, Collections.emptyList());
-
}
-
-
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
-
super(errorAttributes, errorViewResolvers);
-
Assert.notNull(errorProperties, "ErrorProperties must not be null");
-
this.errorProperties = errorProperties;
-
}
-
-
public String getErrorPath() {
-
return this.errorProperties.getPath();
-
}
-
-
-
-
-
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 ? new ModelAndView("error", model) : modelAndView;
-
}
-
-
-
-
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 boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
-
IncludeStacktrace include = this.getErrorProperties().getIncludeStacktrace();
-
if (include == IncludeStacktrace.ALWAYS) {
-
return true;
-
} else {
-
return include == IncludeStacktrace.ON_TRACE_PARAM ? this.getTraceParameter(request) : false;
-
}
-
}
-
-
protected ErrorProperties getErrorProperties() {
-
return this.errorProperties;
-
}
-
}
- 可見BasicErrorController是一個控制器,對/error進行處理
- BasicErrorController根據Accept頭的內容,輸出不同格式的錯誤響應。比如針對瀏覽器的請求生成html頁面,針對其它請求生成json格式的返回。字段為accept的text/html的內容來判斷
- 我們也可自定義ErrorController來實現自己對錯誤的處理,例如瀏覽器訪問也返回json字符串(返回text/html),或自定義錯誤頁面,不同status跳轉不同的頁面等,同時其他請求也可自定義返回的json格式
下面是自己寫的一個ErrorController
-
import java.util.HashMap;
-
import java.util.Map;
-
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
-
import com.alibaba.fastjson.JSONObject;
-
import com.xuecheng.framework.model.response.ErrorCode;
-
import com.xuecheng.framework.model.response.ResultCode;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.boot.web.servlet.error.ErrorAttributes;
-
import org.springframework.boot.web.servlet.error.ErrorController;
-
import org.springframework.stereotype.Controller;
-
import org.springframework.web.bind.annotation.RequestMapping;
-
/**
-
* web錯誤 全局處理
-
* @author jiangwf
-
*
-
*/
-
import org.springframework.web.bind.annotation.ResponseBody;
-
import org.springframework.web.context.request.ServletWebRequest;
-
-
-
public class InterfaceErrorController implements ErrorController {
-
private static final String ERROR_PATH="/error";
-
private ErrorAttributes errorAttributes;
-
-
-
public String getErrorPath() {
-
return ERROR_PATH;
-
}
-
-
public InterfaceErrorController(ErrorAttributes errorAttributes) {
-
this.errorAttributes=errorAttributes;
-
}
-
-
/**
-
* web頁面錯誤處理
-
*/
-
-
-
public String errorPageHandler(HttpServletRequest request,HttpServletResponse response) {
-
ServletWebRequest requestAttributes = new ServletWebRequest(request);
-
Map<String, Object> attr = this.errorAttributes.getErrorAttributes(requestAttributes, false);
-
JSONObject jsonObject = new JSONObject();
-
ErrorCode errorCode = new ErrorCode(false, (int) attr.get("status"), (String) attr.get("message"));
-
return JSONObject.toJSONString(errorCode);
-
}
-
-
/**
-
* 除web頁面外的錯誤處理,比如json/xml等
-
*/
-
-
-
public ResultCode errorApiHander(HttpServletRequest request) {
-
ServletWebRequest requestAttributes = new ServletWebRequest(request);
-
Map<String, Object> attr=this.errorAttributes.getErrorAttributes(requestAttributes, false);
-
return new ErrorCode(false, (int)attr.get("status"), (String) attr.get("message"));
-
}
-
-
}
- 當是瀏覽器訪問時返回json字符串
image
- 當是其他請求時返回自定義的ErrorCode
image
- ErrorCode代碼如下
-
import lombok.AllArgsConstructor;
-
import lombok.Data;
-
import lombok.ToString;
-
-
-
-
-
public class ErrorCode implements ResultCode{
-
-
private boolean success;
-
-
private int code;
-
-
private String message;
-
-
-
-
public boolean success() {
-
return false;
-
}
-
-
-
public int code() {
-
return 0;
-
}
-
-
-
public String message() {
-
return null;
-
}
-
}
- ResultCode代碼如下
-
public interface ResultCode {
-
//操作是否成功,true為成功,false操作失敗
-
boolean success();
-
//操作代碼
-
int code();
-
//提示信息
-
String message();
-
-
}
@ControllerAdvice
- 上面我們提到ErrorController可對全局錯誤進行處理,但是其獲取不到異常的具體信息,同時也無法根據異常類型進行不同的響應,例如對自定義異常的處理
- 而@ControllerAdvice可對全局異常進行捕獲,包括自定義異常
- 需要清楚的是,其是應用於對springmvc中的控制器拋出的異常進行處理,而對於404這樣不會進入控制器處理的異常不起作用,所以此時還是要依靠ErrorController來處理
問題:
- 實際上,當出現錯誤,如獲取值為空或出現異常時,我們並不希望用戶看到異常的具體信息,而是希望對對應的錯誤和異常做相應提示
- 在MVC框架中很多時候會出現執行異常,那我們就需要加try/catch進行捕獲,如果service層和controller層都加上,那就會造成代碼冗余
解決方法:
- 統一返回的數據格式,如上的ResultCode,可實現其做更多擴展,對於程序的可預知錯誤,我們采取拋出異常的方式,再統一處理
- 我們在編程時的順序是先校驗判斷,有問題則拋出異常信息,最后執行具體的業務操作,返回成功信息
- 在統一異常處理類中去捕獲異常,無需再代碼中try/catch,向用戶返回統一規范的響應信息
異常處理流程
系統對異常的處理使用統一的異常處理流程:
- 自定義異常類型
- 自定義錯誤代碼及錯誤信息
- 對於可預知的異常由程序員在代碼中主動拋出,有SpringMVC統一捕獲
可預知異常是程序員在代碼中手動拋出本系統定義的特點異常類型,由於是程序員拋出的異常,通常異常信息比較齊全,程序員在拋出時會指定錯誤代碼及錯誤信息,獲取異常信息也比較方便 - 對於不可預知的異常(運行時異常)由SpringMVC統一捕獲Exception類型的異常
不可預知的異常通常是由於系統出現bug、或一些不可抗拒的錯誤(比如網絡中斷、服務器宕機等),異常類型為RuntimeException類型(運行時異常) - 可預知異常及不可預知異常最終都會采用統一的信息格式(錯誤代碼+錯誤信息)來表示,最終也會隨請求響應給客戶端
異常拋出及處理流程
image
- 在controller、service、dao中程序員拋出自定義異常;SpringMVC框架拋出框架異常類型
- 統一由異常捕獲類捕獲異常並進行處理
- 捕獲到自定義異常則直接取出錯誤代碼及錯誤信息,響應給用戶
- 捕獲到非自定義異常類型首先從Map中找該異常類型是否對應具體的錯誤代碼,如果有則取出錯誤代碼和錯誤信息並響應給用戶,如果從Map中占不到異常類型所對應的錯誤代碼則統一為99999錯誤代碼並響應給用戶
- 將錯誤代碼及錯誤信息以json格式響應給用戶
下面就開始我們的異常處理編程
一、可預知異常
- 自定義異常類
-
import com.xuecheng.framework.model.response.ResultCode;
-
import jdk.nashorn.internal.objects.annotations.Getter;
-
-
/**
-
* @Author: jiangweifan
-
* @Date: 2019/3/4 20:06
-
* @Description:
-
*/
-
public class CustomException extends RuntimeException {
-
-
private ResultCode resultCode;
-
-
public CustomException(ResultCode resultCode) {
-
super("錯誤代碼:" + resultCode.code()+" 錯誤信息:" + resultCode.message());
-
this.resultCode = resultCode;
-
}
-
-
public ResultCode getResultCode() {
-
return resultCode;
-
}
-
}
- 自定義異常拋出類
-
import com.xuecheng.framework.model.response.ResultCode;
-
-
/**
-
* @Author: jiangweifan
-
* @Date: 2019/3/4 20:09
-
* @Description:
-
*/
-
public class ExceptionCast {
-
-
public static void cast(ResultCode resultCode, boolean condition) {
-
if (condition) {
-
throw new CustomException(resultCode);
-
}
-
}
-
}
- 異常捕獲類
使用@ControllerAdvice和@ExceptionHandler注解來捕獲指定類型的異常
-
import com.google.common.collect.ImmutableMap;
-
import com.xuecheng.framework.model.response.CommonCode;
-
import com.xuecheng.framework.model.response.ResponseResult;
-
import com.xuecheng.framework.model.response.ResultCode;
-
import lombok.extern.slf4j.Slf4j;
-
import org.springframework.web.bind.annotation.ControllerAdvice;
-
import org.springframework.web.bind.annotation.ExceptionHandler;
-
import org.springframework.web.bind.annotation.ResponseBody;
-
-
import java.net.SocketTimeoutException;
-
-
/**
-
* @Author: jiangweifan
-
* @Date: 2019/3/4 20:13
-
* @Description:
-
*/
-
-
-
public class ExceptionCatch {
-
-
-
-
public ResponseResult customException(CustomException e) {
-
log.error( "catch exception : {} \r\nexception", e.getMessage(), e);
-
ResponseResult responseResult = new ResponseResult(e.getResultCode());
-
return responseResult;
-
}
-
-
}
4.1 定義響應數據格式
-
import lombok.Data;
-
import lombok.NoArgsConstructor;
-
import lombok.ToString;
-
-
/**
-
* @Author: mrt.
-
* @Description:
-
* @Date:Created in 2018/1/24 18:33.
-
* @Modified By:
-
*/
-
-
-
-
public class ResponseResult implements Response {
-
-
//操作是否成功
-
boolean success = SUCCESS;
-
-
//操作代碼
-
int code = SUCCESS_CODE;
-
-
//提示信息
-
String message;
-
-
public ResponseResult(ResultCode resultCode){
-
this.success = resultCode.success();
-
this.code = resultCode.code();
-
this.message = resultCode.message();
-
}
-
-
public static ResponseResult SUCCESS(){
-
return new ResponseResult(CommonCode.SUCCESS);
-
}
-
public static ResponseResult FAIL(){
-
return new ResponseResult(CommonCode.FAIL);
-
}
-
-
}
-
-
其中Response代碼如下
-
public interface Response {
-
public static final boolean SUCCESS = true;
-
public static final int SUCCESS_CODE = 10000;
-
}
4.2 定義錯誤代碼(ResultCode上文已給出)
-
import com.xuecheng.framework.model.response.ResultCode;
-
import lombok.ToString;
-
-
/**
-
* Created by mrt on 2018/3/5.
-
*/
-
-
public enum CmsCode implements ResultCode {
-
CMS_ADDPAGE_EXISTSNAME( false,24001,"頁面名稱已存在!"),
-
CMS_GENERATEHTML_DATAURLISNULL( false,24002,"從頁面信息中找不到獲取數據的url!"),
-
CMS_GENERATEHTML_DATAISNULL( false,24003,"根據頁面的數據url獲取不到數據!"),
-
CMS_GENERATEHTML_TEMPLATEISNULL( false,24004,"頁面模板為空!"),
-
CMS_GENERATEHTML_HTMLISNULL( false,24005,"生成的靜態html為空!"),
-
CMS_GENERATEHTML_SAVEHTMLERROR( false,24005,"保存靜態html出錯!"),
-
CMS_COURSE_PERVIEWISNULL( false,24007,"預覽頁面為空!"),
-
CMS_TEMPLATEFILE_ERROR( false,24008,"模板文件需要.ftl后綴!"),
-
CMS_TEMPLATEFILE_NULL( false,24009,"模板文件為空!"),
-
CMS_TEMPLATEFILE_EXCEPTION( false,24010,"解析模板文件異常!"),
-
CMS_TEMPLATEFILE_FAIL( false,24011,"模板文件存儲失敗!"),
-
CMS_TEMPLATEFILE_DELETE_ERROR( false,24012,"模板文件刪除失敗!"),
-
CMS_Config_NOTEXISTS( false,24013,"不存在該數據模型!"),
-
CMS_PAGE_NULL( false,24014,"不存在該頁面數據!"),
-
CMS_GENERATEHTML_CONTENT_FAIL( false,24014,"獲取頁面模板失敗!");
-
//操作代碼
-
boolean success;
-
//操作代碼
-
int code;
-
//提示信息
-
String message;
-
private CmsCode(boolean success, int code, String message){
-
this.success = success;
-
this.code = code;
-
this.message = message;
-
}
-
-
-
public boolean success() {
-
return success;
-
}
-
-
-
public int code() {
-
return code;
-
}
-
-
-
public String message() {
-
return message;
-
}
-
}
- 在方法中拋出異常進行測試
-
@GetMapping("/list/{page}/{size}")
-
public QueryResponseResult findList( @PathVariable("page") int page, @PathVariable("size")int size, QueryPageRequest queryPageRequest) {
-
ExceptionCast.cast(CmsCode.CMS_COURSE_PERVIEWISNULL, queryPageRequest == null);
-
return pageService.findList(page,size,queryPageRequest);
-
}
最終方法得到以下結果
image
二、不可預知異常處理
- 在異常捕獲類中添加對Exception類型異常的捕獲,完整代碼如下
-
import com.google.common.collect.ImmutableMap;
-
import com.xuecheng.framework.model.response.CommonCode;
-
import com.xuecheng.framework.model.response.ResponseResult;
-
import com.xuecheng.framework.model.response.ResultCode;
-
import lombok.extern.slf4j.Slf4j;
-
import org.springframework.web.bind.annotation.ControllerAdvice;
-
import org.springframework.web.bind.annotation.ExceptionHandler;
-
import org.springframework.web.bind.annotation.ResponseBody;
-
-
import java.net.SocketTimeoutException;
-
-
/**
-
* @Author: jiangweifan
-
* @Date: 2019/3/4 20:13
-
* @Description:
-
*/
-
-
-
public class ExceptionCatch {
-
-
//使用EXCEPTIOS存放異常類型 和錯誤代碼的映射,ImmutableMap的特點是已創建就不可變,並且線程安全
-
private static ImmutableMap<Class<? extends Throwable>, ResultCode> EXCEPTIOS;
-
//是由builder來構建一個異常類型和錯誤代碼的映射
-
private static ImmutableMap.Builder<Class<? extends Throwable>, ResultCode> builder =
-
ImmutableMap.builder();
-
-
static {
-
//初始化基礎類型異常與錯誤代碼的映射
-
builder.put(NullPointerException.class, CommonCode.NULL);
-
builder.put(SocketTimeoutException.class, CommonCode.NULL);
-
}
-
-
-
-
public ResponseResult customException(CustomException e) {
-
log.error( "catch exception : {} \r\nexception", e.getMessage(), e);
-
ResponseResult responseResult = new ResponseResult(e.getResultCode());
-
return responseResult;
-
}
-
-
-
-
public ResponseResult exception(Exception e) {
-
log.error( "catch exception : {} \r\nexception", e.getMessage(), e);
-
if (EXCEPTIOS == null) {
-
EXCEPTIOS = builder.build();
-
}
-
final ResultCode resultCode = EXCEPTIOS.get(e.getClass());
-
if (resultCode != null) {
-
return new ResponseResult(resultCode);
-
} else {
-
return new ResponseResult(CommonCode.SERVER_ERROR);
-
}
-
-
}
-
-
}
- 對於不可預知異常的處理,我們采取先從定義好的Map獲取該異常類型對應的錯誤代碼和錯誤信息,若沒有則統一返回CommonCode.SERVER_ERROR
- 對於CommonCode代碼如下(ResultCode上文已給出)
-
import lombok.ToString;
-
-
/**
-
* @Author: mrt.
-
* @Description:
-
* @Date:Created in 2018/1/24 18:33.
-
* @Modified By:
-
*/
-
-
-
public enum CommonCode implements ResultCode{
-
-
SUCCESS( true,10000,"操作成功!"),
-
FAIL( false,19999,"操作失敗!"),
-
UNAUTHENTICATED( false,10001,"此操作需要登陸系統!"),
-
UNAUTHORISE( false,10002,"權限不足,無權操作!"),
-
NULL( false,10003,"空值異常!"),
-
TIMEOUT( false, 10004, "服務器連接超時!"),
-
SERVER_ERROR( false,99999,"抱歉,系統繁忙,請稍后重試!");
-
// private static ImmutableMap<Integer, CommonCode> codes ;
-
//操作是否成功
-
boolean success;
-
//操作代碼
-
int code;
-
//提示信息
-
String message;
-
private CommonCode(boolean success,int code, String message){
-
this.success = success;
-
this.code = code;
-
this.message = message;
-
}
-
-
-
public boolean success() {
-
return success;
-
}
-
-
public int code() {
-
return code;
-
}
-
-
-
public String message() {
-
return message;
-
}
-
}
- 方法中進行測試
-
-
public QueryResponseResult findList(
-
int a= 1/0;
-
return pageService.findList(page,size,queryPageRequest);
-
}
瀏覽器訪問結果如下: