一、项目中的异常
1、制造异常
屏蔽 IntegralGrade.java
中的 @TableField
注解
2、Swagger中测试
测试列表查询功能,查看结果,发生错误,显示响应失败
二、统一异常处理
目标:我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要进行统一异常处理。
1、创建统一异常处理器
在indi-common
模块中添加exception
包
创建统一异常处理器类UnifiedExceptionHandler.java
package com.indi.common.exception;
@Slf4j
@RestControllerAdvice
public class UnifiedException {
// 捕获所有异常
@ExceptionHandler(value = Exception.class)
public R handleException(Exception e){
log.error(e.getMessage(),e);
return R.error();
}
}
2、service-core添加扫描
ServiceCoreApplication.java
添加 “com.indi.common”
3、测试
返回统一错误结果
三、处理特定异常
如果我们不想显示统一的“服务器内部错误”,需要个性化的显示异常信息,那么需要针对特定的异常做处理
1、添加依赖
在indi-common
的pom中添加jdbc依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
2、添加异常处理方法
在 UnifiedExceptionHandler.java
中添加
@ExceptionHandler(value = BadSqlGrammarException.class)
public R handleBadSqlGrammarException(BadSqlGrammarException e){
log.error(e.getMessage(),e);
return R.setResult(ResponseEnum.BAD_SQL_GRAMMAR_ERROR);
}
3、测试
问题:上面的例子虽然针对特定的异常显示个性化的错误信息,但是你会发现,针对每个不同的异常我们都需要在项目中添加对应的处理方法,并捕获对应的异常对象,可能还要针对这个异常添加额外的依赖。这显然不是最好的方式。
方案:此类异常直接抛出,并且用Exception类捕获就可以了。
4、实体类恢复原样
四、自定义异常
上面两种方案,第一种不灵活,提示过于单调,第二种较为繁琐,有很多异常的话,需要写很多的方法。
目标:使用一个或较少的异常类,可以捕获和显示所有的异常信息。
方案:因此,我们可以创建一个自定义异常类(必须是运行时异常),在程序中抛出这个自定义异常对象,并在统一异常处理器中捕获自定义异常对象
1、创建自定义异常类
indi-common
模块中创建BusinessException.java
package com.indi.common.exception;
@Data
@NoArgsConstructor
public class BusinessException extends RuntimeException {
private Integer code;
private String message;
/**
* @param message 错误消息
*/
public BusinessException(String message) {
this.message = message;
}
/**
* @param message 错误消息
* @param code 错误码
*/
public BusinessException(String message, Integer code) {
this.message = message;
this.code = code;
}
/**
* @param message 错误消息
* @param code 错误码
* @param cause 原始异常对象
*/
public BusinessException(String message, Integer code, Throwable cause) {
super(cause);
this.message = message;
this.code = code;
}
/**
* @param resultCodeEnum 接收枚举类型
*/
public BusinessException(ResponseEnum resultCodeEnum) {
this.message = resultCodeEnum.getMessage();
this.code = resultCodeEnum.getCode();
}
/**
* @param resultCodeEnum 接收枚举类型
* @param cause 原始异常对象
*/
public BusinessException(ResponseEnum resultCodeEnum, Throwable cause) {
super(cause);
this.message = resultCodeEnum.getMessage();
this.code = resultCodeEnum.getCode();
}
}
2、添加异常处理方法
UnifiedExceptionHandler.java
类中添加
@ExceptionHandler(BusinessException.class)
public R handleBusinessException(BusinessException e){
log.error(e.getMessage(),e);
return R.error().setCode(e.getCode()).setMessage(e.getMessage());
}
3、修改Controller
在AdminIntegralGradeController.java
的方法中添加异常处理,业务中需要的位置抛出BusinessException自定义异常。
if (integralGrade.getBorrowAmount()==null) {
throw new BusinessException(ResponseEnum.BORROW_AMOUNT_NULL_ERROR);
}
4、测试
测试
结果
五、异常处理优化
目标:以优雅的 Assert(断言) 方式来校验业务的异常情况,消除 if else
1、什么是断言
package com.indi.srb.core;
import org.junit.Test;
import org.springframework.util.Assert;
public class AssertTests {
@Test
public void test1(){
Object o = null;
if (o == null) {
throw new IllegalArgumentException("参数错误");
}
}
@Test
public void test2(){
Object o = null;
Assert.notNull(o,"参数错误");
}
}
2、自定义断言
用断言的方式封装异常的抛出
在 indi-common
模块创建Assert.java
package com.indi.common.exception;
@Slf4j
public class Assert {
/**
* 断言对象不为空,为空则抛异常
*
* @param obj
* @param responseEnum
*/
public static void notNull(Object obj, ResponseEnum responseEnum){
if (obj == null) {
log.info("obj is null...............................");
throw new BusinessException(responseEnum);
}
}
/**
* 断言对象为空
* 如果对象obj不为空,则抛出异常
* @param object
* @param responseEnum
*/
public static void isNull(Object object, ResponseEnum responseEnum) {
if (object != null) {
log.info("obj is not null......");
throw new BusinessException(responseEnum);
}
}
/**
* 断言表达式为真
* 如果不为真,则抛出异常
*
* @param expression 是否成功
*/
public static void isTrue(boolean expression, ResponseEnum responseEnum) {
if (!expression) {
log.info("fail...............");
throw new BusinessException(responseEnum);
}
}
/**
* 断言两个对象不相等
* 如果相等,则抛出异常
* @param m1
* @param m2
* @param responseEnum
*/
public static void notEquals(Object m1, Object m2, ResponseEnum responseEnum) {
if (m1.equals(m2)) {
log.info("equals...............");
throw new BusinessException(responseEnum);
}
}
/**
* 断言两个对象相等
* 如果不相等,则抛出异常
* @param m1
* @param m2
* @param responseEnum
*/
public static void equals(Object m1, Object m2, ResponseEnum responseEnum) {
if (!m1.equals(m2)) {
log.info("not equals...............");
throw new BusinessException(responseEnum);
}
}
/**
* 断言参数不为空
* 如果为空,则抛出异常
* @param s
* @param responseEnum
*/
public static void notEmpty(String s, ResponseEnum responseEnum) {
if (StringUtils.isEmpty(s)) {
log.info("is empty...............");
throw new BusinessException(responseEnum);
}
}
}
3、修改controller
在AdminIntegralGradeController.java
中用断言替换if else
Assert.notNull(integralGrade.getBorrowAmount(), ResponseEnum.BORROW_AMOUNT_NULL_ERROR);
六、Controller上层异常
1、异常分类
对异常按阶段进行分类,大体可以分成:进入Controller前的异常 和 业务层异常,具体可以参考下图:
2、处理Controller上层异常
UnifiedExceptionHandler.java
中添加
/**
* Controller上一层的相关异常
*/
@ExceptionHandler({
NoHandlerFoundException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
HttpMediaTypeNotAcceptableException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
MissingServletRequestPartException.class,
AsyncRequestTimeoutException.class
})
public R handleServletException(Exception e) {
log.error(e.getMessage(), e);
// SERVLET_ERROR(-102, "servlet请求异常"),
return R.error().setCode(ResponseEnum.SERVLET_ERROR.getCode()).setMessage(ResponseEnum.SERVLET_ERROR.getMessage());
}
3、测试
在save测试用例中输入非法的json参数,则得到下面的结果。我们可以在控制台日志中查看具体的错误原因。前端只需要返回相对简单友好的提示即可。