`
u010562966
  • 浏览: 2257 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Spring Boot 使用 JSR303 实现参数验证

阅读更多

文章首发于公众号《程序员果果》

地址 : http://blog.itwolfed.com/blog/97

简介

JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。

在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情。应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的。在通常的情况下,应用程序是分层的,不同的层由不同的开发人员来完成。很多时候同样的数据验证逻辑会出现在不同的层,这样就会导致代码冗余和一些管理的问题,比如说语义的一致性等。为了避免这样的情况发生,最好是将验证逻辑与相应的域模型进行绑定。

Bean Validation 为 JavaBean 验证定义了相应的元数据模型和 API。缺省的元数据是 Java Annotations,通过使用 XML 可以对原有的元数据信息进行覆盖和扩展。在应用程序中,通过使用 Bean Validation 或是你自己定义的 constraint,例如 @NotNull, @Max, @ZipCode, 就可以确保数据模型(JavaBean)的正确性。constraint 可以附加到字段,getter 方法,类或者接口上面。对于一些特定的需求,用户可以很容易的开发定制化的 constraint。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。

Bean Validation 规范内嵌的约束注解

实例

基本应用

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

给参数对象添加校验注解

@Data
public class User {
    
    private Integer id;
    @NotBlank(message = "用户名不能为空")
    private String username;
    @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密码必须为8~16个字母和数字组合")
    private String password;
    @Email
    private String email;
    private Integer gender;

}

Controller 中需要校验的参数Bean前添加 @Valid 开启校验功能,紧跟在校验的Bean后添加一个BindingResult,BindingResult封装了前面Bean的校验结果。

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("")
    public Result save (@Valid User user , BindingResult bindingResult)  {
        if (bindingResult.hasErrors()) {
            Map<String , String> map = new HashMap<>();
            bindingResult.getFieldErrors().forEach( (item) -> {
                String message = item.getDefaultMessage();
                String field = item.getField();
                map.put( field , message );
            } );
            return Result.build( 400 , "非法参数 !" , map);
        }
        return Result.ok();
    }

}

测试如下:

 

异常的统一处理

参数校验不通过时,会抛出 BingBindException 异常,可以在统一异常处理中,做统一处理,这样就不用在每个需要参数校验的地方都用 BindingResult 获取校验结果了。

@Slf4j
@RestControllerAdvice(basePackages = "com.itwolfed.controller")
public class GlobalExceptionControllerAdvice {

    @ExceptionHandler(value= {MethodArgumentNotValidException.class , BindException.class})
    public Result handleVaildException(Exception e){
        BindingResult bindingResult = null;
        if (e instanceof MethodArgumentNotValidException) {
            bindingResult = ((MethodArgumentNotValidException)e).getBindingResult();
        } else if (e instanceof BindException) {
            bindingResult = ((BindException)e).getBindingResult();
        }
        Map<String,String> errorMap = new HashMap<>(16);
        bindingResult.getFieldErrors().forEach((fieldError)->
                errorMap.put(fieldError.getField(),fieldError.getDefaultMessage())
        );
        return Result.build(400 , "非法参数 !" , errorMap);
    }

}

分组解决校验

新增和修改对于实体的校验规则是不同的,例如id是自增的时,新增时id要为空,修改则必须不为空;新增和修改,若用的恰好又是同一种实体,那就需要用到分组校验。

校验注解都有一个groups属性,可以将校验注解分组,我们看下@NotNull的源码:

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {

    String message() default "{javax.validation.constraints.NotNull.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        NotNull[] value();
    }
}

从源码可以看出 groups 是一个Class<?>类型的数组,那么就可以创建一个Groups.

public class Groups {
    public interface Add{}
    public interface  Update{}
}

给参数对象的校验注解添加分组

@Data
public class User {

    @Null(message = "新增不需要指定id" , groups = Groups.Add.class)
    @NotNull(message = "修改需要指定id" , groups = Groups.Update.class)
    private Integer id;
    @NotBlank(message = "用户名不能为空")
    @NotNull
    private String username;
    @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密码必须为8~16个字母和数字组合")
    private String password;
    @Email
    private String email;
    private Integer gender;

}

Controller 中原先的@Valid不能指定分组 ,需要替换成@Validated

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("")
    public Result save (@Validated(Groups.Add.class) User user)  {
        return Result.ok();
    }

}

测试如下:

自定义校验注解

虽然JSR303和springboot-validator 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要 自定义校验注解。

例如User中的gender,用 1代表男 2代表女,我们自定义一个校验注解@ListValue,指定取值只能1和2。

创建约束规则

@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface ListValue {
    String message() default "";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    int[] vals() default { };
}

一个标注(annotation) 是通过@interface关键字来定义的. 这个标注中的属性是声明成类似方法 的样式的. 根据Bean Validation API 规范的要求:

  • message属性, 这个属性被用来定义默认得消息模版, 当这个约束条件被验证失败的时候,通过 此属性来输出错误信息。
  • groups 属性, 用于指定这个约束条件属于哪(些)个校验组. 这个的默认值必须是Class<?>类型数组。
  • payload 属性, Bean Validation API 的使用者可以通过此属性来给约束条件指定严重级别. 这个属性并不被API自身所使用。

除了这三个强制性要求的属性(message, groups 和 payload) 之外, 我们还添 加了一个属性用来指定所要求的值. 此属性的名称vals在annotation的定义中比较特 殊, 如果只有这个属性被赋值了的话, 那么, 在使用此annotation到时候可以忽略此属性名称.

另外, 我们还给这个annotation标注了一些元标注( meta annotatioins):

  • @Target({ METHOD, FIELD, ANNOTATION_TYPE }): 表示此注解可以被用在方法, 字段或者 annotation声明上。
  • @Retention(RUNTIME): 表示这个标注信息是在运行期通过反射被读取的.
  • @Constraint(validatedBy = ListValueConstraintValidator.class): 指明使用哪个校验器(类) 去校验使用了此标注的元素.
  • @Documented: 表示在对使用了该注解的类进行javadoc操作到时候, 这个标注会被添加到 javadoc当中.

创建约束校验器

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    private Set<Integer> set = new HashSet<>();
    /**
     * 初始化方法
     */
    @Override
    public void initialize(ListValue constraintAnnotation) {

        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }

    }

    /**
     * 判断是否校验成功
     *
     * @param value 需要校验的值
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {

        return set.contains(value);
    }
}

ListValueConstraintValidator定义了两个泛型参数, 第一个是这个校验器所服务到标注类型(在我们的例子中即ListValue), 第二个这个校验器所支持到被校验元素的类型 (即Integer)。

如果一个约束标注支持多种类型的被校验元素的话, 那么需要为每个所支持的类型定义一个ConstraintValidator,并且注册到约束标注中。

这个验证器的实现就很平常了, initialize() 方法传进来一个所要验证的标注类型的实例, 在本 例中, 我们通过此实例来获取其vals属性的值,并将其保存为Set集合中供下一步使 用。

isValid()是实现真正的校验逻辑的地方, 判断一个给定的int对于@ListValue这个约束条件来说 是否是合法的。

在参数对象中使用@ListValue注解。

@Data
public class User {

    @Null(message = "新增不需要指定id" , groups = Groups.Add.class)
    @NotNull(message = "修改需要指定id" , groups = Groups.Update.class)
    private Integer id;
    @NotBlank(message = "用户名不能为空")
    @NotNull
    private String username;
    @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密码必须为8~16个字母和数字组合")
    private String password;
    @Email
    private String email;
    @ListValue( message = "性别应指定相应的值" , vals = {1,2} , groups = {Groups.Add.class , Groups.Update.class})
    private Integer gender;

}

测试如下:

 

源码地址

https://github.com/gf-huanchupk/SpringBootLearning

参考

https://www.ibm.com/developerworks/cn/java/j-lo-jsr303/index.html https://docs.jboss.org/hibernate/validator/4.3/reference/zh-CN/pdf/hibernatevalidatorreference.pdf

 

关注我

0
1
分享到:
评论

相关推荐

    Springboot集成JSR303参数校验的方法实现

    Spring Boot集成JSR303参数校验的方法实现涉及了Java中参数校验的标准规范,即JSR303(Bean Validation)规范,该规范通过在Java对象属性上添加注解来实现数据校验的约束定义。这种方式能够将校验逻辑与业务逻辑分离...

    [课堂课件讲解]Java微服务实践-Spring Boot 验证.pptx

    本文档主要介绍 Java 微服务实践中的验证机制,特别是使用 Spring Boot 框架进行验证。我们将介绍 Apache commons-validator 库的使用和配置,包括可配置的校验引擎、可重用的原生校验手段、第三方依赖等。同时,...

    JSR303数据校验springboot-01-config.zip

    JSR303数据校验是Java世界中用于验证数据的有效性和一致性的一种规范,它为开发者提供了一套标准的API来实现对象级别的业务规则检查。这个"springboot-01-config.zip"压缩包文件可能包含了关于如何在Spring Boot项目...

    spring boot web项目

    这可能包括了事务管理、输入验证(如使用JSR303/JSR349 Bean Validation框架)、业务规则验证等,确保了数据的正确性。 "Echarts制作的折线图"表明项目中集成了Echarts,这是一个由百度开发的JavaScript数据可视化...

    Spring boot+mybatis+Spring MVC

    5. **数据绑定和验证**:自动将请求参数绑定到模型对象,并支持JSR-303/JSR-349验证。 在一个典型的Spring Boot + MyBatis + Spring MVC项目中,MyBatis作为数据访问层,处理数据库交互;Spring MVC负责处理HTTP...

    spring-boot-starter-validation-1.3.5.RELEASE.zip

    它的“starter”组件是为了快速引入特定功能而设计的,例如这里的"spring-boot-starter-validation"是为了在Spring Boot应用中轻松集成JSR-303/JSR-349(Bean Validation)标准,进行数据校验。 2. **Bean ...

    《Spring Boot 2精髓:从构建小系统到架构分布式大系统》学习项目源码

    URL 映射、文件上传、JSON 解析、JSR303 验证框架、跨域访问、通用错误处理 2018-2018031401:04-ch04-view 第四章《视图技术》学习源码 Beetl 模版引擎、JSON 技术 2018-2018062501:05-ch05-data 第五章...

    从0写一个问卷调查APP后端spring boot基础框架

    - 使用JSR-303/JSR-349 Bean Validation进行数据验证,如`@NotBlank`、`@Size`等。 - 自定义全局异常处理器,如`@ControllerAdvice`和`@ExceptionHandler`。 12. **安全配置**: - 引入Spring Security依赖,...

    Spring Boot技术知识点:如何使用@Valid注解来对邮箱字段进行数据校验

    当在方法参数上使用`@Valid`时,Spring MVC会调用对应的Validator来检查该参数,确保其满足预定义的校验规则。 2. **如何创建验证规则**: 我们可以通过在模型类的属性上添加各种验证注解来定义校验规则。例如,...

    SpringBootJSR303:带有 JSR 303 的 Spring Boot 应用程序 - 独立应用程序的 Bean Validation 1.1

    通常,Spring Boot会自动配置Hibernate Validator(JSR 303的一个实现),因此只需要在`pom.xml`或`build.gradle`中引入`spring-boot-starter-validation`模块。 ```xml &lt;groupId&gt;org.springframework.boot ...

    Spring Boot 2 Thymeleaf服务器端表单验证实现详解

    Spring Boot 2 Thymeleaf 服务器端表单验证实现详解是指在 Spring Boot 2 中使用 Thymeleaf 模板引擎实现服务器端表单验证的方法。该方法主要使用 Java 的 JSR 303 规范和 Hibernate Validator 实现数据验证。 表单...

    集成spring-boot-starter-validation对接口参数校验.zip

    本项目是基于Spring Boot框架构建的,旨在实现一些实用功能,其中包括了对请求参数的有效性检查。下面我们将详细探讨如何集成`spring-boot-starter-validation`以及如何进行接口参数的校验。 首先,`pom.xml`文件是...

    Spring Boot 2.x的EhCache缓存的使用问题详解.docx

    在本文中,我们将深入探讨如何在Spring Boot环境中配置和使用EhCache进行缓存。 首先,Spring Boot自动配置缓存管理器时会按照特定的顺序检测可用的缓存提供商。默认情况下,它会尝试找到以下缓存实现:Generic、...

    spring-boot入门实例(jpa+postgresql+aop实现valid切面式校验)

    这个入门实例将向我们展示如何使用Spring Boot与JPA(Java Persistence API)、PostgreSQL数据库以及AOP(面向切面编程)来实现数据验证。 首先,让我们详细了解一下Spring Boot。Spring Boot的核心理念是“约定...

    validata-spring-boot-starter.zip

    Spring Boot提供了JSR-303/JSR-349(Bean Validation)支持,允许我们定义验证规则并在运行时进行验证。`validata-spring-boot-starter`可能扩展了这一功能,提供了一些定制化的验证规则或更简便的使用方式。例如,...

    Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化

    Springboot通过集成JSR 303规范,可以实现对Controller层及Service层的参数校验。而AOP(面向切面编程)的引入,为服务层的校验提供了更为灵活的实现方式。同时,为了实现更好的用户体验,往往需要对应用进行国际化...

    spring-boot howto 注解版

    Spring Boot 内置了一些 `FailureAnalyzer` 实现,例如用于分析 ApplicationContext 异常、JSR-303 验证错误等。 **如何创建自己的 `FailureAnalyzer`:** 1. **继承 `AbstractFailureAnalyzer`**:这是最简便的...

    Kotlin + Spring Boot 请求参数验证的代码实例

    Spring Boot提供了一种优雅的方式来处理这个问题,即使用Bean Validation(JSR-303)规范,它允许开发者通过注解来实现参数验证。在Kotlin中结合Spring Boot,我们可以利用其特性来创建更简洁且强大的验证逻辑。以下...

    spring boot + mybatis + jersey + mysql源码

    Spring Boot、MyBatis、Jersey和MySQL这四个组件的整合,能够帮助开发者实现这一目标。下面将详细阐述它们各自的功能以及如何将它们有效地集成在一起。 1. Spring Boot: Spring Boot是Spring框架的简化版,旨在...

Global site tag (gtag.js) - Google Analytics