首先讲一下使用全局异常的好处,不需要定义很多的返回值,当业务出错的时候直接通过异常的返回值方式来返回给前端或者API调用方错误信息。使用全局异常类定义一个业务异常类,所有的业务异常都需要抛出这一个异常,然后通过不同的状态码来区分具体是哪个异常,和对应的描述信息,状态码和描述信息在全局异常类里面通过枚举去定义。如果类型比较多,可以定义多个枚举来区分不同类型的业务异常。
其次罗列一下分别有几种方式去管理全局异常
1.使用@ExceptionHandler注解配合 @ControllerAdvice注解使用实现异常处理
2.实现HandlerExceptionResolver接口来管理异常
3.使用@Around注解抓取JoinPoint(切面)的proceed()方法来环绕管理方法抛出的异常
说一下第一点和第二点的区别,为啥很多文章都推荐第一种方式,是因为第一种方案可以使用@ResponseBody注解方法对特定异常进行处理),而使用HandlerExceptionResolver的话如果是ajax的请求,出现异常就会很尴尬,ajax并不认识ModelAndView,结论就是第二种方案只适合ModelAndView或者重定向,不支持返回json,这块在最后的列子中有说明,HandlerExceptionResolver接口中resolveException方法返回体是ModelAndView。
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4);
}
重点再说下第三种方案,一个方法,只有满足指定某@annotation才进入该切面,只有符合execution指定返回体才进入切面
AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。任何一个增强方法都可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息。我们先来了解一下这两个接口的主要方法:
1)JoinPoint
** java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; **
** Signature getSignature() :获取连接点的方法签名对象; **
java.lang.Object getTarget() :获取连接点所在的目标对象;
java.lang.Object getThis() :获取代理对象本身;
2)ProceedingJoinPoint
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:
java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法;
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。
@Around("@annotation(org.springframework.web.bind.annotation.ResponseBody) && (execution(public java.util.Map *(..)) || "+"execution(public com.*.RetResult *(..))
public Object around(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Class returnType = methodSignature.getReturnType();
try {
Object object = joinPoint.proceed();
return object;
} catch (ShopException e) {
e.printStackTrace();
if (returnType == Map.class) {
Map map = MapUtils.getMap(RespEnum.CLIENT_ERROR, e.getMessage());
return map;
} else {
return new RetResult(RespEnum.OK.getCode() + "", e.getMessage());
}
} catch (Throwable throwable) {
logger.error("system error:{}", throwable);
if (returnType == Map.class) {
Map map = MapUtils.getMap(RespEnum.SERVER_FAIL, "系统异常!");
return map;
} else {
return new RetResult(RespEnum.SERVER_FAIL.getCode() + "", "系统异常!");
}
}
}
ps:记得之前的一篇文章:https://www.jianshu.com/p/3f05fe61a54f
有提过实现Filter,通过Invoker来获取方法的方法名。参数信息等等,是不是和Joinpoint切点有异曲同工的感觉,
那么我们简单罗列下类似的名词,他们有啥共同点,又有啥区别:
Joinpoint:可以去执行目标方法
Invocation:可以获得参数,他说一种Joinpoint 可以被interceptor 进行intercepted
Advice:我们aop的时候需要真正执行的类
Interceptor:是advice的子接口
MethodInterceptor:是Interceptor子接口,一般我们们最后都是吧advice转换成他
- 管理异常码全局工具类
/**
* @Title:
* @Auther: hangyu
* @Date: 2019/3/28
* @Description
* @Version:1.0
*/
public class App {
private static App INSTANCE = null;
private final Logger logger = LoggerFactory.getLogger(App.class);
/**
* 错误码的properites
*/
private PropertiesConfiguration errorCode;
private App() {
try {
errorCode = new PropertiesConfiguration();
errorCode.setEncoding("UTF-8");
errorCode.setFileName("errorCode.properties");
errorCode.load();
} catch (ConfigurationException e) {
logger.error(e.getMessage());
}
}
public static synchronized App getInstance() {
if (null == INSTANCE) {
INSTANCE = new App();
}
return INSTANCE;
}
/**
* 获取错误码的文本信息
*
* @param code
* @return
*/
public String getErrorCode(int code) {
String key = String.valueOf(code);
if (errorCode.containsKey(key)) {
return errorCode.getString(key);
}
return "";
}
}
- errorCode.properties
1=密码输入错误,您还可以输入{0}次
2=密码被锁住
- 全局异常返回内容抽象
/**
* @Title:
* @Auther: hangyu
* @Date: 2019/3/28
* @Description
* @Version:1.0
*/
public class ResultBean implements Serializable {
private static final long serialVersionUID = -4365068809657107866L;
private int code;
private String desc;
private Serializable content;
private Integer totalNo;
public ResultBean() {
}
public ResultBean(int code) {
this.code = code;
this.desc = App.getInstance().getErrorCode(code);
}
public ResultBean(int code, String param) {
this.code = code;
//App.getInstance().getErrorCode(code) 是过去错误码的unicode编码
this.desc = MessageFormat.format(App.getInstance().getErrorCode(code), param);
}
public ResultBean(int code, Serializable content) {
this(code);
this.content = content;
}
@Override
public String toString() {
return "ResultBean{" +
"code=" + code +
", desc='" + desc + '\'' +
", content=" + content +
", totalNo=" + totalNo +
'}';
}
}
- 自定义异常
/**
* @Title:
* @Auther: hangyu
* @Date: 2019/3/28
* @Description
* @Version:1.0
*/
public class ErrorCodeArgException extends Exception {
private int error;
private String request;
private Serializable result;
private String[] args;
public ErrorCodeArgException(int error) {
this.error = error;
}
public ErrorCodeArgException(int error, String... params) {
this.error = error;
this.args = params;
}
public ErrorCodeArgException(int error, String request, String... params) {
this.error = error;
this.request = request;
this.args = params;
}
public ErrorCodeArgException(int error, Serializable result, String... params) {
this.error = error;
this.result = result;
this.args = params;
}
public ErrorCodeArgException(int error, String request, Serializable result, String... params) {
this.error = error;
this.request = request;
this.result = result;
this.args = params;
}
public int getError() {
return error;
}
public void setError(int error) {
this.error = error;
}
public String getRequest() {
return request;
}
public void setRequest(String request) {
this.request = request;
}
public Serializable getResult() {
return result;
}
public void setResult(Serializable result) {
this.result = result;
}
public String[] getArgs() {
return args;
}
public void setArgs(String[] args) {
this.args = args;
}
}
- ErrorCode枚举
/**
* @Title:
* @Auther: hangyu
* @Date: 2019/3/28
* @Description
* @Version:1.0
*/
public enum ErrorCode {
PASSWORD_ERROR(1, "密码输入错误"),
PASSWORD_ERROR_LOCKOUT(2, "密码被锁住");
// 状态码
private int code;
// 描述
private String desc;
ErrorCode(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
- 业务请求
/**
* @Title:
* @Auther: hangyu
* @Date: 2019/3/28
* @Description
* @Version:1.0
*/
@Controller
@RequestMapping("/test")
public class TestController {
private final Logger logger = LoggerFactory.getLogger(TestController.class);
public int MAX_FAIL_PWD_COUNT = 2;
@RequestMapping("/login")
@ResponseBody
public ResultBean login(int count) throws ErrorCodeArgException{
ResultBean resultBean = new ResultBean();
if (count > 2) {
// 账户密码错误3次,锁定账户
throw new ErrorCodeArgException(ErrorCode.PASSWORD_ERROR_LOCKOUT.getCode());
}
if (count <= 2 ){
// 密码输入错误,您还可以输入{0}次
throw new ErrorCodeArgException(ErrorCode.PASSWORD_ERROR.getCode(),
new String[] { String.valueOf(MAX_FAIL_PWD_COUNT - count) });
}
return resultBean;
}
}
- 全局异常管理
/**
* @Title:
* @Auther: hangyu
* @Date: 2019/3/28
* @Description
* @Version:1.0
*/
@ControllerAdvice
public class BaseControllerAdvice {
private final Logger logger = LoggerFactory.getLogger(BaseControllerAdvice.class);
@ExceptionHandler()
@ResponseBody
private ResultBean handleException(Exception e, HttpServletRequest request) {
ResultBean bean = null;
ErrorCodeArgException e1 = (ErrorCodeArgException)e;
bean = new ResultBean(e1.getError(), e1.getArgs()[0]);
logger.error("response is {}",bean.toString());
return bean;
}
}
/**
* @Title:
* @Description:
* @Author:hangyu
* @Since:2019/3/29
* @Version:1.0
*/
public class SimpleMappingExceptionResolver implements HandlerExceptionResolver {
private final static Logger LOGGER =
LoggerFactory.getLogger(SimpleMappingExceptionResolver.class);
/**
*
*
*
* @param request
* @param response
* @param o
* @param exception
* @return
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object o, Exception exception) {
// 判断是否ajax请求
if (!(request.getHeader("accept").contains("application/json")
|| (request.getHeader("X-Requested-With") != null
&& request.getHeader("X-Requested-With").contains("XMLHttpRequest")))) {
// 如果不是ajax,JSP格式返回
// 为安全起见,只有业务异常对前端可见,否则否则统一归为系统异常
Map map = new HashMap();
if (exception instanceof ErrorCodeArgException) {
map.put("errorMsg", exception.getMessage());
} else {
map.put("errorMsg", "系统异常!");
}
LOGGER.error("系统运行异常:", exception);
// 对于非ajax请求,统一跳转到error.jsp页面
ModelAndView modelAndView = new ModelAndView("outException");
modelAndView.addObject("errorInfo", map);
return modelAndView;
} else {
// 如果是ajax请求,JSON格式返回
try {
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
// 为安全起见,只有业务异常对前端可见,否则统一归为系统异常
String responseMsg = null;
if (exception instanceof ErrorCodeArgException) {
responseMsg = exception.getMessage();
}
writer.write(responseMsg);
writer.flush();
writer.close();
} catch (IOException e) {
LOGGER.error("系统运行异常:", e);
}
return null;
}
}
}