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);
}
}
稍微看下,应该能懂哈,能明白这个意思;