IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Springboot Controller优雅参数校验源码分析 -> 正文阅读

[Java知识库]Springboot Controller优雅参数校验源码分析

前言

  • 【校验参数在项目中是很常见的,在java中,几乎每个有入参的方法,在执行下一步操作之前,都要验证参数的合法性,比如是入参否为空,数据格式是否正确等等,往常的写法就是一大推的if-else,既不美观也不优雅,这个时候JCP组织站出来了,并且制定了一个标准来规范校验的操作,这个标准就是Java Validation API(JSR 303)。】 —摘自博客园

  • 【Bean Validation是Java定义的一套基于注解的数据校验规范,目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本(2.0完成于2017.08)】— 摘自知乎

使用步骤

pom中添加依赖

以继承 spring-boot-starter-parent 为例

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>

另需加入依赖:

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
        </dependency>
或者
		<dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
        </dependency>

因为hibernate-validator中也依赖了validation-api ( 高版本validation-api中包含了完善的核心注解,提供了与hibernate-validator相同功能的注解)
在这里插入图片描述

在低版本的spring-boot里可能需要两个都要引入,主要是低版本中的各有各的特色注解(比如在validation-api中在2.0版本才加入NotBlank这个注解),但是高版本中validation-api中的注解已经很完善;

hibernate-validator中的一些注解已经不建议使用了;
在这里插入图片描述

接收请求的参数添加注解

加入依赖后,我们就可以在我们的实体上加上对应的校验注解,例如:

package com.xx.log.common.pojo.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;


@Data
public class TestDTO {

    @NotBlank(message = "info 不能为空")
    @NotNull(message = "info 不能为null")
    private String info;


    @NotBlank(message = "name 不能为空")
    @NotNull(message = "name 不能为null")
    private String name;
}

开启校验

开启校验是在我们controller 方法的参数前用 @Validated 或者 @Valid 注解

    @GetMapping("/test")
    public TestVO test(@Validated TestDTO testDTO) {
        log.info("testDTO:{}", testDTO);
        return TestVO.builder().code(0).build();
    }
    
或者

    @PostMapping("/test")
    public TestVO test(@RequestBody @Validated TestDTO testDTO) {
        log.info("testDTO:{}", testDTO);
        return TestVO.builder().code(0).build();
    }

源码分析

源码入口

参数校验是在调用controller方法前参数准备阶段里面,具体代码入口如下

org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
↓
org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
↓
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument
↓
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument
↓
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#validateIfApplicable

我们看看 validateIfApplicable 这个方法

	protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
		Annotation[] annotations = parameter.getParameterAnnotations();
		for (Annotation ann : annotations) {
//@A
			Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
//@B			
			if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
				Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
				Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
				binder.validate(validationHints);
				break;
			}
		}
	}

从这个地方可以看出 @Validated和 @Valid 、以@Valid 开头的自定义注解都可以开启校验

binder代码还很深,包括它的创建和使用
binder创建是由 ServletRequestDataBinderFactory 类创建的ExtendedServletRequestDataBinder实例

public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory {
	/**
	 * Create a new instance.
	 * @param binderMethods one or more {@code @InitBinder} methods
	 * @param initializer provides global data binder initialization
	 */
	public ServletRequestDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods,
			@Nullable WebBindingInitializer initializer) {
		super(binderMethods, initializer);
	}

	/**
	 * Returns an instance of {@link ExtendedServletRequestDataBinder}.
	 */
	@Override
	protected ServletRequestDataBinder createBinderInstance(
			@Nullable Object target, String objectName, NativeWebRequest request) throws Exception  {
		return new ExtendedServletRequestDataBinder(target, objectName);
	}
}

ExtendedServletRequestDataBinder的继承关系
在这里插入图片描述
接上面源码的调用栈的validateIfApplicable方法继续往下走

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#validateIfApplicable
↓
org.springframework.validation.DataBinder#validate(java.lang.Object...)org.springframework.boot.autoconfigure.validation.ValidatorAdapter#validate(java.lang.Object, org.springframework.validation.Errors)org.springframework.validation.beanvalidation.SpringValidatorAdapter#validate(java.lang.Object, org.springframework.validation.Errors)

最后一个方法是

@Override
public void validate(Object target, Errors errors) {
	if (this.targetValidator != null) {
		processConstraintViolations(this.targetValidator.validate(target), errors);
	}
}
  • this.targetValidator 这个属性实际的对象是hibernate提供的校验类 org.hibernate.validator.internal.engine.ValidatorImpl
  • processConstraintViolations 作用是将hibernate校验返回的结果统计成spring能够识别的结果,也就是将校验结果再封装一遍

注意点

  • @NotBlank 不能用来校验数值类型,可以用来校验字符串
  • 数值类型可以用以下注解
    • @NotNull
    • @Max
    • @Min
    • @DecimalMin
    • @DecimalMax

转换校验失败异常提示

如果参数校验不通过,框架会抛出 MethodArgumentNotValidException,在调用方看来不是很友好,我们可以定义通用的该异常的处理类进行统一处理返回:

package com.xx.log.config;

import com.xx.log.common.pojo.vo.TestVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
@Slf4j
public class ParameterCalibration {

    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public TestVO handleMethodArgumentNotValidException(Exception exception) {
        StringBuilder errorInfo = new StringBuilder();
        BindingResult bindingResult = null;
        if (exception instanceof MethodArgumentNotValidException) {
            bindingResult = ((MethodArgumentNotValidException) exception).getBindingResult();
        }
        if (exception instanceof BindException) {
            bindingResult = ((BindException) exception).getBindingResult();
        }
        for (int i = 0; i < bindingResult.getFieldErrors().size(); i++) {
            if (i > 0) {
                errorInfo.append(",");
            }
            FieldError fieldError = bindingResult.getFieldErrors().get(i);
            errorInfo.append(fieldError.getField()).append(" :").append(fieldError.getDefaultMessage());
        }
        log.error(errorInfo.toString());
        //这里返回自己的Result的结果类。
        return TestVO.builder().msg(errorInfo.toString()).code(-1).build();
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public TestVO handleDefaultException(Exception exception) {
        log.error(exception.toString());
        //这里返回自己的Result的结果类。
        return TestVO.builder().msg("服务器错误").code(-1).build();
    }
}

异常统一处理原理可参考我 另一篇文章

自定义校验注解

自定义注解 【cnblogs 文章推荐】

over~~~

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-17 01:24:15  更:2021-08-17 01:24:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 20:58:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码