版本需求: SpringBoot 2.0+

(一) 概述

Java项目中处理异常方式无非两种,要么执行trycatch操作,要么执行throw操作(抛给其它对象处理),无论采用哪种方式,其目的是让我们的系统对异常要有反馈。但现在的问题是我们如何让这种反馈代码的编写即简单又直观、友好。

(二) 处理规范

我们在处理异常的过程中通常要遵循一定的设计规范,例如:

  1. 捕获异常时与抛出的异常必须完全匹配,或者捕获异常是抛出异常的父类类型。
  2. 避免直接抛出RuntimeException,更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常(例如CustomeException)。
  3. 捕获异常后必须进行处理(例如记录日志)。如果不想处理它,需要将异常抛给它的调用者。
  4. 最外层的逻辑必须处理异常,将其转化成用户可以理解的内容。
  5. 避免出现重复的代码(Don't Repeat Yourself),即DAY原则。

(三) SpringBoot 工程下的异常处理

(1) 自己try异常处理

// 1) 捕捉自己自己处理
try {
    // 业务处理
} catch(ArithmeticException e){
    // log日志打印异常信息
    log.error("【异常】异常描述>"+e);
    // 处理异常
}

// 2) 捕捉异常 异常信息格式化/转换 但是自己不处理异常 抛出给上层处理
try {
    // 业务处理
} catch(ArithmeticException e){
    throw new CustomException(SysCode.EXE,"追加信息");// CustomeException为自定义异常
}

// 3) 流处理 try-with-resources JDK1.7 (可以省略finally中关闭流操作)
try (MyFileOutputStream file = new MyFileOutputStream("/home/test.gpg");
     MyZipOutputStream o = new MyZipOutputStream(file)
    ) {
    o.read();
} catch (Exception e) {
   // log日志打印异常信息
    log.error("【异常】异常描述>"+e);
    // 处理异常
}

(2) Controller层面异常处理(不常用)

针对可能出问题的Controller,新增注解方法@ExceptionHandler.

@Controller
public class TestController {
 
    @GetMapping("/demo")
    @ResponseBody
    public R<String> demo(){
        int i = 1 / 0;
        return R.ok();
    }
 
    @ExceptionHandler({RuntimeException.class})
    public R<String> REHandler(Exception e){
        // 处理异常
        log.error("【异常】异常描述>"+e);
        return R.exe(e.getMessage());
    }
}

注意事项: 1. 一个Controller下多个@ExceptionHandler上的异常类型不能出现一样的,否则运行时抛异常.

(3) 全局异常处理

1) 全局处理器(可扩展)

@ControllerAdvice
public class GlobalExceptionHandler {

	private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

	/**
	 * @Title: BindExceptionHandler
	 * @Description:处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常,详情继续往下看代码
	 * @param e
	 * @return
	 */
	@ExceptionHandler(BindException.class)
	@ResponseBody
	public Response<String> BindExceptionHandler(BindException e) {
		String message = e.getBindingResult().getAllErrors().stream()
				.map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
		log.error("【异常】参数校验>>>" + e.getMessage());
		return R.fire(SysCode.UNQUALIFIED_PARAMETERS, message);
	}

	/**
	 * @Title: ConstraintViolationExceptionHandler
	 * @Description: 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException
	 * @param e
	 * @return
	 */
	@ExceptionHandler(ConstraintViolationException.class)
	@ResponseBody
	public Response<String> ConstraintViolationExceptionHandler(ConstraintViolationException e) {
		String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage)
				.collect(Collectors.joining());
		log.error("【异常】参数校验>>>" + e.getMessage());
		return R.fire(SysCode.UNQUALIFIED_PARAMETERS, message);
	}

	/**
	 * @Title: MethodArgumentNotValidExceptionHandler
	 * @Description: 数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常。
	 * @param e
	 * @return
	 */
	@ExceptionHandler(MethodArgumentNotValidException.class)
	@ResponseBody
	public Response<String> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
		String message = e.getBindingResult().getAllErrors().stream()
				.map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
		log.error("【异常】参数校验>>>" + e.getMessage());
		return R.fire(SysCode.UNQUALIFIED_PARAMETERS, message);
	}

	/**
	 * @Title: IllegalArgumentException
	 * @Description: 参数校验异常(适用使用Assert断言)
	 * @param e
	 * @return
	 */
	@ExceptionHandler(IllegalArgumentException.class)
	@ResponseBody
	public Response<String> IllegalArgumentExceptionHandler(Exception e) {
		log.error("【异常】参数校验>>>" + getExceptionInfo(e));
		return R.fire(SysCode.UNQUALIFIED_PARAMETERS, e.getMessage());
	}

	/**
	 * @Title: ExceptionHandler
	 * @Description: 统一异常处理
	 * @param e
	 * @return
	 */
	@ExceptionHandler(Exception.class)
	@ResponseBody
	public Response<String> ExceptionHandler(Exception e) {
		log.error("【异常】全局捕捉>>>" + getExceptionInfo(e));
		return R.exc(e.getMessage());
	}

	/**
	 * @Title: getExceptionInfo
	 * @Description: 异常日志详细信息
	 * @param ex
	 * @return
	 */
	private static String getExceptionInfo(Exception ex) {
		ByteArrayOutputStream out = null;
		PrintStream printStream = null;
		String rs = null;
		try {
			out = new ByteArrayOutputStream();
			printStream = new PrintStream(out);
			ex.printStackTrace(printStream);
			rs = new String(out.toByteArray());
		} catch (Exception e) {
			log.error("GlobalExceptionHandler获取异常信息异常", e);
		} finally {
			if(null != printStream) printStream.close();
			if(null != out) {
				try {
					out.close();
				} catch (IOException e) {
					log.error("GlobalExceptionHandler关闭输出流异常", e);
				}
			}
		}
		return rs;
	}

	/**
	 * @Title: customExceptionHandler
	 * @Description: 自定义异常
	 * @param customException
	 * @return
	 */
	@ExceptionHandler(CustomException.class)
	@ResponseBody
	public Response<String> customExceptionHandler(CustomException customException) {
		return R.fire(customException.getCode(), customException.getMessage(), null, null);
	}
}

2) 自定义异常(仅供参考)

public class CustomException extends RuntimeException {
    private static final long serialVersionUID = -3772197636066585649L;
    
    private int code;
    private String message;

    public CustomException(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public CustomException(SysCode code) {
        this.code = code.getCode();
        this.message = code.getMessage();
    }
    //	省略getter/setter
}

3) 统一响应类(仅供参考)

public class R<T> implements Serializable {

    private static final long serialVersionUID = 742186357541909508L;

    /** 响应编码 */
    private int code;

    /** 响应描述 */
    private String detail;

    /** 响应内容 */
    private T data;

    /**
	 * 成功
	 * 
	 * @param data 结果
	 * @return R
	 */
    public static <T> R<T> ok(T data) {
        return new R<T>(SysCode.SUCC, data);
    }


    /**
	 * 异常
	 * 
	 * @return R
	 */
    public static <T> R<T> exc() {
        return new R<T>(SysCode.EXCEPTION, null);
    }

    /**
	 * 异常
	 * 
	 * @param obj 信息体
	 * @return R
	 */
    public static <T> R<T> exc(Object obj) {
        return new R<T>(SysCode.EXCEPTION, obj);
    }

    /**
	 * 失败
	 * 
	 * @return R
	 */
    public static <T> R<T> fail() {
        return new R<T>(SysCode.FAIL, null);
    }

    /**
	 * 失败
	 * 
	 * @param obj 信息体
	 * @return R
	 */
    public static <T> R<T> fail(Object obj) {
        return new R<T>(SysCode.FAIL, obj);
    }

    /**
	 * 失败
	 * 
	 * @param sysCode 返回码枚举
	 * @return R
	 */
    public static <T> R<T> fire(SysCode code) {
        return new R<T>(code);
    }

    /**
	 * 自定义失败(基本不用)
	 * 
	 * @param code   失败编码
	 * @param detail 失败描述
	 * @param data   结果
	 * @param remark 备注
	 * @return R
	 */
    public static <T> R<T> fire(int code, String detail, T data) {
        return new R<T>(code, detail, data);
    }

    /**
	 * 自定义失败
	 *
	 * @param code   失败编码
	 * @param detail 失败描述
	 * @return R
	 */
    public static <T> R<T> fire(int code, String detail) {
        return new R<T>(code, detail);
    }

    public R() {

    }

    public R(SysCode code) {
        this.code = code.getCode();
        this.detail = code.getMsg();
    }

    public R(SysCode code, T data) {
        this.code = code.getCode();
        this.detail = code.getMsg();
        this.data = data;
    }

    public R(int code, String detail) {
        this.code = code;
        this.detail = detail;
    }

    // 省略getter/setter
}

4) 状态码(仅供参考)

public enum SysCode {

	SUCC(0, "成功"),
	FAIL(1, "失败"),
	EXCEPTION(999999, "系统异常");
    
	private int code;

	private String msg;

	//省略getter/setter和构造函数
}

5) 使用

// 1) 系统抛出异常
public void test() throws Exception {
	//do something
}

// 2) 主动抛出自定义异常
public void test2(int a,int b) {
	//do something
    if(a != b){
        throw new CustomException(SysCode.EXCEPTION);
    }
}

// 3) @Valid参数校验异常
public void test3(@Valid @NotBlank(message="用户名不能为空") String username) {
	//do something
}

// 4) Assert断言
String userId = null;
Assert.notNull(userId, "用户信息不能为空");

// 5)其他系统运行时异常或未知异常