SpringBoot中处理校验逻辑的两种方式:Hibernate Validator+全局异常处理

本文主要是介绍SpringBoot中处理校验逻辑的两种方式:Hibernate Validator+全局异常处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最近正在开发一个校园管理系统,需要对请求参数进行校验,比如说非空啊、长度限制啊等等,可选的解决方案有两种:

  • 一种是用 Hibernate Validator 来处理
  • 一种是用全局异常来处理

两种方式,我们一一来实践体验一下。

一、Hibernate Validator

Spring Boot 已经内置了 Hibernate Validator 校验框架,这个可以通过 Spring Boot 官网查看和确认。

第一步,进入 Spring Boot 官网,点击 learn 这个面板,点击参考文档。

第二步,在参考文档页点击「依赖的版本」。

第三步,在依赖版本页就可以查看到所有的依赖了,包括版本号。

PS:如果发现没有起效,可能是依赖版本冲突了,手动把 Hibernate Validator 依赖添加到 pom.xml 文件就可以了。

<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.0.17.Final</version>
</dependency>
<dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>2.0.1.Final</version>
</dependency>

通过 Hibernate Validator 校验框架,我们可以直接在请求参数的字段上加入注解来完成校验。

具体该怎么做呢

第一步,在需要验证的字段上加上 Hibernate Validator 提供的校验注解。

比如说我现在有一个用户名和密码登录的请求参数 LoginForm类:

@Data
@ApiModel(value = "用户·登录" , description = "用户表")
public class LoginForm {@ApiModelProperty(value = "登录名")@NotBlank(message = "登录名不能为空")private String username;@ApiModelProperty(value = "密码")@NotBlank(message = "密码不能为空")private String password;private String verifiCode;private Integer userType;}

就可以通过 @NotBlank 注解来对用户名和密码进行判空校验。除了 @NotBlank 注解,Hibernate Validator 还提供了以下常用注解:

  • @NotNull:被注解的字段不能为 null;
  • @NotEmpty:被注解的字段不能为空;
  • @Min:被注解的字段必须大于等于其value值;
  • @Max:被注解的字段必须小于等于其value值;
  • @Size:被注解的字段必须在其min和max值之间;
  • @Pattern:被注解的字段必须符合所定义的正则表达式;
  • @Email:被注解的字段必须符合邮箱格式。

第二步,在对应的请求接口(SystemController.login())中添加 @Validated 注解,并注入一个 BindingResult 参数。

@ApiOperation("登录的方法")@PostMapping("/login")public Result login(@ApiParam("登录提交信息的form表单")@RequestBody@Validated LoginForm loginForm, HttpServletRequest request , BindingResult result){//loginForm中此时有客户端传进来的    private String username;//    private String password;//    private String verifiCode;//    private Integer userType;// 验证码校验HttpSession session = request.getSession();String sessionVerifiCode = (String)session.getAttribute("verifiCode");String loginVerifiCode = loginForm.getVerifiCode();if("".equals(sessionVerifiCode) || null == sessionVerifiCode){return Result.fail().message("验证码失效,请刷新后重试");}if (!sessionVerifiCode.equalsIgnoreCase(loginVerifiCode)){return Result.fail().message("验证码有误,请小心输入后重试");}// 从session域中移除现有验证码session.removeAttribute("verifiCode");// 分用户类型进行校验// 准备一个map用户存放响应的数据Map<String,Object> map=new LinkedHashMap<>();switch (loginForm.getUserType()){case 1:try {Admin admin=adminService.login(loginForm);if (null != admin) {// 用户的类型和用户id转换成一个密文,以token的名称向客户端反馈map.put("token",JwtHelper.createToken(admin.getId().longValue(), 1));}else{throw new RuntimeException("用户名或者密码有误");}return Result.ok(map);} catch (RuntimeException e) {e.printStackTrace();return Result.fail().message(e.getMessage());}case 2:try {Student student =studentService.login(loginForm);if (null != student) {// 用户的类型和用户id转换成一个密文,以token的名称向客户端反馈map.put("token",JwtHelper.createToken(student.getId().longValue(), 2));}else{throw new RuntimeException("用户名或者密码有误");}return Result.ok(map);} catch (RuntimeException e) {e.printStackTrace();return Result.fail().message(e.getMessage());}case 3:try {Teacher teahcer =teacherService.login(loginForm);if (null != teahcer) {// 用户的类型和用户id转换成一个密文,以token的名称向客户端反馈map.put("token",JwtHelper.createToken(teahcer.getId().longValue(), 3));}else{throw new RuntimeException("用户名或者密码有误");}return Result.ok(map);} catch (RuntimeException e) {e.printStackTrace();return Result.fail().message(e.getMessage());}}return Result.fail().message("查无此用户");}

第三步,为控制层(SystemController)创建一个切面,将通知注入到 BindingResult 对象中,然后再判断是否有校验错误,有错误的话返回校验提示信息,否则放行。

@Aspect
@Component
@Order(2)
@Slf4j
public class BindingResultAspect {@Pointcut("execution(src* com.atguigu.myzhxy.controller.*.*(..))")public void BindingResult() {}@Around("BindingResult()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {Object[] args = joinPoint.getArgs();for (Object arg : args) {if (arg instanceof BindingResult) {BindingResult result = (BindingResult) arg;if (result.hasErrors()) {FieldError fieldError = result.getFieldError();if(fieldError!=null){return Result.validateFailed(fieldError.getDefaultMessage());}else{return Result.validateFailed();}}}}return joinPoint.proceed();}
}

第四步,访问登录接口,用户名和密码都不传入的情况下,就会返回“用户名不能为空”的提示信息。

可以看得出,Hibernate Validator 带来的优势有这些:

  • 验证逻辑与业务逻辑进行了分离,降低了程序耦合度;
  • 统一且规范的验证方式,无需再次编写重复的验证代码。

不过,也带来一些弊端,比如说:

  • 需要在请求接口的方法中注入 BindingResult 对象,而这个对应在方法体中并没有用到
  • 只能校验一些非常简单的逻辑,涉及到数据查询就无能为力了。

二、全局异常处理

使用全局异常处理的优点就是比较灵活,可以处理比较复杂的逻辑校验,在校验失败的时候直接抛出异常,然后进行捕获处理就可以了。

第一步,新建一个自定义异常类 ApiException。

public class ApiException extends RuntimeException {private IErrorCode errorCode;public ApiException(IErrorCode errorCode) {super(errorCode.getMessage());this.errorCode = errorCode;}public ApiException(String message) {super(message);}public ApiException(Throwable cause) {super(cause);}public ApiException(String message, Throwable cause) {super(message, cause);}public IErrorCode getErrorCode() {return errorCode;}
}

第二步,新建一个断言处理类 Asserts,简化抛出 ApiException 的步骤

public class Asserts {public static void fail(String message) {throw new ApiException(message);}public static void fail(IErrorCode errorCode) {throw new ApiException(errorCode);}
}

第三步,新建一全局异常处理类 GlobalExceptionHandler,对异常信息进行解析,并封装到统一的返回对象 ResultObject 中。

@ControllerAdvice
public class GlobalExceptionHandler {@ResponseBody@ExceptionHandler(value = ApiException.class)public ResultObject handle(ApiException e) {if (e.getErrorCode() != null) {return ResultObject.failed(e.getErrorCode());}return ResultObject.failed(e.getMessage());}
}

全局异常处理类用到了两个注解,@ControllerAdvice@ExceptionHandler

@ControllerAdvice 是一个特殊的 @Component(可以通过源码看得到),用于标识一个类,这个类中被以下三种注解标识的方法:@ExceptionHandler@InitBinder@ModelAttribute,将作用于所有@Controller 类的接口上。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
}

@ExceptionHandler 注解的作用就是标识统一异常处理,它可以指定要统一处理的异常类型,比如说我们自定义的 ApiException。

第四步,在需要校验的地方通过 Asserts 类抛出异常 ApiException。还拿用户登录这个接口来说明吧。

该接口需要查询数据库验证密码是否正确,如果密码不正确就抛出校验信息“密码不正确”。

 

  switch (loginForm.getUserType()){case 1:try {Admin admin=adminService.login(loginForm);if (null != admin) {// 用户的类型和用户id转换成一个密文,以token的名称向客户端反馈map.put("token",JwtHelper.createToken(admin.getId().longValue(), 1));}else{Asserts.fail("用户名或者密码有误");}return Result.ok(map);

也可以通过 debug 的形式,体验一下整个工作流程。

三、总结

实际开发中把两者结合在一起用,就可以弥补彼此的短板了,简单校验用 Hibernate Validator,复杂一点的逻辑校验,比如说需要数据库

这篇关于SpringBoot中处理校验逻辑的两种方式:Hibernate Validator+全局异常处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/853236

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听