SpringBoot 项目中整合数据校验框架 hibernate-validator

简介: SpringBoot 项目中整合数据校验框架 hibernate-validator

SpringBoot 项目中整合数据校验框架 hibernate-validator

1. hibernate-validator 介绍

早期的网站,用户输入一个邮箱地址,需要将邮箱地址发送到服务端,服务端进行校验,校验成功后再给前端一个响应。自从有了 JavaScript 后,校验工作可以放在前端去执行。那么为什么还需要服务端校验呢?因为前端传来的数据不可信,前端很容易获取到后端的接口,如果有人直接调用接口,就可能会出现非法数据,因此服务端也要数据校验。


总的来说:


前端校验,提高用户体验

后端校验,保证数据安全可靠

校验参数基本上是一个体力活,而且冗余代码繁多,也影响代码的可读性,我们需要一个比较优雅的方式来解决这个问题。Hibernate Validator 框架刚好解决了这个问题,可以很优雅地实现参数的校验,让业务代码和校验逻辑分开,不再编写重复的校验逻辑。


hibernate-validator 的优势:


验证逻辑与业务逻辑之间进行了分离,降低了程序耦合度

统一且规范的验证方式,无需再次编写重复的验证代码

hibernate-validator 的 maven 坐标:

<dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.18.Final</version>
</dependency>

hibernate-validator 常用注解及对应描述如下表所示:

注解 描述
@AssertTrue 用于boolean字段,该字段只能为true
@AssertFalse 用于boolean字段,该字段只能为false
@CreditCardNumber 对信用卡号进行一个大致的验证
@DecimalMax 只能小于或等于该值
@DecimalMin 只能大于或等于该值
@Email 检查是否是一个有效的email地址
@Future 检查该字段的日期是否是属于将来的日期
@Length(min=,max=) 检查所属的字段的长度是否在min和max之间,只能用于字符串
@Max 该字段的值只能小于或等于该值
@Min 该字段的值只能大于或等于该值
@NotNull 该字段的值不能为null
@NotBlank 不能为空,检查时会将空格忽略
@NotEmpty 不能为空,这里的空是指空字符串
@Pattern(regex=) 被注释的元素必须符合指定的正则表达式
@URL(protocol=,host,port) 检查是否是一个有效的URL,如果提供了protocol,host等,则该URL还需满足提供的条件

2. hibernate-validator 使用

简单了解 hibernate-validator 数据校验框架之后,下面便开始着手在 SpringBoot 项目中体验一下它的使用了。

工程的目录结构如下图所示

2e718f6de6d94987ae2f2182c528d9c8.png

第一步,创建 maven 工程 hibernate-validator_demo 并配置 pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hzz</groupId>
    <artifactId>hibernate-validator_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <!--
    spring-boot-starter-web中已经依赖了hibernate-validator,无需单独导入依赖
    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

第二步,创建实体类 User

package com.hzz.entity;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;
@Data
public class User {
    @NotNull(message = "用户id不能为空")
    private Integer id;
    @NotEmpty(message = "用户名不能为空")
    @Length(max = 50, message = "用户名长度不能超过50")
    private String username;
    @Max(value = 80,message = "年龄最大为80")
    @Min(value = 18,message = "年龄最小为18")
    private int age;
    @Pattern(regexp = "[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$",
            message = "邮箱格式不正确")
    private String email;
}

第三步,创建 UserController

package com.hzz.entity;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;
@Data
public class User {
    @NotNull(message = "用户id不能为空")
    private Integer id;
    @NotEmpty(message = "用户名不能为空")
    @Length(max = 50, message = "用户名长度不能超过50")
    private String username;
    @Max(value = 80,message = "年龄最大为80")
    @Min(value = 18,message = "年龄最小为18")
    private int age;
    @Pattern(regexp = "[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$",
            message = "邮箱格式不正确")
    private String email;
}

第四步,创建 application.yml

server:
  port: 9100

第五步,创建启动类 HibernateValidatorApp,并启动项目

package com.hzz;
import com.hzz.config.EnableFormValidator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableFormValidator
public class HibernateValidatorApp {
    public static void main(String[] args) {
        SpringApplication.run(HibernateValidatorApp.class, args);
    }
}

在浏览器中访问地址 http://localhost:9100/user/save2e718f6de6d94987ae2f2182c528d9c8.png

观察后台日志信息,打印出现如下信息

2022-07-05 14:48:03.343  WARN 37816 --- [nio-9100-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors
Field error in object 'user' on field 'id': rejected value [null]; codes [NotNull.user.id,NotNull.id,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.id,id]; arguments []; default message [id]]; default message [用户id不能为空]
Field error in object 'user' on field 'username': rejected value [null]; codes [NotEmpty.user.username,NotEmpty.username,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.username,username]; arguments []; default message [username]]; default message [用户名不能为空]
Field error in object 'user' on field 'age': rejected value [0]; codes [Min.user.age,Min.age,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age],18]; default message [年龄最小为18]]

为了能够在前端页面给用户展示友好的效果,做出进一步改变。

第六步,创建全局异常处理类,在前端给出友好的异常提示信息

package com.hzz.config;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
/**
 * 全局异常处理
 */
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
public class ExceptionConfiguration {
    //异常处理的方法
    @ExceptionHandler({ConstraintViolationException.class, BindException.class})
    public String exceptionHandler(Exception ex, HttpServletRequest request) {
      ex.printStackTrace();
        String msg = "";
        if (ex instanceof ConstraintViolationException) {
            ConstraintViolationException constraintViolationException = (ConstraintViolationException) ex;
            ConstraintViolation<?> next = constraintViolationException.getConstraintViolations().iterator().next();
            msg = next.getMessage();
        } else if (ex instanceof BindException) {
            BindException bindException = (BindException) ex;
            msg = bindException.getBindingResult().getFieldError().getDefaultMessage();
        }
        return msg;
    }
}

重启项目并访问地址 http://localhost:9100/user/save,前端效果如下所示

2e718f6de6d94987ae2f2182c528d9c8.png

控制台的打印信息如下:

org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors
Field error in object 'user' on field 'username': rejected value [null]; codes [NotEmpty.user.username,NotEmpty.username,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.username,username]; arguments []; default message [username]]; default message [用户名不能为空]
Field error in object 'user' on field 'id': rejected value [null]; codes [NotNull.user.id,NotNull.id,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.id,id]; arguments []; default message [id]]; default message [用户id不能为空]
Field error in object 'user' on field 'age': rejected value [0]; codes [Min.user.age,Min.age,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age],18]; default message [年龄最小为18]

通过控制台的输出可以看到,校验框架将我们的多个属性都进行了数据校验(默认行为),这并不好。我们希望只要有一个属性校验失败就直接返回提示信息,后面的属性不再进行校验了,相当于 “短路” 一样,因此作出进一步改进。


第七步,创建 ValidatorConfiguration 类,指定校验时使用快速失败返回模式,只要出现一个数据校验不通过,后面跳过校验,快速返回异常信息

package com.hzz.config;
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
public class ValidatorConfiguration {
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory =
                Validation.byProvider(HibernateValidator.class)
                        .configure()
                        //快速失败返回模式
                        .addProperty("hibernate.validator.fail_fast", "true")
                        .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
    /**
     * 开启快速返回
     * 如果参数校验有异常,直接抛异常,不会进入到 controller,使用全局异常拦截进行拦截
     */
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor postProcessor =
                new MethodValidationPostProcessor();
        /**设置validator模式为快速失败返回*/
        postProcessor.setValidator(validator());
        return postProcessor;
    }
}

值得注意的是,上面创建的类并不是配置类(没有@Configuration注解修饰),所以到目前为止快速失败返回模式并不会生效。而且,现在的需求是我可以根据需要选择是否开启快速失败模式,而不是一个全局配置类为全局都开启这样的模式。因此,为了达到这样的效果,需要创建一个注解用于控制此模式的开启。


第八步,创建注解 @EnableFormValidator 用于控制快速失败返回模式的开启

package com.hzz.config;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 在启动类上添加该注解来启动表单验证功能---快速失败返回模式
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ValidatorConfiguration.class)
public @interface EnableFormValidator {
}

第九步,在启动类上加入 @EnableFormValidator 注解,开启快速失败返回模式

package com.hzz;
import com.hzz.config.EnableFormValidator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableFormValidator
public class HibernateValidatorApp {
    public static void main(String[] args) {
        SpringApplication.run(HibernateValidatorApp.class, args);
    }
}

第十步,重新启动项目,访问地址 http://localhost:9100/user/save,观察控制台打印信息

org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'user' on field 'id': rejected value [null]; codes [NotNull.user.id,NotNull.id,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.id,id]; arguments []; default message [id]]; default message [用户id不能为空]

上述打印信息验证信息表示只有一个错误,验证了快速失败模式已经开启。

相关文章
|
18天前
|
Java 容器
如何在SpringBoot项目中使用过滤器和拦截器
过滤器和拦截器是日常开发中常用技术,用于对特定请求进行增强处理,如插入自定义代码以实现特定功能。过滤器在请求到达 `servlet` 前执行,而拦截器在请求到达 `servlet` 后执行。`SpringBoot` 中的拦截器依赖于 `SpringBoot` 容器,过滤器则由 `servlet` 提供。通过实现 `Filter` 接口并重写 `doFilter()` 方法可实现过滤器;通过实现 `HandlerInterceptor` 接口并重写相应方法可实现拦截器。两者的主要区别在于执行时机的不同,需根据具体场景选择使用。
如何在SpringBoot项目中使用过滤器和拦截器
|
11天前
|
Java 关系型数据库 MySQL
创建一个SpringBoot项目,实现简单的CRUD功能和分页查询
【9月更文挑战第6天】该内容介绍如何使用 Spring Boot 实现具备 CRUD 功能及分页查询的项目。首先通过 Spring Initializr 创建项目并选择所需依赖;其次配置数据库连接,并创建实体类与数据访问层;接着构建服务层处理业务逻辑;最后创建控制器处理 HTTP 请求。分页查询可通过添加 URL 参数实现。
|
6天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
6天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
1天前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
13 2
|
1天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
|
10天前
|
运维 NoSQL Java
SpringBoot接入轻量级分布式日志框架GrayLog技术分享
在当今的软件开发环境中,日志管理扮演着至关重要的角色,尤其是在微服务架构下,分布式日志的统一收集、分析和展示成为了开发者和运维人员必须面对的问题。GrayLog作为一个轻量级的分布式日志框架,以其简洁、高效和易部署的特性,逐渐受到广大开发者的青睐。本文将详细介绍如何在SpringBoot项目中接入GrayLog,以实现日志的集中管理和分析。
49 1
|
14天前
|
缓存 Java 应用服务中间件
随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架
【9月更文挑战第6天】随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架。Nginx作为高性能的HTTP反向代理服务器,常用于前端负载均衡,提升应用的可用性和响应速度。本文详细介绍如何通过合理配置实现Spring Boot与Nginx的高效协同工作,包括负载均衡策略、静态资源缓存、数据压缩传输及Spring Boot内部优化(如线程池配置、缓存策略等)。通过这些方法,开发者可以显著提升系统的整体性能,打造高性能、高可用的Web应用。
43 2
|
15天前
|
Cloud Native 安全 Java
Micronaut对决Spring Boot:谁是微服务领域的王者?揭秘两者优劣,选对框架至关重要!
【9月更文挑战第5天】近年来,微服务架构备受关注,Micronaut和Spring Boot成为热门选择。Micronaut由OCI开发,基于注解的依赖注入,内置多种特性,轻量级且启动迅速;Spring Boot则简化了Spring应用开发,拥有丰富的生态支持。选择框架需考虑项目需求、团队经验、性能要求及社区支持等因素。希望本文能帮助您选择合适的微服务框架,助力您的软件开发项目取得成功!
59 2
|
16天前
|
JavaScript 前端开发 Java
【颠覆传统】Spring框架如何用WebSocket技术重塑实时通信格局?揭秘背后的故事与技术细节!
【9月更文挑战第4天】随着Web应用对实时交互需求的增长,传统的HTTP模型已无法满足现代应用的要求,特别是在需要持续、双向通信的场景下。WebSocket协议由此诞生,提供全双工通信渠道,使服务器与客户端能实时互发消息。作为Java开发中最受欢迎的框架之一,Spring通过其WebSocket模块支持这一协议,简化了WebSocket在Spring应用中的集成。
34 0