SpringBoot异常处理的方法
关于SpringMVC框架统一处理异常,需要自定义方法来处理,该方法:
- 【注解】必须添加
@ExceptionHandler注解; - 【访问权限】应该使用
public权限; - 【返回值类型】与处理请求的方法的设计思路完全相同,即:如果需要转发或重定向,在使用
@Controller注解的情况下,使用String作为返回值类型即可,则返回的字符串就是视图名,如果需要重定向,则返回的字符串必须以redirect:作为前缀并拼接目标路径,如果需要响应正文,在使用@Controller的情况下,当前处理异常的方法还需要添加@ResponseBody,或直接将@Controller替换为@RestController,则方法之前就不必添加@ResponseBody了,如果需要响应的正文是JSON格式的,则需要添加jackson依赖并将返回值设计为自定义类型; - 【方法名称】自定义;
- 【参数列表】必须添加1个异常类型的参数,表示SpringMVC捕获并用于调用当前方法的异常对象,为了保证调用时不出错,该参数类型对于即将抛出的异常来说,“只能大不能小”!另外,不可以像处理请求的方法一样随心所欲的添加参数,但是,可选择性的添加
HttpServletRequest、HttpServletResponse类型的参数;
例如,可以在UserController中添加:
@ExceptionHandler
public R handleException(Throwable e) {
if (e instanceof InviteCodeException) {
return R.failure(2).setMessage("InviteCodeException");
} else if (e instanceof PhoneDuplicateException) {
return R.failure(3).setMessage("PhoneDuplicateException");
} else if (e instanceof InsertException) {
return R.failure(4).setMessage("InsertException");
} else {
return R.failure(998).setMessage("未知错误!");
}
}
关于@ExceptionHandler注解,其源代码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/**
* Exceptions handled by the annotated method. If empty, will default to any
* exceptions listed in the method argument list.
*/
Class<? extends Throwable>[] value() default {};
}
通过以上源代码可以看到:@ExceptionHandler注解存在value属性,是默认属性(在配置时,不需要显式的声明属性名称,可以直接在注解中填写参数值),其类型是“异常类的数组”,表示“添加了注解的方法将处理的异常类型,如果没有配置该属性值,将按照方法的参数列表中的异常进行处理”。简单来说“当需要指定需要处理的某些异常种类时,可以在@EXceptionHandler注解中添加参数,或者将处理异常的方法的参数指定为某种异常,而其它的异常将不会被当前方法所处理!”。
另外,以上处理异常的方法,只作用于当前控制器类!如果需要将处理异常的方法作用于整个项目的任何控制类中的请求,可以:
- 将处理异常的方法定义在控制器类的基类中;
- 将处理异常的方法定义在任意类中,并在该类的声明之前添加
@ControllerAdvice或@RestControllerAdvice注解;
@RestControllerAdvice=@ControllerAdvice+@ResponseBody
所以,可以在controller子包中创建GlobalExceptionHandler类,在类的声明之前添加@RestControllerAdvice,并将处理异常的方法移动到该类中:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
public R handleException(Throwable e) {
if (e instanceof InviteCodeException) {
return R.failure(2).setMessage("InviteCodeException");
} else if (e instanceof PhoneDuplicateException) {
return R.failure(3).setMessage("PhoneDuplicateException");
} else if (e instanceof InsertException) {
return R.failure(4).setMessage("InsertException");
} else {
return R.failure(998).setMessage("未知错误!");
}
}
}
一旦使用了“统一处理异常”的机制,也就意味了同一种异常,无论在哪种情景下被抛出,都会是相同的处理方式,按照以上代码,处理异常时,封装的“错误描述信息”也是完全相同的!这是极为不合理的,关于“错误信息描述”,应该是由抛出异常的那一方进行描述,而不是处理异常的一方进行描述!
所以,应该在“注册”的业务中,抛出异常时进行描述:
@Override
public void regStudent(StudentRegisterDTO studentRegisterDTO) {
// ...
// 原有其它代码
// ...
if (classInfo == null) {
throw new InviteCodeException("注册失败!邀请码错误!");
}
// ...
// 原有其它代码
// ...
if (result != null) {
throw new PhoneDuplicateException("注册失败!手机号码已经被占用!");
}
// ...
// 原有其它代码
// ...
if (rows != 1) {
throw new InsertException("注册失败!服务器忙,请稍后再次尝试!");
}
}
然后,在处理异常时,调用异常对象的getMessage()方法就可以获取以上抛出时封装的异常描述信息:
@ExceptionHandler(ServiceException.class)
public R handleException(Throwable e) {
if (e instanceof InviteCodeException) {
return R.failure(2).setMessage(e.getMessage());
} else if (e instanceof PhoneDuplicateException) {
return R.failure(3).setMessage(e.getMessage());
} else if (e instanceof InsertException) {
return R.failure(4).setMessage(e.getMessage());
} else {
return R.failure(998).setMessage("未知错误!");
}
}
由于以上处理异常时,每次处理时都使用的相同的做法,所以,还可以将R类再次调整,以便于管理代码!
在R类中添加新的方法:
public static R failure(Integer failureState, Throwable e) {
return new R().setState(failureState).setMessage(e.getMessage());
}
另外,关于创建R对象时使用到的状态码(state),其编号应该是有一定规律的,不应该就是1 > 2 > 3 ……这样编号!同时,这些编号应该使用静态常量来表示,以增加程序代码的可阅读性!
最终,R类应该调整为:
package cn.tedu.straw.commons.vo;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 响应到客户端的JSON数据的封装类
*/
@Data
@Accessors(chain = true)
public class R<T> {
/**
* 响应状态码
*/
private Integer state;
/**
* 出错时的错误提示信息
*/
private String message;
/**
* 成功时响应给客户端的数据
*/
private T data;
/**
* 操作成功
*
* @return 状态码已经标记为“成功”的对象
*/
public static R ok() {
return new R().setState(State.SUCCESS);
}
/**
* 操作失败
*
* @param failureState 操作失败的状态码,取值推荐使用#link{R.State}
* @param e 操作失败时抛出并被捕获的异常对象
* @return 已经封装了操作失败的状态码、错误描述信息的对象
* @see R.State
*/
public static R failure(Integer failureState, Throwable e) {
return new R().setState(failureState).setMessage(e.getMessage());
}
/**
* 状态码
*/
public static interface State {
/**
* 成功
*/
int SUCCESS = 2000;
/**
* 邀请码错误
*/
int ERR_INVITE_CODE = 4000;
/**
* 手机号码冲突
*/
int ERR_PHONE_DUPLICATE = 4001;
/**
* 插入数据失败
*/
int ERR_INSERT_FAIL = 4002;
/**
* 未知错误
*/
int ERR_UNKNOWN = 9000;
}
}
关于处理异常的代码:
package cn.tedu.straw.api.controller;
import cn.tedu.straw.api.ex.InsertException;
import cn.tedu.straw.api.ex.InviteCodeException;
import cn.tedu.straw.api.ex.PhoneDuplicateException;
import cn.tedu.straw.api.ex.ServiceException;
import cn.tedu.straw.commons.vo.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
public R handleException(Throwable e) {
if (e instanceof InviteCodeException) {
return R.failure(R.State.ERR_INVITE_CODE, e);
} else if (e instanceof PhoneDuplicateException) {
return R.failure(R.State.ERR_PHONE_DUPLICATE, e);
} else if (e instanceof InsertException) {
return R.failure(R.State.ERR_INSERT_FAIL, e);
} else {
return R.failure(R.State.ERR_UNKNOWN, e);
}
}
}
关于控制器的代码:
package cn.tedu.straw.api.controller;
import cn.tedu.straw.api.dto.StudentRegisterDTO;
import cn.tedu.straw.api.service.IUserService;
import cn.tedu.straw.commons.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author tedu.cn
* @since 2020-08-11
*/
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@Autowired
private IUserService userService;
// http://localhost:8080/api/v1/users/student/register?phone=13100131001&password=1234&inviteCode=JSD1912-876840
@RequestMapping("/student/register")
public R regStudent(StudentRegisterDTO studentRegisterDTO) {
userService.regStudent(studentRegisterDTO);
return R.ok();
}
}
最后,还应该在application.properties中添加:
# 将响应的JSON数据设置为“不为null”时显示,反之,为null的属性将不会出现在JSON数据中
spring.jackson.default-property-inclusion=non_null
本文介绍SpringBoot中统一处理异常的方法,包括使用@ExceptionHandler注解、返回值类型设计、异常对象参数处理及错误信息描述策略。并通过示例展示了如何在全局范围内有效管理异常。
6536

被折叠的 条评论
为什么被折叠?



