1.简介
为什么要用统一的异常处理机制?
在开发过程中,我们经常会遇到异常,不管是DAO、SERVICE、Controller那一层都有可能发生,对于异常处理,一般是try-catch或者直接throw出去不管了,这就导致了代码中四处散落着try-catch的代码,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。
那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。下面将介绍使用SpringBoot统一处理异常的解决和实现过程。
2.全局异常处理的使用
首先,这是我的项目目录
SpringBoot中有一个ControllerAdvice的注解,使用该注解表示开启全局异常的捕获,我们只需在自定义一个方法使用ExceptionHandler注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。
我用是RestControllerAdvice其实就是ControllerAdvice和Responsebody两个注解的作用,跟Controller和RestController的用法一样。
代码如下
@RestControllerAdvice
public class GlobalExceptionHandler {
private final Logger logger=LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = {Exception.class})
public Object exceptionHandler(Exception e ,HttpServletRequest req ,Object handler){
Result result=new Result();
if (e instanceof BusinessException){//业务失败的异常,如“账号或密码错误”
result.setCode(ResultCode.FAIL.code());
result.setMessage(e.getMessage());
logger.info(e.getMessage());
}else if (e instanceof NoHandlerFoundException){//404异常
result.setCode(ResultCode.NOT_FOUND.code());
result.setMessage("接口 [" + req.getRequestURI() + "] 不存在");
// result.setMessage("你找的页面被小偷偷走了,请联系管理员抓小偷");
logger.info(e.getMessage());
}else if (e instanceof HttpRequestMethodNotSupportedException){//请求方式不对
result.setCode(ResultCode.METHOD_NOT_ALLOWED.code());
result.setMessage("接口 [" + req.getRequestURI() + "] 请求方式不对,请换个姿势操作试试!");
logger.info(e.getMessage());
}else if (e instanceof NullPointerException){//空指针异常
result.setCode(ResultCode.FAIL.code());
result.setMessage("接口 [" + req.getRequestURI() + "] 空指针异常");
logger.info(e.getMessage());
}else if (e instanceof ServletException){
result.setCode(ResultCode.FAIL.code());
result.setMessage(e.getMessage());
logger.info(e.getMessage());
}else {
result.setCode(ResultCode.INTERNAL_SERVER_ERROR.code());
result.setMessage("接口 [" + req.getRequestURI() + "] 内部错误");
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
req.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
e.getMessage());
} else {
message = e.getMessage();
}
logger.error(message, e);
}
return result;
}
}
里面统一定义了对一些常见的异常的处理方法 ,404、空指针、请求方式异常、自定义的业务异常,除了这些异常,我们可以用else处理其他类型的异常,返回异常的错误信息。
需要注意的是统一异常对404的处理,404异常需要添加其它配置才能捕抓到,这个在后面说明
自定义的业务异常
/**
* @Description: 服务(业务)异常如“ 账号或密码错误 ”
* @author: zt
* @date: 2020年3月26日
*/
public class BusinessException extends RuntimeException{
public BusinessException() {
}
public BusinessException(String message) {
super(message);
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
}
自定义返回的数据格式
@Data
public class Result<T> {
private int code;
private String message;
private T data;
}
响应结果生成工具
/**
* 响应结果生成工具
*/
public class ResponseUtil {
private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
public static Result genSuccessResult() {
Result<Object> objectResult = new Result<>();
objectResult.setCode(ResultCode.SUCCESS.code());
objectResult.setMessage(DEFAULT_SUCCESS_MESSAGE);
return objectResult;
}
public static <T> Result<T> genSuccessResult(T data) {
Result<T> objectResult = new Result<>();
objectResult.setCode(ResultCode.SUCCESS.code());
objectResult.setMessage(DEFAULT_SUCCESS_MESSAGE);
objectResult.setData(data);
return objectResult;
}
public static Result genFailResult(String message) {
Result objectResult = new Result<>();
objectResult.setCode(ResultCode.FAIL.code());
objectResult.setMessage(message);
return objectResult;
}
}
响应码枚举
/**
* 响应码枚举,参考HTTP状态码的语义
*/
public enum ResultCode {
SUCCESS(200),//成功
FAIL(400),//失败
UNAUTHORIZED(401),//未认证(签名错误)
NOT_FOUND(404),//接口不存在
METHOD_NOT_ALLOWED(405),//请求方式不对
INTERNAL_SERVER_ERROR(500);//服务器内部错误
private final int code;
ResultCode(int code) {
this.code = code;
}
public int code() {
return code;
}
}
下面是为了测试用的controller层和service层的代码
controller
/**
* @Description: 异常测试类
* @author: zt
* @date: 2020年3月26日
*/
@Controller
@RequestMapping("test")
public class TestController {
@Resource
private ITestService testService;
@PostMapping("save")
public boolean insert(String name) {
testService.insert();
return true;
}
@PutMapping("update")
public boolean update() {
System.out.println("开始更新...");
testService.update();
return true;
}
@DeleteMapping("delete")
public boolean delete() {
System.out.println("开始删除...");
testService.delete();
return true;
}
@RequestMapping("view")
public Object view() {
return "hello";
}
}
service层
/**
* @Description: 测试业务接口类
* @author: zt
* @date: 2020年3月26日
*/
public interface ITestService {
void insert();
void update();
void delete();
}
/**
* @Description: 业务接口实现类
* @author: zt
* @date: 2020年3月26日
*/
@Service
public class TestServiceImpl implements ITestService {
@Override
public void insert() {
String name=null;
//如果姓名为空就手动抛出一个自定义的异常!
if(name==null){
throw new BusinessException("用户姓名不能为空!");
// throw new BizException("-1","用户姓名不能为空!");
}
}
@Override
public void update() {
//这里故意造成一个空指针的异常,并且不进行处理
String str=null;
str.equals("111");
}
@Override
public void delete() {
//这里故意造成一个异常,并且不进行处理
Integer.parseInt("abc123");
}
}
测试
自定义业务异常
请求方式异常
空指针异常
4.404异常处理
@ControllerAdvice注解只处理经过Controller的不经过Controller的不进行处理
由此可得出404的错误@ControllerAdvice是不进行处理的。
想要处理404异常还需要进行另外的配置
404异常处理有两种方法
第一种
直接在application.properties配置文件中加入以下配置
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
然后在全局异常处理类GlobalExceptionHandler中添加404异常的处理即可
上面图片的代码就是404异常的处理
测试
总结:因为application.properties添加了以下配置导致静态资源不可使用,所以推荐使用第二种方法
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
第二种
专门写一个类用来处理404异常
Spring boot 在处理异常的时候,500/404默认都会转发到/error,而这个异常的处理类是
ErrorController,所以我们重写一个
ErrorController的子类即可:
需要注意的是处理异常的只能是/error,不能改变这个值,也就是不能改变代码中的ERROR_PATH 常量,否则捕抓不到404异常
/**
* @Description: 继承ErrorController接口重写getErrorPath方法,处理404异常
* @author: zt
* @date: 2020年3月26日
*/
@Controller
public class NotFoundException implements ErrorController {
private static final String ERROR_PATH = "/error";
@Override
public String getErrorPath() {
return ERROR_PATH;
}
@RequestMapping(ERROR_PATH)
public Object error(){
return "404";
}
可以返回404页面,也可以返回404提醒的json数据
自定义返回的404页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>404找不到-temples</h1>
</body>
</html>
测试
源码地址:https://github.com/ZTReborn/my-example.git