Spring 两种全局异常比较---泛型,建造者设计模式

把我两次书写全局异常捕获统一处理返回JSON的经验分享给大家。其中真的还是有点小进步的。里面涉及到泛型和建造者的使用,还是能学到挺多的。

实现的思路是

  1. 全局异常的抓取。
  2. 封装统一返回结果对象

第一种方式:我记得好像是参考阿里巴巴的黄勇同志写出来的。其实很简单通过ControllerAdvice做一Controller的AOP。然后通过拦截对应的Exception可以自定义返回对应的HttpStatus。然后封装Reponse统一返回数据即可。甚至连正常返回都可以封装为一个Exception返回,可以参照下面的StatusSuccess,具体实现比较简单。

别人亲测,这里有一个天大的坑,入门的小伙伴要注意了。事务回滚的异常处理是需要extend RunTimeException 而继承Exception 是没办法做到事务回滚的。

第二种方式:是我参考简书小伙伴的文章写出来的。另外加入了建造者设计模式,个人见解使用简单,代码可读性更好。实现思路更第一种类似,但是在代码上有了比较大的改动。尤其是泛型的使用,和设计模式的使用。

两种方式的比较:

  1. 弱点一:Controller返回的都是Object 。虽然可以满足大部分的业务,但是过了两个月,前端问你这个接口返回的是什么东西。多半你要找到你的dao层才能判断了。而第二种方式则可以避免这种问题。使用了泛型。很容易让你清楚的知道data里面放到是什么

  2. 弱点二:代码可读性更强了。不会在Service里面throw new StatusSuccess()了。这段代码始终让我感觉奇奇怪怪的又说不是是啥。
    第二种实现方式,则是在Controller里面使用Builder设计模式,让代码更加优雅帅气,MVC分层明显,代码可读性强,真的是一个很棒的实现方式。

第二种实现方式,直接上代码了。

1 在controller 的实现效果。代码简洁。可读性👍。

@RestController
@RequestMapping("/app/user")
public class ApiController {

    @RequestMapping("data")
    public RestResult<Object> text() throws Exception {
        throw new Exception();
    }

    @RequestMapping("getData")
    public RestResult<String> getData() throws Exception {
        return RestResultGenrator.build(
                xxxService.getData();
        );
    }
}

2 统一异常抓取。注意看我的RestResultBuild的构建。看起来更加美观。


/**
 * RestExceptionHandler
 *
 * @author zf
 * @date 9/23/16
 */
@ControllerAdvice(annotations = RestController.class)
public class RestExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionHandler.class);

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    private <T> RestResult<T> runtimeExceptionHandler(Exception e){
        LOGGER.error("------->error !" ,e);
        return new RestResultBuilder<T>()
                .setErrorCode(ErrorCode.ERROR)
                .setMessage(ErrorCode.ErrorMessage.ERROR)
                .build();
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    private <T> RestResult<T> illegalParamsExceptionHandler(MethodArgumentNotValidException e) {
        LOGGER.error("---------> invalid request!", e);
        return new RestResultBuilder<T>()
                .setErrorCode(ErrorCode.ERROR_METHOD)
                .setMessage(ErrorCode.ErrorMessage.ERROR_METHOD)
                .build();
    }


    @ExceptionHandler(ExpireException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    private <T> RestResult<T> expireHandler(MethodArgumentNotValidException e) {
        LOGGER.error("---------> invalid expire!", e);
        return new RestResultBuilder<T>()
                .setErrorCode(ErrorCode.EXPIRED)
                .setMessage(ErrorCode.ErrorMessage.EXPIRED)
                .build();
    }

}

3 使用统一泛型处理,增强Controller的可读性。

/**
 * RestResult
 *
 * @author zf
 * @date 9/23/16
 */
public class RestResult <T>{
    private int errorCode;
    private String message;
    private T data;

    private RestResult(){}

    protected RestResult(int errorCode, String message, T data) {
        this.errorCode = errorCode;
        this.message = message;
        this.data = data;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public RestResult setErrorCode(int errorCode) {
        this.errorCode = errorCode;
        return this;
    }

    public String getMessage() {
        return message;
    }

    public RestResult<T> setMessage(String message) {
        this.message = message;
        return this;
    }

    public T getData() {
        return data;
    }

    public RestResult<T> setData(T data) {
        this.data = data;
        return this;
    }
}

4 使用builder设计模式构建统一返回对象。

/**
 * RestResultBuilder
 *
 * @author zf
 * @date 9/23/16
 */
public class RestResultBuilder<T> {

    private int errorCode = ErrorCode.VALID;

    private String message =ErrorCode.ErrorMessage.VALID;;

    private T data ;

    protected RestResultBuilder<T> setErrorCode(int errorCode){
        this.errorCode = errorCode;
        return this;
    }

    public RestResultBuilder<T> setMessage(String message){
        this.message = message;
        return this;
    }

    public RestResultBuilder<T> setData(T data){
        this.data = data;
        return this;
    }

    public RestResult<T> build(){
        return new RestResult<T>(errorCode,message,data);
    }


}

5 我是为了不想写new三个字母,所以写了第五个类,Genrator。

public class RestResultGenrator {

    public static <T> RestResult<T> build(){
        return build(null);
    }

    public static <T> RestResult<T> build(T t){
        return new RestResultBuilder<T>().setData(t).build();
    }
}

下面是第一种实现方式。

  1. 全局异常抓取类
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
    Logger logger = Logger.getGlobal();

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Response handleHttpMessageNotReadableException(
            HttpMessageNotReadableException e) {
        logger.info("参数解析失败" + e);
        return new Response().failure("could_not_read_json");
    }

    /**
     * 405 - Method Not Allowed
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Response handleHttpRequestMethodNotSupportedException(
            HttpRequestMethodNotSupportedException e) {
        logger.info("不支持当前请求方法" + e);
        return new Response().failure("request_method_not_supported");
    }

    /**
     * 415 - Unsupported Media Type
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public Response handleHttpMediaTypeNotSupportedException(Exception e) {
        logger.info("不支持当前媒体类型" + e);
        return new Response().failure("content_type_not_supported");
    }

    /**
     * 500 - Internal Server Error
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(Exception.class)
    public Response handleException(Exception e) {
        e.printStackTrace();
        return new Response().failure(e.getMessage());
    }

    /**
     * - Internal Server Error
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(StatusSuccess.class)
    public Response handleSuccessStatus(StatusSuccess e) {
        logger.info("StatusSuccess");
        return new Response(Response.SUCCESS_CODE, e.getMessage(), e.getData());
    }

    /**
     * 500 - Internal Server Error
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ExpireException.class)
    public Response handleExipreStatus(Exception e) {
        logger.info("内部错误");
        return new Response().timeOut(e.getMessage());
    }

    /**
     * 500 - 内部错误
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(IllegalArgumentException.class)
    public Response parameterException(IllegalArgumentException e) {
        logger.info("非法参数" + e.toString());
        return new Response().failure(e.getMessage());
    }

    /**
     * 500 - Internal Server Error
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ShopStatusException.class)
    public Response errorShopStatus(Exception e) {
        logger.info("店铺状态异常");
        return new Response().shopStatusException(e.getMessage());
    }

    /**
     * 订单状态异常拦截器
     * 
     * @author yangyanchao
     * @date 2016年8月16日
     * @param e
     * @return
     */
    public Response orderStatusException(Exception e) {
        return new Response().shopStatusException(e.getMessage());
    }

}
  1. 统一返回实体类
/**
 * Response
 * 
 * @author zf
 * @date 16/3/21
 */
public class Response {

    private static final String OK = "success";
        private static final String ERROR = "failure";
    private static final String TIMEOUT = "expired";

    public static final int SUCCESS_CODE = 1;
    public static final int FAIL_CODE = 0;
    public static final int TOKEN_INVALID = -1;
    public static final int SHOP_ABNORMAL = -2;
    

    @JsonView(BaseView.BaseResponse.class)
    private int status;

    @JsonView(BaseView.BaseResponse.class)
    private String msg;

    @JsonView(BaseView.BaseResponse.class)
    private Object data;

    public Response success() {
        this.status = SUCCESS_CODE;
        this.msg = OK;
        return this;
    }

    public Response success(Object data) {
        this.status = SUCCESS_CODE;
        this.msg = OK;
        this.data = data;
        return this;
    }

    public Response failure() {
        this.status = FAIL_CODE;
        this.msg = ERROR;
        return this;
    }

    public Response() {
    }

    public Response(int status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public Response expireException(String msg) {
        this.status = TOKEN_INVALID;
        this.msg = msg;
        return this;
    }

    public Response timeOut(String msg) {
        this.status = TOKEN_INVALID;
        this.msg = msg;
        return this;
    }
    
    public Response shopStatusException(String msg) {
        this.status = SHOP_ABNORMAL;
        this.msg = msg;
        return this;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Response failure(String message) {
        this.status = FAIL_CODE;
        this.msg = message;
        return this;
    }

    public Object getData() {
        return data;
    }

}

3.service 使用例子

   /**
     * 保存用户
     *
     * @param principle
     *            用户信息
     * @param uadId
     *            用户地址id
     * @return 保存
     */
    public void save(Principle principle, Integer uadId) throws NotSamePeopleException, StatusSuccess {
        AsUserAddress userAddress = asUserAddressMapper.selectByPrimaryKey(uadId);
        if (userAddress.getUserId().equals(principle.getUserId())) {
            principle.setAddressId(uadId);
            AsUser u = new AsUser();
            u.setUserId(principle.getUserId());
            u.setAddressId(uadId);
            asUsersMapper.updateByPrimaryKeySelective(u);
            throw new StatusSuccess();
        } else {
            throw new NotSamePeopleException();
        }

    }

3 . 正确返回的异常处理,注意看这里是封装了data 的。

/**
 * OrderStatusException
 *
 * @author zf
 * @date 16/7/14
 */
public class StatusSuccess extends Exception{
    private static String msg = "操作成功";
    private Object data;
    public StatusSuccess(){
        super(msg);
    }

    public StatusSuccess(String msg) {
        super(msg);
    }

    public StatusSuccess(Object data) {
        super(msg);
        this.data = data;
    }

    public StatusSuccess(String message, Object data) {
        super(message);
        this.data = data;
    }

    public static String getMsg() {
        return msg;
    }

    public static void setMsg(String msg) {
        StatusSuccess.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

最后用postMan得出最终相同的结果。

Paste_Image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容