这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战
在我们的项目中,对异常的捕捉是十分重要的。所以我们通常会在业务代码中写一堆的try...catch去捕捉异常。但是这样做,十分影响我们代码的可读性,并且也十分的繁琐。我们需要一种统一的方法去帮我们处理这些异常。
知识点
参考:
@ResponseStatus
很多情况下,在处理 Web 请求时抛出的任何未处理的异常都会导致服务器返回 HTTP 500 响应。我们在编写自定义异常时,可以指定HTTP状态码。
@ExceptionHandler - 基于控制器的异常处理
可以在控制器中添加含有@ExceptionHandler注解的方法,这样可以处理在同一控制器中引发的异常。
- 处理没有
@ResponseStatus注解的异常 - 将用户重定向到专用的错误视图
- 建立完全自定义的错误相应
@ControllerAdvice - 全局异常处理
含有@ControllerAdvice注解的类允许异常处理应用于整个应用程序,而不是单个的控制器中,这个类支持以下三种类型的方法。
- 用
@ExceptionHandler注解的方法 - 全局异常处理 - 用
@ModelAttribute注解的模型增强方法(向模型中添加额外的数据)- 全局数据绑定 - 用
@InitBinder注解的Binder初始化方法(用于表单配置)- 全局数据预处理
@ControllerAdvice包含@Component注解,这意味着用该注解的类会被注册成Spring Bean
@RestControllerAdvice
@RestControllerAdvice包含@ControllerAdvice和@ResponseBody
这意味着返回值是Web响应的主体(body)。一般用于返回JSON格式的数据。
在启动时,@RequestMapping和@ExceptionHanlder的基础类方法会检测带有@ControllerAdvice的Bean,并在运行时应用他们全局的@ExceptionHandler方法(来自@ControllerAdvice),全局的方法会在本地方法(来自@Controller)之后应用。相比之下,全局的@ModelAttribute和@InitBinder方法会在本地方法之前应用。
默认情况下,@ControllerAdvice方法适用于所有的Controller,但也可以通过注解的属性将其缩小到Controller的子集。
// 对@RestController注解的类生效
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// 对某个特定的包下的controller生效
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// 对特定的类生效
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
统一返回类型
参考:
现在的许多项目都是采用前后端分离,因此约定相同的返回类型有助于高效地工作。
通常的返回类型包含以下三个信息
- code - 状态码
- message - 状态码对应的简单描述
- data - 数据
当我们能成功获取数据时,状态码起的作用不大,一般只表示成功获取了请求的信息。但当获取数据异常时,我们能根据状态码去告知用户发生了什么事,应该怎么做。
我们封装一个接口,这个接口包含了一些关于状态码的操作。
/**
* 业务状态码封装接口
*/
public interface CommonCode {
int getCode();
String getMsg();
void setMsg(String msg);
}
创建一个枚举类实现该接口,并在此定义一些状态码。
public enum BusinessCodeEnum implements CommonCode {
SUCCESS(20000,"SUCCESS"),
ERROR(50000,"未知错误"),
NULL_POINTER_ERROR(50001,"空指针错误"),
PARAMETER_VALIDATION_ERROR(10001, "参数不合法"),
ERROR_USERNAME_OR_PASSWORD(20001, "用户名或密码错误"),
;
private Integer code;
private String msg;
BusinessCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public int getCode() {
return this.code;
}
@Override
public String getMsg() {
return this.msg;
}
@Override
public void setMsg(String msg) {
this.msg = msg;
}
}
为了能更方便地处理业务异常,我们创建一个业务异常类,它继承了Exception类并且实现了CommonCode接口。我们可以直接通过刚刚定义的状态码去创建业务异常。
import lombok.NoArgsConstructor;
@NoArgsConstructor
public class BusinessException extends Exception implements CommonCode {
private CommonCode commonCode;
/**
* 接受BusinessCodeEnum的传参用于构造业务异常
* @param commonCode
*/
public BusinessException(CommonCode commonCode){
super();
this.commonCode = commonCode;
}
/**
* 接收自定义msg的方式构造业务异常
* @param commonCode
* @param msg
*/
public BusinessException(CommonCode commonCode, String msg){
super();
this.commonCode = commonCode;
this.commonCode.setMsg(msg);
}
@Override
public int getCode() {
return this.commonCode.getCode();
}
@Override
public String getMsg() {
return this.commonCode.getMsg();
}
@Override
public void setMsg(String msg) {
this.commonCode.setMsg(msg);
}
}
创建统一返回的结果类
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class CommonReturnType<T> {
/**
* 状态码
*/
private Integer code;
/**
* 结果信息
*/
private String message;
/**
* 实体对象数据
*/
private T data;
/**
* 通过BusinessCodeEnum实例化结果类
* @param businessCodeEnum
*/
private CommonReturnType(BusinessCodeEnum businessCodeEnum){
this.setCode(businessCodeEnum);
}
/**
* 通过code和message实例化结果类
* @param code 状态码
* @param message 结果信息
*/
private CommonReturnType(Integer code, String message){
this.code = code;
this.message = message;
}
/**
* 成功 - 返回实体类结果对象
* @param data 实体类
* @param <T> 实体类的类型
* @return 带有实体类的结果对象
*/
public static <T> CommonReturnType<T> success(T data){
CommonReturnType<T> returnType = new CommonReturnType<>();
returnType.setCode(BusinessCodeEnum.SUCCESS);
returnType.setData(data);
return returnType;
}
/**
* 错误 - 通过业务错误码直接返回结果
* @param businessCodeEnum 状态码枚举类
* @return 只含状态码信息的结果对象
*/
public static CommonReturnType error(BusinessCodeEnum businessCodeEnum){
return new CommonReturnType(businessCodeEnum);
}
/**
* 通过code和message直接返回结果类
* @param code 状态码
* @param message 结果信息
* @return 只含状态码信息的结果对象
*/
public static CommonReturnType error(Integer code, String message){
return new CommonReturnType(code, message);
}
/**
* 通过BusinessCodeEnum设置code和message
* @param businessCodeEnum 状态码枚举类
*/
public void setCode(BusinessCodeEnum businessCodeEnum){
this.code = businessCodeEnum.getCode();
this.message = businessCodeEnum.getMsg();
}
}
在这个统一的结果类中,我们封装了几种会经常用到的方法。
- 当我们成功拿到数据时,状态码无需特别设置,我们只用填入需要获取的数据即可
- 当我们在捕捉到异常时,需要根据抛出的异常去返回错误信息,只需填入定义好的错误状态码
- 当我们捕捉到业务异常时,根据异常中含有的状态码和信息构造结果类返回
全局异常配置
通过前面的配置,到了这里就很简单了。创建一个全局异常处理类。
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = BusinessException.class)
public CommonReturnType handleBusinessException(BusinessException e){
return CommonReturnType.error(e.getCode(),e.getMsg());
}
@ExceptionHandler(value = NullPointerException.class)
public CommonReturnType handleNullPointerException(){
return CommonReturnType.error(BusinessCodeEnum.NULL_POINTER_ERROR);
}
@ExceptionHandler(value = Exception.class)
public CommonReturnType handleException(){
return CommonReturnType.error(BusinessCodeEnum.ERROR);
}
}
直接抛出异常即可,例如。
public UserDO findById(Integer id) throws BusinessException {
UserDO userDO = userDao.findById(id);
if(userDO == null){
throw new BusinessException(BusinessCodeEnum.ERROR_USERNAME_OR_PASSWORD);
}
return userDO;
}
总结
本文主要讲述了如何创建统一的返回类型以及在Spring Boot中的全局异常处理