全局異常處理及自定義異常:ErrorController與@ControllerAdvice區別和用法


https://blog.csdn.net/jwf111/article/details/88571067

ErrorController

在springboot項目中當我們訪問一個不存在的url時經常會出現以下頁面

在postman訪問時則是以下情況

image

image

對於上面的情況究竟是什么原因造成呢,實際上當springboot項目出現異常時,默認會跳轉到/error,而/error則是由BasicErrorController進行處理,其代碼如下

  1.  
    @Controller
  2.  
    @RequestMapping({"${server.error.path:${error.path:/error}}"})
  3.  
    public class BasicErrorController extends AbstractErrorController {
  4.  
         private final ErrorProperties errorProperties;
  5.  
     
  6.  
         public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
  7.  
             this(errorAttributes, errorProperties, Collections.emptyList());
  8.  
        }
  9.  
     
  10.  
         public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
  11.  
             super(errorAttributes, errorViewResolvers);
  12.  
            Assert.notNull(errorProperties,  "ErrorProperties must not be null");
  13.  
             this.errorProperties = errorProperties;
  14.  
        }
  15.  
     
  16.  
         public String getErrorPath() {
  17.  
             return this.errorProperties.getPath();
  18.  
        }
  19.  
     
  20.  
         @RequestMapping(
  21.  
            produces = {"text/html"}
  22.  
        )
  23.  
         public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  24.  
            HttpStatus status =  this.getStatus(request);
  25.  
            Map<String, Object> model = Collections.unmodifiableMap( this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  26.  
            response.setStatus(status.value());
  27.  
            ModelAndView modelAndView =  this.resolveErrorView(request, response, status, model);
  28.  
             return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
  29.  
        }
  30.  
     
  31.  
         @RequestMapping
  32.  
         @ResponseBody
  33.  
         public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  34.  
            Map<String, Object> body =  this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
  35.  
            HttpStatus status =  this.getStatus(request);
  36.  
             return new ResponseEntity(body, status);
  37.  
        }
  38.  
     
  39.  
         protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
  40.  
            IncludeStacktrace include =  this.getErrorProperties().getIncludeStacktrace();
  41.  
             if (include == IncludeStacktrace.ALWAYS) {
  42.  
                 return true;
  43.  
            }  else {
  44.  
                 return include == IncludeStacktrace.ON_TRACE_PARAM ? this.getTraceParameter(request) : false;
  45.  
            }
  46.  
        }
  47.  
     
  48.  
         protected ErrorProperties getErrorProperties() {
  49.  
             return this.errorProperties;
  50.  
        }
  51.  
    }
  • 可見BasicErrorController是一個控制器,對/error進行處理
  • BasicErrorController根據Accept頭的內容,輸出不同格式的錯誤響應。比如針對瀏覽器的請求生成html頁面,針對其它請求生成json格式的返回。字段為accept的text/html的內容來判斷
  • 我們也可自定義ErrorController來實現自己對錯誤的處理,例如瀏覽器訪問也返回json字符串(返回text/html),或自定義錯誤頁面,不同status跳轉不同的頁面等,同時其他請求也可自定義返回的json格式

下面是自己寫的一個ErrorController

  1.  
    import java.util.HashMap;
  2.  
    import java.util.Map;
  3.  
     
  4.  
    import javax.servlet.http.HttpServletRequest;
  5.  
    import javax.servlet.http.HttpServletResponse;
  6.  
     
  7.  
    import com.alibaba.fastjson.JSONObject;
  8.  
    import com.xuecheng.framework.model.response.ErrorCode;
  9.  
    import com.xuecheng.framework.model.response.ResultCode;
  10.  
    import org.springframework.beans.factory.annotation.Autowired;
  11.  
    import org.springframework.boot.web.servlet.error.ErrorAttributes;
  12.  
    import org.springframework.boot.web.servlet.error.ErrorController;
  13.  
    import org.springframework.stereotype.Controller;
  14.  
    import org.springframework.web.bind.annotation.RequestMapping;
  15.  
    /**
  16.  
     * web錯誤 全局處理
  17.  
     * @author jiangwf
  18.  
     *
  19.  
     */
  20.  
    import org.springframework.web.bind.annotation.ResponseBody;
  21.  
    import org.springframework.web.context.request.ServletWebRequest;
  22.  
     
  23.  
    @Controller
  24.  
    public class InterfaceErrorController implements ErrorController {
  25.  
         private static final String ERROR_PATH="/error";
  26.  
         private ErrorAttributes errorAttributes;
  27.  
     
  28.  
         @Override
  29.  
         public String getErrorPath() {
  30.  
             return ERROR_PATH;
  31.  
        }
  32.  
         @Autowired
  33.  
         public InterfaceErrorController(ErrorAttributes errorAttributes) {
  34.  
             this.errorAttributes=errorAttributes;
  35.  
        }
  36.  
     
  37.  
         /**
  38.  
         * web頁面錯誤處理
  39.  
         */
  40.  
         @RequestMapping(value=ERROR_PATH,produces="text/html")
  41.  
         @ResponseBody
  42.  
         public String errorPageHandler(HttpServletRequest request,HttpServletResponse response) {
  43.  
            ServletWebRequest requestAttributes =   new ServletWebRequest(request);
  44.  
             Map<String, Object> attr = this.errorAttributes.getErrorAttributes(requestAttributes, false);
  45.  
            JSONObject jsonObject =  new JSONObject();
  46.  
            ErrorCode errorCode =  new ErrorCode(false, (int) attr.get("status"), (String) attr.get("message"));
  47.  
             return JSONObject.toJSONString(errorCode);
  48.  
        }
  49.  
     
  50.  
         /**
  51.  
         * 除web頁面外的錯誤處理,比如json/xml等
  52.  
         */
  53.  
         @RequestMapping(value=ERROR_PATH)
  54.  
         @ResponseBody
  55.  
         public ResultCode errorApiHander(HttpServletRequest request) {
  56.  
            ServletWebRequest requestAttributes =  new ServletWebRequest(request);
  57.  
             Map<String, Object> attr=this.errorAttributes.getErrorAttributes(requestAttributes, false);
  58.  
             return new ErrorCode(false, (int)attr.get("status"), (String) attr.get("message"));
  59.  
        }
  60.  
     
  61.  
    }
  • 當是瀏覽器訪問時返回json字符串image

    image

  • 當是其他請求時返回自定義的ErrorCodeimage

    image

  • ErrorCode代碼如下
  1.  
    import lombok.AllArgsConstructor;
  2.  
    import lombok.Data;
  3.  
    import lombok.ToString;
  4.  
     
  5.  
    @ToString
  6.  
    @Data
  7.  
    @AllArgsConstructor
  8.  
    public class ErrorCode implements ResultCode{
  9.  
     
  10.  
         private boolean success;
  11.  
     
  12.  
         private int code;
  13.  
     
  14.  
         private String message;
  15.  
     
  16.  
     
  17.  
         @Override
  18.  
         public boolean success() {
  19.  
             return false;
  20.  
        }
  21.  
     
  22.  
         @Override
  23.  
         public int code() {
  24.  
             return 0;
  25.  
        }
  26.  
     
  27.  
         @Override
  28.  
         public String message() {
  29.  
             return null;
  30.  
        }
  31.  
    }
  • ResultCode代碼如下
  1.  
    public interface ResultCode {
  2.  
         //操作是否成功,true為成功,false操作失敗
  3.  
         boolean success();
  4.  
         //操作代碼
  5.  
         int code();
  6.  
         //提示信息
  7.  
         String message();
  8.  
     
  9.  
    }

@ControllerAdvice

  • 上面我們提到ErrorController可對全局錯誤進行處理,但是其獲取不到異常的具體信息,同時也無法根據異常類型進行不同的響應,例如對自定義異常的處理
  • 而@ControllerAdvice可對全局異常進行捕獲,包括自定義異常
  • 需要清楚的是,其是應用於對springmvc中的控制器拋出的異常進行處理,而對於404這樣不會進入控制器處理的異常不起作用,所以此時還是要依靠ErrorController來處理

 

問題:

 

  • 實際上,當出現錯誤,如獲取值為空或出現異常時,我們並不希望用戶看到異常的具體信息,而是希望對對應的錯誤和異常做相應提示
  • 在MVC框架中很多時候會出現執行異常,那我們就需要加try/catch進行捕獲,如果service層和controller層都加上,那就會造成代碼冗余

 

解決方法:

 

  • 統一返回的數據格式,如上的ResultCode,可實現其做更多擴展,對於程序的可預知錯誤,我們采取拋出異常的方式,再統一處理
  • 我們在編程時的順序是先校驗判斷,有問題則拋出異常信息,最后執行具體的業務操作,返回成功信息
  • 在統一異常處理類中去捕獲異常,無需再代碼中try/catch,向用戶返回統一規范的響應信息

異常處理流程

系統對異常的處理使用統一的異常處理流程:

  1. 自定義異常類型
  2. 自定義錯誤代碼及錯誤信息
  3. 對於可預知的異常由程序員在代碼中主動拋出,有SpringMVC統一捕獲
    可預知異常是程序員在代碼中手動拋出本系統定義的特點異常類型,由於是程序員拋出的異常,通常異常信息比較齊全,程序員在拋出時會指定錯誤代碼及錯誤信息,獲取異常信息也比較方便
  4. 對於不可預知的異常(運行時異常)由SpringMVC統一捕獲Exception類型的異常
    不可預知的異常通常是由於系統出現bug、或一些不可抗拒的錯誤(比如網絡中斷、服務器宕機等),異常類型為RuntimeException類型(運行時異常)
  5. 可預知異常及不可預知異常最終都會采用統一的信息格式(錯誤代碼+錯誤信息)來表示,最終也會隨請求響應給客戶端

異常拋出及處理流程

image

image

  1. 在controller、service、dao中程序員拋出自定義異常;SpringMVC框架拋出框架異常類型
  2. 統一由異常捕獲類捕獲異常並進行處理
  3. 捕獲到自定義異常則直接取出錯誤代碼及錯誤信息,響應給用戶
  4. 捕獲到非自定義異常類型首先從Map中找該異常類型是否對應具體的錯誤代碼,如果有則取出錯誤代碼和錯誤信息並響應給用戶,如果從Map中占不到異常類型所對應的錯誤代碼則統一為99999錯誤代碼並響應給用戶
  5. 將錯誤代碼及錯誤信息以json格式響應給用戶

下面就開始我們的異常處理編程

一、可預知異常

  1. 自定義異常類
  1.  
    import com.xuecheng.framework.model.response.ResultCode;
  2.  
    import jdk.nashorn.internal.objects.annotations.Getter;
  3.  
     
  4.  
    /**
  5.  
     * @Author: jiangweifan
  6.  
     * @Date: 2019/3/4 20:06
  7.  
     * @Description:
  8.  
     */
  9.  
    public class CustomException extends RuntimeException {
  10.  
     
  11.  
         private ResultCode resultCode;
  12.  
     
  13.  
         public CustomException(ResultCode resultCode) {
  14.  
             super("錯誤代碼:" + resultCode.code()+" 錯誤信息:" + resultCode.message());
  15.  
             this.resultCode = resultCode;
  16.  
        }
  17.  
     
  18.  
         public ResultCode getResultCode() {
  19.  
             return resultCode;
  20.  
        }
  21.  
    }
  1. 自定義異常拋出類
  1.  
    import com.xuecheng.framework.model.response.ResultCode;
  2.  
     
  3.  
    /**
  4.  
     * @Author: jiangweifan
  5.  
     * @Date: 2019/3/4 20:09
  6.  
     * @Description:
  7.  
     */
  8.  
    public class ExceptionCast {
  9.  
     
  10.  
         public static void cast(ResultCode resultCode, boolean condition) {
  11.  
             if (condition) {
  12.  
                 throw new CustomException(resultCode);
  13.  
            }
  14.  
        }
  15.  
    }
  1. 異常捕獲類
    使用@ControllerAdvice和@ExceptionHandler注解來捕獲指定類型的異常
  1.  
    import com.google.common.collect.ImmutableMap;
  2.  
    import com.xuecheng.framework.model.response.CommonCode;
  3.  
    import com.xuecheng.framework.model.response.ResponseResult;
  4.  
    import com.xuecheng.framework.model.response.ResultCode;
  5.  
    import lombok.extern.slf4j.Slf4j;
  6.  
    import org.springframework.web.bind.annotation.ControllerAdvice;
  7.  
    import org.springframework.web.bind.annotation.ExceptionHandler;
  8.  
    import org.springframework.web.bind.annotation.ResponseBody;
  9.  
     
  10.  
    import java.net.SocketTimeoutException;
  11.  
     
  12.  
    /**
  13.  
     * @Author: jiangweifan
  14.  
     * @Date: 2019/3/4 20:13
  15.  
     * @Description:
  16.  
     */
  17.  
    @ControllerAdvice
  18.  
    @Slf4j
  19.  
    public class ExceptionCatch {
  20.  
     
  21.  
         @ExceptionHandler(CustomException.class)
  22.  
         @ResponseBody
  23.  
         public ResponseResult customException(CustomException e) {
  24.  
            log.error( "catch exception : {} \r\nexception", e.getMessage(), e);
  25.  
            ResponseResult responseResult = new ResponseResult(e.getResultCode());
  26.  
             return responseResult;
  27.  
        }
  28.  
     
  29.  
    }

4.1 定義響應數據格式

  1.  
    import lombok.Data;
  2.  
    import lombok.NoArgsConstructor;
  3.  
    import lombok.ToString;
  4.  
     
  5.  
    /**
  6.  
     * @Author: mrt.
  7.  
     * @Description:
  8.  
     * @Date:Created in 2018/1/24 18:33.
  9.  
     * @Modified By:
  10.  
     */
  11.  
    @Data
  12.  
    @ToString
  13.  
    @NoArgsConstructor
  14.  
    public class ResponseResult implements Response {
  15.  
     
  16.  
         //操作是否成功
  17.  
         boolean success = SUCCESS;
  18.  
     
  19.  
         //操作代碼
  20.  
         int code = SUCCESS_CODE;
  21.  
     
  22.  
         //提示信息
  23.  
        String message;
  24.  
     
  25.  
         public ResponseResult(ResultCode resultCode){
  26.  
             this.success = resultCode.success();
  27.  
             this.code = resultCode.code();
  28.  
             this.message = resultCode.message();
  29.  
        }
  30.  
     
  31.  
         public static ResponseResult SUCCESS(){
  32.  
             return new ResponseResult(CommonCode.SUCCESS);
  33.  
        }
  34.  
         public static ResponseResult FAIL(){
  35.  
             return new ResponseResult(CommonCode.FAIL);
  36.  
        }
  37.  
     
  38.  
    }
  39.  
     
  40.  
    其中Response代碼如下
  41.  
    public interface Response {
  42.  
         public static final boolean SUCCESS = true;
  43.  
         public static final int SUCCESS_CODE = 10000;
  44.  
    }

4.2 定義錯誤代碼(ResultCode上文已給出)

  1.  
    import com.xuecheng.framework.model.response.ResultCode;
  2.  
    import lombok.ToString;
  3.  
     
  4.  
    /**
  5.  
     * Created by mrt on 2018/3/5.
  6.  
     */
  7.  
    @ToString
  8.  
    public enum CmsCode implements ResultCode {
  9.  
        CMS_ADDPAGE_EXISTSNAME( false,24001,"頁面名稱已存在!"),
  10.  
        CMS_GENERATEHTML_DATAURLISNULL( false,24002,"從頁面信息中找不到獲取數據的url!"),
  11.  
        CMS_GENERATEHTML_DATAISNULL( false,24003,"根據頁面的數據url獲取不到數據!"),
  12.  
        CMS_GENERATEHTML_TEMPLATEISNULL( false,24004,"頁面模板為空!"),
  13.  
        CMS_GENERATEHTML_HTMLISNULL( false,24005,"生成的靜態html為空!"),
  14.  
        CMS_GENERATEHTML_SAVEHTMLERROR( false,24005,"保存靜態html出錯!"),
  15.  
        CMS_COURSE_PERVIEWISNULL( false,24007,"預覽頁面為空!"),
  16.  
        CMS_TEMPLATEFILE_ERROR( false,24008,"模板文件需要.ftl后綴!"),
  17.  
        CMS_TEMPLATEFILE_NULL( false,24009,"模板文件為空!"),
  18.  
        CMS_TEMPLATEFILE_EXCEPTION( false,24010,"解析模板文件異常!"),
  19.  
        CMS_TEMPLATEFILE_FAIL( false,24011,"模板文件存儲失敗!"),
  20.  
        CMS_TEMPLATEFILE_DELETE_ERROR( false,24012,"模板文件刪除失敗!"),
  21.  
        CMS_Config_NOTEXISTS( false,24013,"不存在該數據模型!"),
  22.  
        CMS_PAGE_NULL( false,24014,"不存在該頁面數據!"),
  23.  
        CMS_GENERATEHTML_CONTENT_FAIL( false,24014,"獲取頁面模板失敗!");
  24.  
         //操作代碼
  25.  
         boolean success;
  26.  
         //操作代碼
  27.  
         int code;
  28.  
         //提示信息
  29.  
        String message;
  30.  
         private CmsCode(boolean success, int code, String message){
  31.  
             this.success = success;
  32.  
             this.code = code;
  33.  
             this.message = message;
  34.  
        }
  35.  
     
  36.  
         @Override
  37.  
         public boolean success() {
  38.  
             return success;
  39.  
        }
  40.  
     
  41.  
         @Override
  42.  
         public int code() {
  43.  
             return code;
  44.  
        }
  45.  
     
  46.  
         @Override
  47.  
         public String message() {
  48.  
             return message;
  49.  
        }
  50.  
    }
  1. 在方法中拋出異常進行測試
  1.  
    @GetMapping("/list/{page}/{size}")
  2.  
    public QueryResponseResult findList( @PathVariable("page") int page, @PathVariable("size")int size, QueryPageRequest queryPageRequest) {
  3.  
         ExceptionCast.cast(CmsCode.CMS_COURSE_PERVIEWISNULL, queryPageRequest == null);
  4.  
         return pageService.findList(page,size,queryPageRequest);
  5.  
    }

最終方法得到以下結果
 

image

image

二、不可預知異常處理

  1. 在異常捕獲類中添加對Exception類型異常的捕獲,完整代碼如下
  1.  
    import com.google.common.collect.ImmutableMap;
  2.  
    import com.xuecheng.framework.model.response.CommonCode;
  3.  
    import com.xuecheng.framework.model.response.ResponseResult;
  4.  
    import com.xuecheng.framework.model.response.ResultCode;
  5.  
    import lombok.extern.slf4j.Slf4j;
  6.  
    import org.springframework.web.bind.annotation.ControllerAdvice;
  7.  
    import org.springframework.web.bind.annotation.ExceptionHandler;
  8.  
    import org.springframework.web.bind.annotation.ResponseBody;
  9.  
     
  10.  
    import java.net.SocketTimeoutException;
  11.  
     
  12.  
    /**
  13.  
     * @Author: jiangweifan
  14.  
     * @Date: 2019/3/4 20:13
  15.  
     * @Description:
  16.  
     */
  17.  
    @ControllerAdvice
  18.  
    @Slf4j
  19.  
    public class ExceptionCatch {
  20.  
     
  21.  
         //使用EXCEPTIOS存放異常類型 和錯誤代碼的映射,ImmutableMap的特點是已創建就不可變,並且線程安全
  22.  
         private static ImmutableMap<Class<? extends  Throwable>, ResultCode> EXCEPTIOS;
  23.  
         //是由builder來構建一個異常類型和錯誤代碼的映射
  24.  
         private static ImmutableMap.Builder<Class<? extends  Throwable>, ResultCode> builder =
  25.  
                ImmutableMap.builder();
  26.  
     
  27.  
         static {
  28.  
             //初始化基礎類型異常與錯誤代碼的映射
  29.  
            builder.put(NullPointerException.class, CommonCode.NULL);
  30.  
            builder.put(SocketTimeoutException.class, CommonCode.NULL);
  31.  
        }
  32.  
     
  33.  
         @ExceptionHandler(CustomException.class)
  34.  
         @ResponseBody
  35.  
         public ResponseResult customException(CustomException e) {
  36.  
            log.error( "catch exception : {} \r\nexception", e.getMessage(), e);
  37.  
            ResponseResult responseResult =  new ResponseResult(e.getResultCode());
  38.  
             return responseResult;
  39.  
        }
  40.  
     
  41.  
         @ExceptionHandler(Exception.class)
  42.  
         @ResponseBody
  43.  
         public ResponseResult exception(Exception e) {
  44.  
            log.error( "catch exception : {} \r\nexception", e.getMessage(), e);
  45.  
             if (EXCEPTIOS == null) {
  46.  
                EXCEPTIOS = builder.build();
  47.  
            }
  48.  
             final ResultCode resultCode = EXCEPTIOS.get(e.getClass());
  49.  
             if (resultCode != null) {
  50.  
                 return new ResponseResult(resultCode);
  51.  
            }  else {
  52.  
                 return new ResponseResult(CommonCode.SERVER_ERROR);
  53.  
            }
  54.  
     
  55.  
        }
  56.  
     
  57.  
    }
  • 對於不可預知異常的處理,我們采取先從定義好的Map獲取該異常類型對應的錯誤代碼和錯誤信息,若沒有則統一返回CommonCode.SERVER_ERROR
  • 對於CommonCode代碼如下(ResultCode上文已給出)
  1.  
    import lombok.ToString;
  2.  
     
  3.  
    /**
  4.  
     * @Author: mrt.
  5.  
     * @Description:
  6.  
     * @Date:Created in 2018/1/24 18:33.
  7.  
     * @Modified By:
  8.  
     */
  9.  
     
  10.  
    @ToString
  11.  
    public enum CommonCode implements ResultCode{
  12.  
     
  13.  
        SUCCESS( true,10000,"操作成功!"),
  14.  
        FAIL( false,19999,"操作失敗!"),
  15.  
        UNAUTHENTICATED( false,10001,"此操作需要登陸系統!"),
  16.  
        UNAUTHORISE( false,10002,"權限不足,無權操作!"),
  17.  
        NULL( false,10003,"空值異常!"),
  18.  
        TIMEOUT( false, 10004, "服務器連接超時!"),
  19.  
        SERVER_ERROR( false,99999,"抱歉,系統繁忙,請稍后重試!");
  20.  
    //    private static ImmutableMap<Integer, CommonCode> codes ;
  21.  
         //操作是否成功
  22.  
         boolean success;
  23.  
         //操作代碼
  24.  
         int code;
  25.  
         //提示信息
  26.  
        String message;
  27.  
         private CommonCode(boolean success,int code, String message){
  28.  
             this.success = success;
  29.  
             this.code = code;
  30.  
             this.message = message;
  31.  
        }
  32.  
     
  33.  
         @Override
  34.  
         public boolean success() {
  35.  
             return success;
  36.  
        }
  37.  
         @Override
  38.  
         public int code() {
  39.  
             return code;
  40.  
        }
  41.  
     
  42.  
         @Override
  43.  
         public String message() {
  44.  
             return message;
  45.  
        }
  46.  
    }
  1. 方法中進行測試
  1.  
      @GetMapping("/list/{page}/{size}")
  2.  
    public QueryResponseResult findList(@PathVariable("page") int page, @PathVariable("size")int size, QueryPageRequest queryPageRequest) {
  3.  
        int a=  1/0;
  4.  
         return pageService.findList(page,size,queryPageRequest);
  5.  
    }

瀏覽器訪問結果如下:
 

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM