异常处理
有异常就必须处理,通常会在方法后面throws异常,或者是在方法内部进行try catch处理。
直接throws Exception
直接throws Exception,抛的异常太过宽泛,最好能抛出准确的异常,比如throws IOException之类。
如果有多种异常,那么方法后面要throws IOException,InterruptedException又显得冗长。
而且,异常一直向上抛,上层的类还是得处理这些异常。
try catch捕获异常
阿里巴巴的java规范中有一条,"最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。"
如果在Controller层进行大量的捕获异常,那么可能会出现大量的非常丑的try catch代码块。
统一异常处理
@ControllerAdvice配合@ExceptionHandler,可以很方便地统一处理异常。
首先是枚举类的错误类型ErrorType,如下所示:
public enum ErrorType {
SUCCESS("0000", "成功"),
SESSION("0001", "session失效"),
PERMSION("0002","无权限"),
TOKEN("0003","token失效"),
FAIL("0004", "失败"),
EXCEPTION_FAIL("0005","统一异常处理Exception返回错误结果") ,
NAME_IS_NULL("0501","ErrorType:用户名为空") ,
ID_IS_NULL("0502","ErrorType:ID不能为空") ,
USER_IS_NULL("0503" ,"ErrorType:UER为空") ,
;
private String code;
private String msg;
private ErrorType(String code, String msg) {
this.code = code;
this.msg = msg;
}
private ErrorType() {
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
接着是自定义的业务异常类,如下所示:
/**
* @Description: 自定义异常。
* 这里的BusinessException继承于RuntimeException,而非Exception。
* 如果继承的是Exception,那么在服务层还是得进行异常处理。
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String code;
private String msg;
public BusinessException(ErrorType error) {
this.msg=error.getMsg();
this.code = error.getCode();
}
public BusinessException(String code, String msg) {
super(msg);
this.code = code;
}
public BusinessException(String msg) {
super(msg);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
@ControllerAdvice进行统一异常处理。通过 @ExceptionHandler(BusinessException.class)指定对应的异常处理措施。
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理所有业务异常
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
ResultInfo handleBusinessException(BusinessException e){
log.error(e.getMessage());
ResultInfo response = new ResultInfo();
response.setMsg(e.getMsg());
response.setCode(e.getCode());
response.setData(e.getMessage());
return response;
}
/**
* 处理所有接口数据验证异常。对应的是@Validated注解。
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
ResultInfo handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
log.error(e.getMessage(), e);
ResultInfo response = new ResultInfo();
response.setCode(ErrorType.FAIL.getCode());
response.setMsg(ErrorType.FAIL.getMsg());
response.setData(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return response;
}
//这个方法可以拦截所有的异常
@ExceptionHandler(Exception.class)
@ResponseBody
ResultInfo handleException(){
return new ResultInfo(ErrorType.EXCEPTION_FAIL);
}
//handleException()方法也可以写成如下格式 。
// @ExceptionHandler()
// @ResponseBody
// String handleException(Exception e){
// return "Exception Deal! " + e.getMessage();
// }
}
服务层
有了自定义异常,就可以在服务层抛出。如下示:
@Service
public class ExceptionServiceImpl implements ExceptionService {
@Override
@Transactional
public User getUserById(Integer id) {
//实际项目中这里一般都会有Dao层查询user,
// 比如 User user =userDao.getUser(id);
// 此Demo为了方便,忽略不写。直接假设user查询结果为null
User user=null;
if(user==null) {
throw new BusinessException(ErrorType.ID_IS_NULL);
}
return user;
}
@Override
@Transactional
public String getUserName(User user) {
String name=user.getUserName();
if(name==null) {
throw new BusinessException(ErrorType.NAME_IS_NULL);
}
return name;
}
}
有了@ControllerAdvice统一异常处理,那么在控制层就无须在处理了。
参数校验
Controller层的参数通常都需要检验,经常会看到大量的判空,然后返回错误提示,比如"名字不能为空"之类的提示。
这些冗长的参数校验,可以通过@Validated注解简化。
如下所示:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@NotNull(message = "@NotNull错误信息:id不能为空")
private Integer id;
@NotNull(message = "@NotNull错误信息:name不能为空")
//这里的属性名为"name"时,校验没有生效,如果是其他命名,比如userName就可以校验,很诡异的bug。
private String userName;
@Min(value = 18,message = "@NotNull错误信息:age不能小于18岁")
private Integer age;
private String phoneNumber;
}
其中的类上方注解@Data之类是Lombok注解,详情见:https://www.cnblogs.com/expiator/p/10854141.html
而@NotNull,@Min这些是Validation注解。常见的参数校验注解如下:
JSR提供的校验注解:
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
@Validated注解的参数校验同样可以进行统一异常处理。
在统一异常处理类中加入如下代码:
/**
* 处理所有接口数据验证异常。对应的是@Validated注解。
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
ResultInfo handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
log.error(e.getMessage(), e);
ResultInfo response = new ResultInfo();
response.setCode(ErrorType.FAIL.getCode());
response.setMsg(ErrorType.FAIL.getMsg());
response.setData(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return response;
}
控制层
如下所示:
@RestController
public class ExceptionController {
@Autowired
private ExceptionService exceptionService;
/**
* 使用了ControllerAdvice进行统一异常处理,就不需要在Controller层再抛异常的。
* @param id
* @return
* @throws BusinessException
*/
@PostMapping("/id")
public ResultInfo getUserById(@Validated Integer id) {
User user=exceptionService.getUserById(id);
return new ResultInfo(ErrorType.SUCCESS.getCode(),ErrorType.SUCCESS.getMsg(),user);
}
/**
* 使用@Validated校验数据。
* 校验发生异常时,在GlobalExceptionHandler类中通过MethodArgumentNotValidException处理。
* @param user
* @return
* @throws BusinessException
*/
@PostMapping("/name")
public ResultInfo get(@Validated @RequestBody User user) {
String name=exceptionService.getUserName(user);
return new ResultInfo(ErrorType.SUCCESS.getCode(),ErrorType.SUCCESS.getMsg(),name);
}
}