第四节 SpringBoot处理异常(异常设计的思考)
一、基本异常
我常常自己问自己,如果为某个系统设计一套异常体系,我应该怎么开始我的工作。
(1) 首先就是基本异常响应。前端们希望得到的异常响应肯定是有 响应码code 、错误信息message。在JAVA异常体系中,错误原因message字段异常体系已经帮我们定义了,所以不用重复定义。因此,我们可以编写一个基础的基类。构造两个常见的构造函数,来自定义错误响应码code。并提供get方法来让同事获取到错误信息。
package com.zhoutianyu.learnspringboot.exception;
public abstract class AbstractBusinessException extends RuntimeException {
private int code;
public AbstractBusinessException(int code, String message) {
super(message);
this.code = code;
}
public AbstractBusinessException(int code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public int getCode() {
return code;
}
}
(2)我的主管批评过我,要有面向对象的思想。因此,在设计过程中,我积极让定义的类,符合抽象类规则。
接着,我们可以定义一个错误响应对象Response对象。因为在前端同事们不希望的到的是一个错误对象,他们希望得到的是一个封装后的响应(响应包括:响应码code 与 响应信息message)。因此,再编写如下的类,作为异常响应类。
在下面的代码中,因为我们面向接口编程,首先判断异常的分类。使用 instanceof 关键字来判断具体异常来自哪个模块。即使这个异常实现类还没有定义,但是只要是继承的AbstractBusinessException,都能进入我们异常处理。这里就能体现到抽象类的好处。
package com.zhoutianyu.learnspringboot.response;
import com.zhoutianyu.learnspringboot.exception.AbstractBusinessException;
import lombok.Data;
@Data
public class ExceptionResponse {
private int code;
private String message;
public ExceptionResponse(Exception exception) {
if (exception instanceof AbstractBusinessException) {
this.code = ((AbstractBusinessException) exception).getCode();
this.message = exception.getMessage();
return;
}
//todo:add other abstract exception type
//here is default code and message
this.code = 500;
this.message = "Server internal error";
}
}
(3)最后编写一个用于捕获全局异常的类。
package com.zhoutianyu.learnspringboot.exception;
import com.zhoutianyu.learnspringboot.response.ExceptionResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理所有异常
*
* @param exception 异常对象
* @return 异常标准响应 {code : XX , message : XXX}
* @see ExceptionResponse 主要处理逻辑在ExceptionResponse中
*/
@ExceptionHandler(value = Exception.class)
public ExceptionResponse handlerException(Exception exception) {
return new ExceptionResponse(exception);
}
}
主要异常体系的基础框架就搭建好了。处理异常的最终目的就是封装code与message给前端同事。因此,考虑到扩展性(大宇破音)在设计异常的过程中,将某一类异常设计一个基类,即使具体异常实现类此刻没有创建,我们也不用担心。因为在ExceptionResponse的构造函数中,我们能够通过JAVA的多态特性,使用instanceof关键字捕获到这个异常。
所有关于code与message的解析,全部封装在ExceptionResponse的构造函数中。
(4)现在,让我们编写一个具体的异常来继承AbstractBusinessException。假设现在有一个参数校验问题。我们定义一个FieldInvalidException。假如我们规定,参数校验不通过,code返回-1。可以像下面一样设计。
package com.zhoutianyu.learnspringboot.exception;
public class FieldInvalidException extends AbstractBusinessException {
private static final int FIELD_INVALID_CODE = -1;
public FieldInvalidException(String message) {
super(FIELD_INVALID_CODE, message);
}
public FieldInvalidException(String message, Throwable cause) {
super(FIELD_INVALID_CODE, message, cause);
}
}
二、测试
编写一个测试Controller。手动抛出一个参数校验异常。
package com.zhoutianyu.learnspringboot.test;
import com.zhoutianyu.learnspringboot.exception.FieldInvalidException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SomethingController {
@GetMapping(value = "/exception/test")
public void function(String paramType) {
throw new FieldInvalidException("paramType is null");
}
}
启动服务器,访问:http://localhost:8081/study/springboot/exception/test
返回结果:{"code":-1,"message":"paramType is null"} 。符合预期。
三、实战
再编写一个Controller接口,使用一个@Valid注解,使用BindingResult来保存校验结果。最后把校验结果处理字符串,使用异常抛出。此异常将会被全局异常捕获到,最终以ExceptionResponse的形式呈现给前端。
@GetMapping(value = "/exception/test2")
public Computer function(@Valid Computer computer, BindingResult result) {
if (result.hasErrors()) {
List<FieldError> fieldErrors = result.getFieldErrors();
StringBuilder stringBuilder = new StringBuilder();
fieldErrors.forEach(error -> {
stringBuilder.append("字段:").append(error.getField()).
append(",原因:").append(error.getDefaultMessage()).append(";");
}
);
throw new FieldInvalidException(stringBuilder.toString());
}
return computer;
}
@Data
class Computer {
@NotBlank(message = "CPU不能为空")
@Length(max = 5, message = "CPU不能超过{max}个字符")
private String cpu;
@NotNull(message = "内存大小不能为空")
private Long memory;
}
访问:http://localhost:8081/study/springboot/exception/test2?cpu=&memory=
访问:http://localhost:8081/study/springboot/exception/test2?cpu=abcdef&memory=123
四、源码下载
本章节项目源码:点我下载源代码