如何全局管理异常ExceptionHandler、HandlerExceptionResolver、JoinPoint

首先讲一下使用全局异常的好处,不需要定义很多的返回值,当业务出错的时候直接通过异常的返回值方式来返回给前端或者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;
    }

}
上面是方案1实现,下面开始方案2列子
/**
 * @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;
        }
    }
}

你可能感兴趣的:(如何全局管理异常ExceptionHandler、HandlerExceptionResolver、JoinPoint)