springboot-web进阶——统一异常处理

Posted ---江北

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot-web进阶——统一异常处理相关的知识,希望对你有一定的参考价值。

补充

  springboot中也是一样的可以对结果进行统一格式的包装,这样也就方便了前台的统一接收处理了;

  1.结果集包装类 

package com.example.demo.bean;

/**
 * 结果包装
 *
 * @author zcc ON 2018/2/9
 **/
public class Result<T> {
    /**
     * 错误代码(可以设定例如500表示错误)
     */
    private Integer code;
    /**
     * 提示信息
     */
    private String msg;
    /**
     * 数据内容
     */
    private T data;

    public Result() {
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

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

    public T getData() {
        return data;
    }

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

 

    可以参考之前SSM中的包装类http://www.cnblogs.com/jiangbei/p/7080955.html

  2.使用包装结果集

@PostMapping(value = "/girls")
    public Result<Girl> addGirl(@Valid Girl girl, BindingResult bindingResult) {
        Result<Girl> result = new Result<>();
        // 表单验证逻辑
        if (bindingResult.hasErrors()) {
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            StringBuffer sb = new StringBuffer();
            for (FieldError fieldError : fieldErrors) {
                sb.append(fieldError.getDefaultMessage());
            }
            result.setCode(500);
            result.setMsg(sb.toString());
            return result;
        }
        result.setCode(200);
        result.setMsg("success");
        result.setData(girlRepository.save(girl));
        return result;
    }

    3。改进结果集

      通过代码观察或者对比SSM中的示例,都发现这样写在代码中是挺傻的,这样,我们提供一个工具类(或者在结果集中直接封装)

/**
 * 结果集的工具类
 *
 * @author zcc ON 2018/2/9
 **/
public class ResultUtils {

    public static Result<Object> success(Object data) {
        Result<Object> result = new Result<>();
        result.setCode(200);
        result.setMsg("success");
        result.setData(data);

        return result;
    }

    public static Result<Object> error(String msg) {
        Result<Object> result = new Result<>();
        result.setCode(500);
        result.setMsg(msg);
        return result;
    }
}

    使用:

 @PostMapping(value = "/girls")
    public Result<Object> addGirl(@Valid Girl girl, BindingResult bindingResult) {
        // 表单验证逻辑
        if (bindingResult.hasErrors()) {
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            StringBuffer sb = new StringBuffer();
            for (FieldError fieldError : fieldErrors) {
                sb.append(fieldError.getDefaultMessage());
            }
            return ResultUtils.error(sb.toString());
        }
        return ResultUtils.success(girlRepository.save(girl));
    }

 

一、概述

  springboot提供了默认的统一异常处理,basicErrorController,这个controller提供了默认了错误处理方法,包括错误跳转的路径和渲染方法。

  因为默认的错误处理方法可能会不适合项目的需要,所以当需要自定义统一错误处理的时候,可以继承basicErrorController。或重新实现basicErrorController.

统一异常处理可配置400,404,500等状态异常错误处理。basicErrorController内部提供了针对accept=**的不同处理方法,可区别是普通调用还是ajax等其他调用。

  缺点:需要重写basicErrorController。并配置覆盖原来springboot的默认配置。当使用EmbeddedServletContainerCustomizer类配置400,404。500的时候,暂时不清楚调用springboot的处理方法。

二、问题提出

  假设需要根据girl的不同age作出不同的动作,controller如下:

 @GetMapping(value = "/girls/getAge/{id}")
    public void getAge(@PathVariable("id") Integer id) {
        // 判断应该交给service
        

  service:

public void getAge(Integer id) {
        Integer age = girlRepository.findOne(id).getAge();
        if (age < 12) {
            // 小学生
        } else {
            // 上初中了
        }
    }

  如果只是简单的判断,不同年龄返回不同信息给前台,那确实可以通过返回一个String来进行

  但是,如果业务比较复杂,需要的不仅仅是返回一个字符串,而是需要其他动作,这个时候就显得有点力不从心了!

三、统一异常处理

   1.基本用法

   controller调用service,抛出异常即可:

 @GetMapping(value = "/girls/getAge/{id}")
    public void getAge(@PathVariable("id") Integer id) throws Exception{
        // 判断应该交给service
        girlService.getAge(id);
    }

  service也是,该抛异常就抛出异常:

public void getAge(Integer id) throws Exception{
        Integer age = girlRepository.findOne(id).getAge();
        if (age < 12) {
            // 小学生
            throw new Exception("小学生!");
        } else {
            // 上初中了
            throw new Exception("初中生!");
        }
    }

  统一异常处理类:

package com.example.demo.handle;

import com.example.demo.bean.Result;
import com.example.demo.utils.ResultUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 异常处理类
 * 添加@ControllerAdvice标记为异常处理类
 * @author zcc ON 2018/2/9
 **/
@ControllerAdvice
public class MyExceptionHandler {

    /**
     * 如果不在这里加@ResponseBody,则需要在类上加
     * @param e 异常
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Result handle(Exception e) {
        return ResultUtils.error(e.getMessage());
    }
}

  2.自定义异常

    写自定义异常还是之前的基础篇的老套路,基本就是几个构造方法的编写,再根据需要加上自己的变量!

 

package com.example.demo.exception;

/**
 * 自定义异常
 * 继承RuntimeException才能使得spring进行异常时的回滚
 * @author zcc ON 2018/2/9
 **/
public class MyException extends RuntimeException{
    private Integer code;

    public MyException(Integer code, String msg) {
        super(msg);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

 

    异常处理类:

package com.example.demo.handle;

import com.example.demo.bean.Result;
import com.example.demo.exception.MyException;
import com.example.demo.utils.ResultUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 异常处理类
 * 添加@ControllerAdvice标记为异常处理类
 * @author zcc ON 2018/2/9
 **/
@ControllerAdvice
public class MyExceptionHandler {

    /**
     * 如果不在这里加@ResponseBody,则需要在类上加
     * @param e 异常
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Result handle(Exception e) {
        // 先判断捕获的是不是我们定义的异常
        if (e instanceof MyException) {
            MyException me = (MyException) e;
            return ResultUtils.exp(me.getCode(), me.getMessage());
        } else {
            return ResultUtils.error("未知错误!");
        }
    }
}

  其他地方需要注意:

    既然MyException继承RuntimeException了,那controller和service就不用抛出异常了

    ResultUtils根据需要微调一些

  统一404、500异常处理

 

  @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResponseData defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        logger.error("", e);
        ResponseData r = new ResponseData();
        r.setMessage(e.getMessage());
        if (e instanceof org.springframework.web.servlet.NoHandlerFoundException) {
             r.setCode(404);
        } else {
             r.setCode(500);
        }

 

 

 

三、使用枚举优化管理

  1.枚举

package com.example.demo.enums;

/**
 * 结果集的枚举管理类
 * 不用给出setter了,因为枚举不会再去set了
 * @author zcc
 **/
public enum ResultEnum {
    UNKNOWN(-1, "未知错误"),
    SUCCESS(200, "success"),
    PRIMARY(100, "小学生"),
    MIDDLE(101, "中学生");

    private Integer code;
    private String msg;

    ResultEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

  更多枚举基础知识,参考:http://www.cnblogs.com/jiangbei/p/7580482.html

 

  2.调整自定义异常的构造方法

/**
 * 自定义异常
 * 继承RuntimeException才能使得spring进行异常时的回滚
 * @author zcc ON 2018/2/9
 **/
public class MyException extends RuntimeException{
    private Integer code;

    public MyException(ResultEnum resultEnum) {
        super(resultEnum.getMsg());
        this.code = resultEnum.getCode();
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

  3.使用自定义枚举统一管理错误代码和错误信息之间的关系

 public void getAge(Integer id) {
        Integer age = girlRepository.findOne(id).getAge();
        if (age < 12) {
            // 小学生
            throw new MyException(ResultEnum.PRIMARY);
        } else {
            // 上初中了
            throw new MyException(ResultEnum.MIDDLE);
        }
    }

 

以上是关于springboot-web进阶——统一异常处理的主要内容,如果未能解决你的问题,请参考以下文章

springboot-web进阶——单元测试

C#进阶系列——WebApi 异常处理解决方案

C#进阶系列——WebApi 异常处理解决方案

SpringBoot进阶

进阶计划

Python进阶08 异常处理