up:: SpringBoot电商项目用户模块注册接口开发之自定义异常类

说明:

(1) 为什么写这篇博客?:在【SpringBoot电商项目用户模块注册接口开发】中,在Service层中遇到了【用户名重复】的情况,然后Service层把这个情况做成了一个异常,并向上抛出;Controller获得了这个异常,继续向上抛出;

那么,就出现了以下问题:【接口返回格式,不符合,接口文档定义的统一规范】,【暴露异常的具体信息,不安全】;

所以,我们要对Controller继续抛的异常,进行统一处理;

(2) 本篇博客的点:

●【GlobalExceptionHandler】的书写;其中用到了【@ControllerAdvice】注解和【@ExceptionHandler(Exception.class)】注解;

● 在实际开发中,无论是【系统异常】还是【自定义异常】,需要细分一下;

(3) 本篇博客参考的博客有:

● 【Springboot全局异常处理GlobalExceptionHandler】,该文的作者是【weixin_33910460】;但,该文亦是转载博客;

● 【SpringBoot处理全局统一异常】,该文的作者是【冬眠的山谷】;

● 【项目使用 GlobalExceptionHandler 自定义异常 一】,该文的作者是【香吧香】;

● 【GlobalExceptionHandle(全局统一异常处理)】,该文的作者是【knock】;


一:创建【GlobalExceptionHandler】去解决上述问题;

1.创建【GlobalExceptionHandler】,去捕获、拦截、并处理Controller抛出的异常;

GlobalExceptionHandler类:

 
     package com.imooc.mall.exception;
 
     import com.imooc.mall.common.ApiRestResponse;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     import org.springframework.web.bind.annotation.ControllerAdvice;
     import org.springframework.web.bind.annotation.ExceptionHandler;
     import org.springframework.web.bind.annotation.ResponseBody;
 
     /**
      * 描述:  处理统一异常的handler
      */
     @ControllerAdvice
     public class GlobalExceptionHandler {
 
         private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
         @ExceptionHandler(Exception.class)
         @ResponseBody
         public Object handleException(Exception e) {
             log.error("Default Exception",e);
             return  ApiRestResponse.error(ImoocMallExceptionEnum.SYSTEM_ERROR);
         }
 
         @ExceptionHandler(ImoocMallException.class)
         @ResponseBody
         public Object handleImoocMallException(ImoocMallException e) {
             log.error("ImoocMallException",e);
             return ApiRestResponse.error(e.getCode(), e.getMessage());
         }
     }
 

说明:

(1) 内容整体说明;

(2) 细节分析:【@ControllerAdvice】注解;

●【@ControllerAdvice】相当于是一个Controller增强器,可对controller中被 @RequestMapping注解的方法(也就是Controller中,那些设置了url,可以接受并处理前端请求的方法),加一些逻辑处理;

● 即,如果Controller中的方法如果满足这两个条件:【这个方法使用了@RequestMapping、 @GetMapping或@PosttMapping注解:也就是这个方法是一个设置了url,可以接受并处理前端请求的方法】+【这个方法抛出了异常】;那么这个Controller中的方法,就会被【使用了@ControllerAdvice注解的,GlobalExceptionHandler类】给捕获;然后,就会根据【GlobalExceptionHandler类】中编写的具体内容,去拦截异常,给这个方法添加一些处理逻辑;(对于,这儿的情况来说,这个处理逻辑就是:提取异常信息,构建API统一返回对象;)

● 即,【@ControllerAdvice】注解的主要作用是:捕获;但是,只捕获,而不去处理可不行;所以,如果想要处理的话,就需要下面介绍的【@ExceptionHandler(Exception.class)】去拦截异常,并对其做相应的处理;

(3) 细节分析:【@ExceptionHandler(Exception.class)】注解;

●【@ExceptionHandler】用来统一处理方法抛出的异常,可以给注解添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常。

如下图所示:

(4) 既然【GlobalExceptionHandler】的目的是,处理异常,把其转化为符合接口返回格式的API统一返回对象的格式;所以,这儿我们也是返回的API统一返回对象;

(5) 又因为,返回格式要求是JSON格式;所以,这个方法处理完后,返回的格式自然要求是JSON格式;所以这儿我们在方法上使用了@ResponseBody注解;

● @ControllerAdvice 捕获 Controller 层抛出的异常,如果添加 @ResponseBody 返回信息则为JSON格式。 ● @RestControllerAdvice 相当于 @ControllerAdvice 与 @ResponseBody 的结合体。

(6) 因为,系统异常我们也需要得到对应API统一返回对象,同样为了快速构建对应的ApiRestResponse对象;系统异常对应的错误信息,我们也放在了枚举类中;

2.效果;

(1)这是原先的情况;

(2)这是现在的情况;

3.创建【GlobalExceptionHandler】后的:一切就会很给力了;

这样以后,以后我们如果在Service层遇到了异常后;Service层大胆的向上抛就行了,Controller接到这个异常后,也继续抛就行了,完全不用怕,因为,我们上面编写的【GlobalExceptionHandler】会很友好的去处理这个异常,把其转化为API统一返回对象,以符合接口返回的格式;


二:进一步,补充说明;

这儿的内容,参考的文章有:

● 【Springboot全局异常处理GlobalExceptionHandler】,该文的作者是【weixin_33910460】;但,该文亦是转载博客;

● 【SpringBoot处理全局统一异常】,该文的作者是【冬眠的山谷】;

● 【项目使用 GlobalExceptionHandler 自定义异常 一】,该文的作者是【香吧香】;

● 【GlobalExceptionHandle(全局统一异常处理)】,该文的作者是【knock】;

通过上面四篇博客,主要给自己以下启发:

启发1:

在实际项目中,对于一个复杂的系统,其业务往往会很复杂,为了应对各种业务情况;我们往往需要定义多种【自定义异常】,来表征不同的业务错误的情况;

启发2:

即使,对于系统异常,我们不能全部用顶级的Exception来统一处理;而是要细分处理,比如细分为【RuntimeException】、【IOException】;(即,有的时候,我们也需要对其分开处理,而不是仅仅只用Exception去处理;)


应对措施:

自然,为了应对【细粒度上的,系统异常】和【多种自定义异常】;我们在【GlobalExceptionHandler】也要分别的去处理;(自然,为了在快速构建这些【细粒度上的 ,系统异常】和【多种自定义异常】的对象,以及,对应上接口返回格式;→ 我们自定义异常的属性也要符合接口返回格式的要求;同时,可以在枚举类来统一管理【细粒度上的 ,系统异常】和【多种自定义异常】这些异常对应的错误信息;)

即,更加符合实际的做法是,如 【GlobalExceptionHandle(全局统一异常处理)】中的代码所示:

 
     package kr.weitao.starter.config;
 
     import kr.weitao.common.exception.CommonException;
     import kr.weitao.common.exception.ServiceException;
     import kr.weitao.starter.model.DataResponse;
     import kr.weitao.starter.model.Status;
     import lombok.extern.slf4j.Slf4j;
     import org.springframework.beans.TypeMismatchException;
     import org.springframework.http.HttpStatus;
     import org.springframework.http.ResponseEntity;
     import org.springframework.http.converter.HttpMessageNotReadableException;
     import org.springframework.validation.FieldError;
     import org.springframework.web.HttpRequestMethodNotSupportedException;
     import org.springframework.web.bind.MethodArgumentNotValidException;
     import org.springframework.web.bind.MissingServletRequestParameterException;
     import org.springframework.web.bind.annotation.ExceptionHandler;
     import org.springframework.web.bind.annotation.RestControllerAdvice;
 
     import java.io.IOException;
     import java.net.SocketTimeoutException;
     import java.util.List;
 
     /**
      * 全局统一异常处理
      */
     @RestControllerAdvice
     @Slf4j
     public class GlobalExceptionHandle {
 
         /**
          * 业务异常
          */
         @ExceptionHandler(value = ServiceException.class)
         public ResponseEntity<DataResponse> handle(ServiceException e) {
             e.printStackTrace();
             DataResponse failure = new DataResponse().setStatus(Status.FAILED).setMsg(e.getMsg());
             return ResponseEntity.ok().body(failure);
         }
 
         @ExceptionHandler(value = CommonException.class)
         public ResponseEntity<DataResponse> handle(CommonException e) {
             e.printStackTrace();
             DataResponse failure = new DataResponse().setStatus(Status.FAILED).setMsg(e.getMessage());
             return ResponseEntity.ok().body(failure);
         }
 
         /**
          * 400错误
          */
         @ExceptionHandler({HttpMessageNotReadableException.class})
         public ResponseEntity<DataResponse>requestNotReadable(HttpMessageNotReadableException ex) {
             ex.printStackTrace();
             DataResponse failure = new  DataResponse().setStatus(Status.FAILED).setMsg(ex.getMessage());
             return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(failure);
         }
 
         /**
          * 400错误
          * @param ex
          * @return
          */
         @ExceptionHandler({TypeMismatchException.class})
         public ResponseEntity<DataResponse> requestTypeMismatch(TypeMismatchException ex) {
             ex.printStackTrace();
             DataResponse failure = new  DataResponse().setStatus(Status.FAILED).setMsg(ex.getMessage());
             return  ResponseEntity.status(HttpStatus.BAD_REQUEST).body(failure);
         }
 
         /**
          * 400错误
          *
          * @param ex
          * @return
          */
         @ExceptionHandler({MissingServletRequestParameterException.class})
         public ResponseEntity<DataResponse> requestMissingServletRequest(MissingServletRequestParameterException ex) {
             ex.printStackTrace();
             DataResponse failure = new DataResponse().setStatus(Status.FAILED).setMsg(ex.getMessage());
             return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(failure);
         }
 
         /**
          * IO异常
          *
          * @param ex
          * @return
          */
         @ExceptionHandler(IOException.class)
         public ResponseEntity<DataResponse iOExceptionHandler(IOException ex) {
             ex.printStackTrace();
             DataResponse failure = new  DataResponse().setStatus(Status.FAILED).setMsg(ex.getMessage());
             return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(failure);
         }
 
         /**
          * 405错误
          *
          * @param ex
          * @return
          */
         @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
         public ResponseEntity<DataResponse>request405(HttpRequestMethodNotSupportedException ex) {
             ex.printStackTrace();
             DataResponse failure = new DataResponse().setStatus(Status.FAILED).setMsg("不支持请求方法");
             return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(failure);
         }
 
 
         /**
          * 超时异常
          *
          * @param ex
          * @return
          */
         @ExceptionHandler(SocketTimeoutException.class)
         public ResponseEntity<DataResponse> SocketTimeoutException(SocketTimeoutException ex) {
             ex.printStackTrace();
             DataResponse failure = new DataResponse().setStatus(Status.FAILED).setMsg("连接超时,请检查网络环境或重试");
             return ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body(failure);
         }
 
         /**
          * 处理入参异常
          */
         @ExceptionHandler(value = MethodArgumentNotValidException.class)
         public ResponseEntity<DataResponse> handleIllegalParamException(MethodArgumentNotValidException e) {
             e.printStackTrace();
             String message = "参数不合法";
             List<FieldError> errors = e.getBindingResult().getFieldErrors();
             if (errors.size()  0) {
                 message = errors.get(0).getDefaultMessage();
             }
             DataResponse failure = new DataResponse().setStatus(Status.FAILED).setMsg(message);
             return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(failure);
         }
 
         /**
          * 其他类型的异常
          * @param e
          * @return
          */
         @ExceptionHandler(value = RuntimeException.class)
         public ResponseEntity<DataResponse> handle(Exception e) {
             e.printStackTrace();
             DataResponse failure = new DataResponse().setStatus(Status.FAILED).setMsg(e.getMessage());
             return  ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(failure);
         }
 
 
     }
 

稍微看下,应该能懂哈,能明白这个意思;