前言

在web应用中,请求处理时,出现异常是非常常见的。所以当应用出现各类异常时,进行异常的捕获或者二次处理(比如sql异常正常是不能外抛)是非常必要的,比如在开发对外api服务时,约定了响应的参数格式,如respCoderespMsg,调用方根据错误码进行自己的业务逻辑。本章节就重点讲解下统一异常和数据校验处理。

springboot中,默认在发送异常时,会跳转值/error请求进行错误的展现,根据不同的Content-Type展现不同的错误结果,如json请求时,直接返回json格式参数。

浏览器访问异常时:

使用postman访问时:

统一异常处理

显然,默认的异常页是对用户或者调用者而言都是不友好的,所以一般上我们都会进行实现自己业务的异常提示信息。

创建全局的统一异常处理类

利用@ControllerAdvice@ExceptionHandler定义一个统一异常处理类

  • @ControllerAdvice:控制器增强,使@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。

  • @ExceptionHandler:异常处理器,此注解的作用是当出现其定义的异常时进行处理的方法

创建异常类:CommonExceptionHandler

@ControllerAdvice
public class CommonExceptionHandler { /**
* 拦截Exception类的异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Map<String,Object> exceptionHandler(Exception e){
Map<String,Object> result = new HashMap<String,Object>();
result.put("respCode", "9999");
result.put("respMsg", e.getMessage());
//正常开发中,可创建一个统一响应实体,如CommonResp
return result;
}
}

多余不同异常(如自定义异常),需要进行不同的异常处理时,可编写多个exceptionHandler方法,注解ExceptionHandler指定处理的异常类,如

    /**
* 拦截 CommonException 的异常
* @param ex
* @return
*/
@ExceptionHandler(CommonException.class)
@ResponseBody
public Map<String,Object> exceptionHandler(CommonException ex){
log.info("CommonException:{}({})",ex.getMsg(), ex.getCode());
Map<String,Object> result = new HashMap<String,Object>();
result.put("respCode", ex.getCode());
result.put("respMsg", ex.getMsg());
return result;
}

由于加入了@ResponseBody,所以返回的是json格式

说明异常已经被拦截了。

可拦截不同的异常,进行不同的异常提示,比如NoHandlerFoundExceptionHttpMediaTypeNotSupportedExceptionAsyncRequestTimeoutException等等,这里就不列举了,读者可自己加入后实际操作下。

对于返回页面时,返回ModelAndView即可,如

@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}

由于工作中都是才有前后端分离开发模式,所以一般上都没有直接返回资源页的需求了,一般上都是返回固定的响应格式,如respCoderespMsgdata,前端通过判断respCode的值进行业务判断,是弹窗还是跳转页面。

数据校验

在web开发时,对于请求参数,一般上都需要进行参数合法性校验的,原先的写法时一个个字段一个个去判断,这种方式太不通用了,所以java的JSR 303: Bean Validation规范就是解决这个问题的。

JSR 303只是个规范,并没有具体的实现,目前通常都是才有hibernate-validator进行统一参数校验。

JSR303定义的校验类型

Constraint 详细信息
@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(value) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

Constraint 详细信息
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内

创建实体类


@Data
@NoArgsConstructor
@AllArgsConstructor
public class DemoReq { @NotBlank(message="code不能为空")
String code; @Length(max=10,message="name长度不能超过10")
String name; }

然后在控制层方法里,加入@Valid即可,这样在访问前,会对请求参数进行检验。

    @GetMapping("/demo/valid")
public String demoValid(@Valid DemoReq req) {
return req.getCode() + "," + req.getName();
}

启动,后访问http://127.0.0.1:8080/demo/valid

加上正确参数后,http://127.0.0.1:8080/demo/valid?code=3&name=s

这样数据的统一校验就完成了,对于其他注解的使用,大家可自行谷歌下,基本上都很简单的,对于已有的注解无法满足校验需要时,也可进行自定义注解的开发,一下简单讲解下,自定义注解的编写

不使用@valid的情况下,也可利用编程的方式编写一个工具类,进行实体参数校验

public class ValidatorUtil {
private static Validator validator = ((HibernateValidatorConfiguration) Validation
.byProvider(HibernateValidator.class).configure()).failFast(true).buildValidatorFactory().getValidator(); /**
* 实体校验
*
* @param obj
* @throws CommonException
*/
public static <T> void validate(T obj) throws CommonException {
Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj, new Class[0]);
if (constraintViolations.size() > 0) {
ConstraintViolation<T> validateInfo = (ConstraintViolation<T>) constraintViolations.iterator().next();
// validateInfo.getMessage() 校验不通过时的信息,即message对应的值
throw new CommonException("0001", validateInfo.getMessage());
}
}
}

使用

    @GetMapping("/demo/valid")
public String demoValid(@Valid DemoReq req) {
//手动校验
ValidatorUtil.validate(req);
return req.getCode() + "," + req.getName();
}

自定义校验注解

自定义注解,主要时实现ConstraintValidator的处理类即可,这里已编写一个校验常量的注解为例:参数值只能为特定的值。

自定义注解

@Documented
//指定注解的处理类
@Constraint(validatedBy = {ConstantValidatorHandler.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface Constant { String message() default "{constraint.default.const.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String value(); }

注解处理类

public class ConstantValidatorHandler implements ConstraintValidator<Constant, String> {

    private String constant;

    @Override
public void initialize(Constant constraintAnnotation) {
//获取设置的字段值
this.constant = constraintAnnotation.value();
} @Override
public boolean isValid(String value, ConstraintValidatorContext context) {
//判断参数是否等于设置的字段值,返回结果
return constant.equals(value);
} }

使用

    @Constant(message = "verson只能为1.0",value="1.0")
String version;

运行:

此时,自定义注解已生效。大家可根据实际需求进行开发。

大家看到在校验不通过时,返回的异常信息是不友好的,此时可利用统一异常处理,对校验异常进行特殊处理,特别说明下,对于异常处理类

共有以下几种情况(被@RequestBody和@RequestParam注解的请求实体,校验异常类是不同的)

@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String,Object> handleBindException(MethodArgumentNotValidException ex) {
FieldError fieldError = ex.getBindingResult().getFieldError();
log.info("参数校验异常:{}({})", fieldError.getDefaultMessage(),fieldError.getField());
Map<String,Object> result = new HashMap<String,Object>();
result.put("respCode", "01002");
result.put("respMsg", fieldError.getDefaultMessage());
return result;
} @ExceptionHandler(BindException.class)
public Map<String,Object> handleBindException(BindException ex) {
//校验 除了 requestbody 注解方式的参数校验 对应的 bindingresult 为 BeanPropertyBindingResult
FieldError fieldError = ex.getBindingResult().getFieldError();
log.info("必填校验异常:{}({})", fieldError.getDefaultMessage(),fieldError.getField());
Map<String,Object> result = new HashMap<String,Object>();
result.put("respCode", "01002");
result.put("respMsg", fieldError.getDefaultMessage());
return result;
}

启动后,提示就友好了

所以统一异常还是很有必要的。

总结

本章节主要是阐述了统一异常处理和数据的合法性校验,同时简单实现了一个自定义的注解类,大家在碰见已有注解无法解决时,可通过自定义的形式进行,当然对于通用而已,利用@Pattern(正则表达式)基本上都是可以实现的。

最后

目前互联网上很多大佬都有springboot系列教程,如有雷同,请多多包涵了。本文是作者在电脑前一字一句敲的,每一步都是实践的。若文中有所错误之处,还望提出,谢谢。

老生常谈

  • 个人QQ:499452441
  • 微信公众号:lqdevOps

个人博客:https://blog.lqdev.cn

完整示例:chapter-8

原文地址:http://blog.lqdev.cn/2018/07/20/springboot/chapter-eight/

SpringBoot | 第八章:统一异常、数据校验处理的更多相关文章

  1. springboot使用validation 插件做数据校验

    不多说废话. 首先,我们需要在入参实体对象中,使用注解,控制 @Datapublic class UpdateShufflingRequest { private String shuffling_l ...

  2. SpringBoot处理全局统一异常

    在后端发生异常或者是请求出错时,前端通常显示如下 Whitelabel Error Page This application has no explicit mapping for /error, ...

  3. 【使用篇二】SpringBoot服务端数据校验(8)

    对于任何一个应用而言,客户端做的数据有效性验证都不是安全有效的,而数据验证又是一个企业级项目架构上最为基础的功能模块,这时候就要求我们在服务端接收到数据的时候也对数据的有效性进行验证.为什么这么说呢? ...

  4. SpringBoot中BeanValidation数据校验与优雅处理详解

    目录 本篇要点 后端参数校验的必要性 不使用Validator的参数处理逻辑 Validator框架提供的便利 SpringBoot自动配置ValidationAutoConfiguration Va ...

  5. SpringBoot入门 (十一) 数据校验

    本文记录学习在SpringBoot中做数据校验. 一 什么是数据校验 数据校验就是在应用程序中,对输入进来得数据做语义分析判断,阻挡不符合规则得数据,放行符合规则得数据,以确保被保存得数据符合我们得数 ...

  6. [SpringBoot] - 配置文件的多种形式及JSR303数据校验

    Springboot配置文件: application.yml   application.properties(自带) yml的格式写起来稍微舒服一点 在application.properties ...

  7. SpringBoot 2 快速整合 | Hibernate Validator 数据校验

    概述 在开发RESTFull API 和普通的表单提交都需要对用户提交的数据进行校验,例如:用户姓名不能为空,年龄必须大于0 等等.这里我们主要说的是后台的校验,在 SpringBoot 中我们可以通 ...

  8. @Valid 数据校验 + 自定义全局异常信息

    关于javax.validation.Validator校验的使用 对于要校验的实体类:其需要校验的字段上需要添加注解 实际例子 使用:首先要拿到 validator的子类 Validator val ...

  9. Springboot数据校验

    SpringBoot中使用了Hibernate-validate校验框架 1.在实体类中添加校验规则 校验规则: @NotBlank: 判断字符串是否为null或者是空串(去掉首尾空格).@NotEm ...

随机推荐

  1. 元素(Element)和结点(Node)的区别(org.w3c.dom)

    1.元素(Element)和结点(Node)的区别, 元素是一个小范围的定义,必须是含有完整信息的结点才是一个元素,例如 - . 但是一个结点不一定是一个元素,而一个元素一定是一个结点. 什么是nod ...

  2. [51nod1247]可能的路径(思维题)

    题意:给定(a,b),(x,y)  ,(a,b)可以通向(a-b,b) (a+b,b) (a,a+b) (a,a-b) 求能否到达(x,y) 解题关键:类似于更相减损,变换过程中gcd是一样的. #i ...

  3. 《精通Spring4.X企业应用开发实战》读后感第七章(创建增强类)

  4. sklearn保存模型

    # View more python tutorials on my Youtube and Youku channel!!! # Youtube video tutorial: https://ww ...

  5. JAVA IO包的整理---------Exception

    EOFException Signals that an end of file or end of stream has been reached unexpectedly during input ...

  6. Flask 入门(第二篇)

    1. 数据库 Flask 没有限定使用哪种数据库,不管是 SQL 还是 NoSQL.如果你不想自己编写负责的 SQL语句的话,你也可以使用 ORM,通过 SQLALchemy 即可实现. 1.1 SQ ...

  7. 1、jquery_属性和选择器

    1.ID选择器 2.attr和val和removeattr 3.attr和removeattr和val <html> <head> <meta charset=" ...

  8. Log4NET的日志框架的使用

    日志信息分类 1.等级由低到高:debug<info<warn<Error<Fatal; 2.区别: debug 级别最低,可以随意的使用于任何觉得有利于在调试时更详细的了解系 ...

  9. 在Android中使用FlatBuffers(下篇)

    本文来自网易云社区. FlatBuffers编码数组 编码数组的过程如下: 先执行 startVector(),这个方法会记录数组的长度,处理元素的对齐,准备足够的空间,并设置nested,用于指示记 ...

  10. 基本图形的绘制(基于skimage)

    图形包括线条.圆形.椭圆形.多边形等.在skimage包中,绘制图形用的是draw模块,不要和绘制图像搞混了. 一  线条 函数调用格式:     skimage.draw.line(r1,c1,r2 ...