zoukankan      html  css  js  c++  java
  • 全局异常处理及自定义异常: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 : {}  exception", 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 : {}  exception", 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 : {}  exception", 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

  • 相关阅读:
    015-面向对象
    017-错误和异常
    019-File
    020-OS
    021-模块
    022-标准库
    数据库目录
    数据库 概念详解
    MySQL 基础
    MySQL 数据库操作
  • 原文地址:https://www.cnblogs.com/wuer888/p/14398008.html
Copyright © 2011-2022 走看看