统一异常处理@ControllerAdvice
一、异常处理
有异常就必须处理,通常会在方法后面throws异常,或者是在方法内部进行try catch处理。
直接throws Exception
直接throws Exception,抛的异常太过宽泛,最好能抛出准确的异常,比如throws IOException之类。
User getUserById(Integer id) throws IOException,BusinessException,InterruptedException;
如果有多种异常,那么方法后面要throws IOException,InterruptedException又显得冗长。
而且,异常一直向上抛,上层的类还是得处理这些异常。
try catch捕获异常
阿里巴巴的java规范中有一条,"最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。"
也就是说在Controller层,最好不要又throws Exception继续往上抛了。
但是,如果在Controller层进行大量的捕获异常,可能会出现大量的非常多的try catch代码块。
/**
* 以下这种代码写法很丑。
*/
@PostMapping("/id")
public ResultInfo getUserById(HttpServletRequest request) {
String postData = null;
try {
postData = IOUtils.toString(request.getInputStream(), "UTF-8");
} catch (IOException e) {
logger.error("IO异常);
}
JSONObject postJson = JSONObject.parseObject(postData);
logger.info("请求中获取的参数为:" + postJson);
String id=postJson.getString("id");
User user=new User();
try{
user=getUserById(id)
}catch(BusinessException e){
logger.error("根据id查找用户发生异常,id:"+{});
}
//...
}
这么多的try catch很难看,不建议这样写。
二、统一异常处理
@ControllerAdvice配合@ExceptionHandler,可以很方便地统一处理异常。
首先是自定义的业务异常类,如下所示:
/**
* @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);
}
//属性的getter、setter,这里忽略不写。请自行补上。
}
接着是重点,@ControllerAdvice进行统一异常处理。通过 @ExceptionHandler指定对应的异常处理措施。
如下所示:
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理所有业务异常
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
public 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
public ResultInfo handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
log.error(e.getMessage(), e);
ResultInfo response = new ResultInfo(ErrorType.FAIL);
response.setData(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return response;
}
//这个方法可以拦截所有的异常,最好放在最下面。
@ExceptionHandler(Exception.class)
@ResponseBody
public ResultInfo handleException(){
return new ResultInfo(ErrorType.EXCEPTION_FAIL);
}
//handleException()方法也可以写成如下格式 。
// @ExceptionHandler()
// @ResponseBody
// public String handleException(Exception e){
// return "Exception Deal! " + e.getMessage();
// }
}
服务层
有了自定义异常,就可以在服务层抛出,直接在方法内部 throw new BusinessException();。如下示:
@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统一异常处理,那么在控制层就无须再处理了。
三、参数校验@Validated
@ControllerAdvice除了进行统一异常,还能配合@Validated注解进行参数校验。
Controller层的参数通常都需要检验,经常会看到大量的判空,然后返回错误提示,比如"名字不能为空"之类的提示。
有些人可能会像下面这样写:
/**
* 以下的参数校验实在是太繁杂了。不建议这样写。
*/
@PostMapping("register/h5")
@ResponseBody
public BaseResult registerInMiniProgram(HttpServletRequest request,HttpServletResponse response) throws BusinessException, IOException {
//从请求中取出参数的代码,此处忽略,以下是参数校验
//税号为空就返回错误提示"税号不能为空"
if(StringUtils.isEmpty(taxNo)){
return new BaseResult( ErrorType.COMPANY_TAX_NO_NOT_NULL );
}
//企业名字为空就返回错误提示"企业名字不能为空"
if(StringUtils.isEmpty(companyName)){
return new BaseResult( ErrorType.COMPANY_NAME_NOT_NULL );
}
//手机号码为空就返回错误提示"手机号码不能为空"
if(StringUtils.isEmpty(phoneNumber)){
return new BaseResult( ErrorType.PHONENUMBER_IS_NULL );
}
// ...
}
这些冗长的参数校验,可以通过@Validated注解简化。
如下所示,直接在bean对象上面添加注解:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@NotNull(message = "id不能为空")
private Integer id;
@NotBlank(message = "名字不能为空")
private String userName;
@Min(value = 18,message = "年龄不能小于18岁")
private Integer age;
@NotNull(message = "手机号码不能为空")
private String phoneNumber;
}
其中的类上方注解@Data之类是Lombok注解,详情见:https://www.cnblogs.com/expiator/p/10854141.html
而@NotNull,@Min这些是Validation注解。常见的参数校验注解如下:
JSR提供的校验注解:
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@NotBlank 被注释的元素必须不为 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注解的参数校验同样可以进行统一异常处理。
异常类型为MethodArgumentNotValidException.class 。
在统一异常处理类GlobalExceptionHandler 中加入如下代码:
/**
* 处理所有接口数据验证异常。对应的是@Validated注解。
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public 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;
}
控制层
只需要在方法参数前面加上注解@Validated ,如下所示:
@RestController
public class ExceptionController {
@Autowired
private ExceptionService exceptionService;
/**
* 使用了ControllerAdvice进行统一异常处理,就不需要在Controller层再抛异常的。
* @param id
* @return
* @throws BusinessException
*/
@PostMapping("/id")
public ResultInfo getUserById(@Validated @RequestParam("id") 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);
}
}
完整代码:
https://github.com/firefoxer1992/SpringBootDemo/tree/master/controllerAdvice
参考资料:
https://blog.csdn.net/kinginblue/article/details/70186586
https://blog.csdn.net/u013815546/article/details/77248003/
统一异常处理@ControllerAdvice的更多相关文章
- Springboot统一异常处理(@ControllerAdvice)
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind ...
- springboot返回统一接口与统一异常处理
springboot返回统一接口与统一异常处理 编写人员:yls 编写时间:2019-9-19 0001-springboot返回统一接口与统一异常处理 简介 创建统一的返回格式 Result 封装统 ...
- 【统一异常处理】@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常
1.利用springmvc注解对Controller层异常全局处理 对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service ...
- spring boot / cloud (二) 规范响应格式以及统一异常处理
spring boot / cloud (二) 规范响应格式以及统一异常处理 前言 为什么规范响应格式? 我认为,采用预先约定好的数据格式,将返回数据(无论是正常的还是异常的)规范起来,有助于提高团队 ...
- springMVC统一异常处理
Spring MVC处理异常有3种方式: 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver: 实现Spring的异常处理接口HandlerExc ...
- Spring Boot统一异常处理实践
摘要: SpringBoot异常处理. 原文:Spring MVC/Boot 统一异常处理最佳实践 作者:赵俊 前言 在 Web 开发中, 我们经常会需要处理各种异常, 这是一件棘手的事情, 对于很多 ...
- springboot aop + logback + 统一异常处理 打印日志
1.src/resources路径下新建logback.xml 控制台彩色日志打印 info日志和异常日志分不同文件存储 每天自动生成日志 结合myibatis方便日志打印(debug模式) < ...
- springboot统一异常处理类及注解参数为数组的写法
统一异常处理类 package com.wdcloud.categoryserver.common.exception; import com.wdcloud.categoryserver.commo ...
- Spring Boot中Web应用的统一异常处理
我们在做Web应用的时候,请求处理过程中发生错误是非常常见的情况.Spring Boot提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来 ...
随机推荐
- spring boot 集成 sitemesh
一.Sitemesh简介 Sitemesh是由一个基于Web页面布局.装饰及与现存Web应用整合的框架,是一个装饰器.它能帮助我们在由大量页面工程的项目中创建一致的页面布局和外观,如一致的导航条.一致 ...
- mysql DDL数据定义语言
DDL数据定义语言 本节涉及MySQL关键字:create.alter(rename,add,chang,modify,drop).drop.delete.truncate等. -- 创建表:-- 数 ...
- 【故障处理】 DBCA建库报错CRS-2566
[故障处理] DBCA建库报错CRS-2566 PRCR-1071 PRCR-1006 一.1 BLOG文档结构图 一.2 前言部分 一.2.1 导读和注意事项 各位技术爱好者, ...
- Flask入门到放弃(四)—— 数据库
转载请在文章开头附上原文链接地址:https://www.cnblogs.com/Sunzz/p/10979970.html 数据库操作 ORM ORM 全拼Object-Relation Mappi ...
- Linux之RHEL7root密码破解(三)
Linux系列root密码破解第三种方式,利用修改boot分区里的开机启动顺序来修改密码,即我们进入BIOS,修改boot启动顺序为CD-ROM: 接下来按F10保存退出 选择Troubleshoot ...
- HTML+Css+JavaScript知识点汇总
HTML 部分 HTML基础知识 1. HTML简介 HTML(Hypertext Markup Language),超文本标记语言,HTML利用各种标记来标识文档的结构以及标识超链接的信息.它是从S ...
- Kotlin属性委托系统总结与提供委托详解
属性委托总结回顾: 在前三次已经将Kotlin委托相关的知识点进行了完整的学习了,具体博文如下: https://www.cnblogs.com/webor2006/p/11369019.html h ...
- 基于qemu和unicorn的Fuzz技术分析
前言 本文主要介绍如果使用 qemu 和 unicorn 来搜集程序执行的覆盖率信息以及如何把搜集到的覆盖率信息反馈到 fuzzer 中辅助 fuzz 的进行. AFL Fork Server 为了后 ...
- Alpha冲刺(10/10)——追光的人
1.队友信息 队员学号 队员博客 221600219 小墨 https://www.cnblogs.com/hengyumo/ 221600240 真·大能猫 https://www.cnblogs. ...
- Python开发AI应用-国际象棋应用
AI 部分总述 AI在做出决策前经过三个不同的步骤.首先,他找到所有规则允许的棋步(通常在开局时会有20-30种,随后会降低到几种).其次,它生成一个棋步树用来随后决定最佳决策.虽然树的大小随 ...