你们有没有见过一个项目整个controller层每一个方法写一个try..catch来处理异常情况的,例如下面这种:
不但如此,在业务层的操作也全都是 try...catch,异常全靠打印异常堆栈;这是我待过的第一家公司的代码。
后来其实一直想重构一下,做一个全局的异常处理,但是害怕一改全是问题,心生恐惧,犹如下图(直到离职都没敢动手):
下面写一个简洁优雅的全局异常捕获处理的方法:
需要用到两个注解:
- @ControllerAdvice:是Spring3.2提供的新注解,它是一个Controller增强器,可对controller中被 @RequestMapping注解的方法加一些逻辑处理。最常用的就是异常处理
- @ExceptionHandler: 异常处理,Exception的匹配 符合" 就近原则 ", 一次声明,全接口生效
实现
一、首先添加一个业务异常类 ServiceException.java 继承自RuntimeException,用于项目中业务异常。
package com.pn.ebs.util;
public class ServiceException extends RuntimeException {
public ServiceException(String message) {
super(message);
}
}
说明一下:
- 如果希望写一个检查性异常类,则需要继承 Exception 类。
- 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
这里我是需要写一个运行时异常,所以继承自 RuntimeException
二、写一个 ExceptionTranslator.java ,包含两个方法,errorHandler和handleBusinessException ,一个处理全局(未知)异常 ,一个处理业务异常。
package com.pn.ebs.util;
import com.pn.ebs.dto.BaseResp;
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;
@ControllerAdvice
public class ExceptionTranslator {
private final Logger log = LoggerFactory.getLogger(ExceptionTranslator.class);
// 全局异常
@ResponseBody
@ExceptionHandler(value = Exception.class)
public BaseResp errorHandler(Exception ex) {
log.error(ex.getLocalizedMessage(), ex);
return BaseResp.getFailInstance();
}
// 业务异常
@ResponseBody
@ExceptionHandler(value = ServiceException.class)
public BaseResp handleBusinessException(ServiceException ex) {
log.error(ex.getLocalizedMessage(), ex);
return BaseResp.getFailInstance(ex.getMessage());
}
}
其中BaseResp.java 是定义的一个返回结果类型类。
package com.pn.ebs.dto;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import java.io.Serializable;
//保证序列化json的时候,如果是null的对象,key也会消失
@Data
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
@JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.ANY, getterVisibility=JsonAutoDetect.Visibility.NONE)
public class BaseResp implements Serializable {
public final static Integer HTTP_OK = 200;
public final static String MESSAGE_OK = "SUCCESS";
public final static Integer HTTP_ERROR_500 = 500;
public final static String MESSAGE_ERROR = "The system error, please wait.";
public final static Integer HTTP_ERROR_501 = 501;
@JSONField(name = "retCode")
private Integer retCode;
@JSONField(name = "message")
private String message;
@JSONField(name = "data")
private Object data;
public BaseResp() { }
public BaseResp(int retCode, String message, Object data) {
this.retCode = retCode;
this.message = message;
this.data = data;
}
public static BaseResp getSuccessInstance() {
return new BaseResp(HTTP_OK, MESSAGE_OK, null);
}
public static BaseResp getSuccessInstance(Object data) {
return new BaseResp(HTTP_OK, MESSAGE_OK, data);
}
// 系统异常
public static BaseResp getFailInstance() {
return new BaseResp(HTTP_ERROR_500, MESSAGE_ERROR, null);
}
// 业务异常
public static BaseResp getFailInstance(String errorMsg) {
return new BaseResp(HTTP_ERROR_501, errorMsg, null);
}
public Integer getRetCode() {
return retCode;
}
public void setRetCode(Integer retCode) {
this.retCode = retCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
三、使用
- 全局异常:
在controller层写一个Get方法如下:
@GetMapping("/dispatcher/test")
public BaseResp test() {
int s = 3 / 0;
return BaseResp.getSuccessInstance();
}
当调用此接口时肯定会抛出异常,而不会运行到test中最后一行 return BaseResp.getSuccessInstance(); 这一行。此异常 会被ExceptionTranslator 的errorHandler方法捕获,然后 log.error(ex.getLocalizedMessage(), ex); 打印错误日志,最后return BaseResp.getFailInstance(); 返回 错误信息 500, The system error, please wait. 可以打断电调试验证一下过程。
控制台日志:
- 业务异常
改造一下刚刚的test方法,throw 一个异常 ServiceException。
@GetMapping("/dispatcher/test")
public BaseResp test() {
// int s = 3 / 0;
if (true) {
throw new ServiceException("catch example2 exception");
}
return BaseResp.getSuccessInstance();
}
当接口被调用时,会抛出一个 ServiceException,然后被ExceptionTranslator类中 handleBusinessException 方法捕获,然后执行log.error(ex.getLocalizedMessage(), ex); 输出错误日志,之后执行 return BaseResp.getFailInstance(ex.getMessage());返回错误信息:
控制台:
这样 ,每个接口方法的异常都会被 errorHandler 捕获,不需要 在每个接口方法中处理异常了,而业务上已知的异常直接throw new ServiceExption(msg) 就可以了。是不是简洁了不少。