Springboot中接口参数校验N种方法你会几个?

95 篇文章 3 订阅
12 篇文章 0 订阅

环境:springboot2.2.10.RELEASE

Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),结合BindingResult对象可以直接获取错误信息。

JSR是什么?

JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。

JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。

关于JSR303规范中定义的注解及Hibernate的一些扩展注解可参见如下文章:

SpringMVC参数统一验证方法

在Spring环境中这两个注解都可以使用。

本篇文章主要介绍如下内容:

  • 参数校验分组
  • 单个参数校验
  • 嵌套参数校验
  • 自定义工具类参数校验
  • 国际化支持
  • AOP验证参数统一处理
  1. 校验分组
    @Validated支持分组,@Valid不支持

Bean定义:

public class Users {
	@NotEmpty(message = "姓名不能为空1", groups = G1.class)
	private String name ;
	@Min(value = 10, message = "年龄不能小于10", groups = G1.class)
	@Min(value = 20, message = "年龄不能小于20", groups = G2.class)
	private Integer age ;
	@Length(min = 6, max = 18, message = "邮箱介于6到18之间", groups = {G1.class, G2.class})
	private String email ;
	public static interface G1 {}
	public static interface G2 {}
}

这里定义了2个分组G1,G2这里的定义可以随意。

定义接口:

@CustomEndpoint
@ResponseBody
public class UsersController extends BaseController {
	@PackMapping(value = "/valid/save1", method = RequestMethod.POST)
	public Object save1(@RequestBody @Validated(Users.G1.class) Users user, BindingResult result) {
		Optional<List<String>> op = valid(result) ;
		if (op.isPresent()) {
			return op.get() ;
		}
		return "success" ;
	}
	
	@PackMapping(value = "/valid/save2", method = RequestMethod.POST)
	public Object save2(@RequestBody @Validated(Users.G2.class) Users user, BindingResult result) {
		Optional<List<String>> op = valid(result) ;
		if (op.isPresent()) {
			return op.get() ;
		}
		return "success" ;
	}
}
public class BaseController {
	protected Optional<List<String>> valid(BindingResult result) {
		if (result.hasErrors()) {
			return Optional.of(result.getAllErrors().stream().map(err -> err.getDefaultMessage()).collect(Collectors.toList())) ;
		}
		return Optional.empty() ;
	}
}

这里我自定义了HandlerMapping使用了自定义的注解@CustomEndpoint和@PackMapping 具体怎么使用自定义的参见如下文章:

SpringMVC自定义注解实现接口调用 》

这里定义了两个接口在参数校验时指明了使用哪个分组,测试:

Springboot中接口参数校验N种方法你会几个?

Springboot中接口参数校验N种方法你会几个?

在这里我们的分组校验已经生效了。

注意:这里在做参数校验时分别指明了具体的分组,如果在Bean中没有指明具体的分组,那么这个校验将不会生效,比如这里:

@Length(min = 6, max = 18, message = "邮箱介于6到18之间")
	private String email ;

如果这里没有设定groups属性,那么这里的校验将不会生效,因为我们的Controller都指明了具体使用的分组。

  1. 单个参数校验
    JSR303的Hibernate Validator实现只能对对象进行参数校验不能对当个方法参数进行校验,Spring对此进行了扩展
@CustomEndpoint
@ResponseBody
@Validated
public class UsersController extends BaseController {
	@PackMapping("/valid/find")
	public Object find(@NotEmpty(message = "参数Id不能为空") String id) {
		return "查询到参数【" + id + "】" ;
	}
}

注意:这里在类上需要加入@Validated注解。参数的校验直接在相应的参数前写上对应的注解即可。

Springboot中接口参数校验N种方法你会几个?

同时后台抛出了异常信息:

Springboot中接口参数校验N种方法你会几个?

  1. 嵌套参数校验
    嵌套的对象需要使用@Valid 注解

情况1:

public class Users {
	@NotEmpty(message = "电话必需填写")
	private String phone ;
	@Valid
	private Address address;
}
public class Address {
	@NotEmpty(message = "地址信息必需填写")
	private String addr ;
}

接口:

@PackMapping(value = "/valid/save3", method = RequestMethod.POST)
	public Object save3(@RequestBody @Validated Users user, BindingResult result) {
		Optional<List<String>> op = valid(result) ;
		if (op.isPresent()) {
			return op.get() ;
		}
		return "success" ;
	}

测试:

Springboot中接口参数校验N种方法你会几个?

发现地址根本就没有校验。

调整参数继续校验:

Springboot中接口参数校验N种方法你会几个?

把Address对应的属性address加上后能够校验了。

情况2:

public class Users {
	@NotEmpty(message = "电话必需填写")
	private String phone ;
	@Valid
	private Address address = new Address();
}

这里直接把Address对象new好再测试:

Springboot中接口参数校验N种方法你会几个?

这回没有添加address也可以校验了。

  1. 自定义工具类参数校验
    参数校验不一定都Controller层进行校验,也可能在Service校验,比如你一个通用的save方法,不仅你会调用,别人也会调用,那这时候就有必要在Service中进行参数的校验。
public class ValidatorUtil {

	private static Validator validator;

	static {
		validator = Validation.buildDefaultValidatorFactory().getValidator();
	}

	/**
	 *  <p>
	 *  	抛出异常方式
	 *  </p>
	 *  <p>时间:2021年2月7日-下午5:49:29</p>
	 * @author xg
	 * @param object 校验的对象参数
	 * @param groups 分组
	 * @return void
	 */
	public static <T> void validateParams(T object, Class<?>... groups) {
		Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
		if (!constraintViolations.isEmpty()) {
			String messages = constraintViolations.stream().map(cv -> cv.getMessage()).collect(Collectors.joining("\n")) ;
			throw new ParamsException(messages) ;
		}
	}
	
	/**
	 *  <p>
	 *  	收集错误提示信息
	 *  </p>
	 *  <p>时间:2021年2月7日-下午5:51:27</p>
	 * @author xg
	 * @param object 校验的对象参数
	 * @param groups 分组
	 * @return 
	 * @return Optional<List<String>>
	 */
	public static <T> Optional<List<String>> validateParamsRet(T object, Class<?>... groups) {
		Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
		if (!constraintViolations.isEmpty()) {
			return Optional.of(constraintViolations.stream().map(cv -> cv.getMessage()).collect(Collectors.toList())) ;
		}
		return Optional.empty() ;
	}

}

先来验证下:

public static void main(String[] args) {
		Users user = new Users() ;
		System.out.println(validateParamsRet(user).get()) ;
		validateParams(user) ;
}

Springboot中接口参数校验N种方法你会几个?

控制台输出了错误信息。

工具类说明:

static {
		validator = Validation.buildDefaultValidatorFactory().getValidator();
}

这里底层是通过SPI技术来获取所有的Validator验证器:


Validation.GetValidationProviderListAction类中有如下方法:

private List<ValidationProvider<?>> loadProviders(ClassLoader classloader) {
			ServiceLoader<ValidationProvider> loader = ServiceLoader.load( ValidationProvider.class, classloader );
			Iterator<ValidationProvider> providerIterator = loader.iterator();
			List<ValidationProvider<?>> validationProviderList = new ArrayList<>();
			while ( providerIterator.hasNext() ) {
				try {
					validationProviderList.add( providerIterator.next() );
				}
				catch ( ServiceConfigurationError e ) {
				}
			}
			return validationProviderList;
		}

ServiceLoader<ValidationProvider> loader = ServiceLoader.load( ValidationProvider.class, classloader );

在我的环境中有引入Hibernate验证器。

Springboot中接口参数校验N种方法你会几个?

关于验证工厂类的获取,可以通过如下3种方式:

Springboot中接口参数校验N种方法你会几个?

  1. 国际化支持
    在resources下建立,ValidationMessages.properties文件这是默认的,中文:ValidationMessages_zh_CN.properties,英文:ValidationMessages_en_US.properties

内容如下:

name.notempty=姓名必需填写

英文:

name.notempty=name is require

Bean配置:

public class Users {
	@NotEmpty(message = "{name.notempty}", groups = G1.class)
	private String name ;
}

这里message的写法:{properties中定义的key}

Springboot中接口参数校验N种方法你会几个?

模拟英文环境:

Springboot中接口参数校验N种方法你会几个?

请求中需要添加:Accept-Language头信息指明语言。

  1. AOP验证参数统一处理
    以上都接口测试都是在每个方法中自己调用验证逻辑进行处理,接下来通过AOP类对这里有参数校验的进行统一的处理。

引入依赖:

<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<scope>runtime</scope>
		</dependency>

自定义注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableValidate {
}

定义AOP:

@Component
@Aspect
public class ValidateAspect {
	
	@Pointcut("@annotation(com.pack.params.valid.EnableValidate)")
	public void valid() {} 
	
	@Before("valid()")
	public void validateBefore(JoinPoint jp) {
		Object[] args = jp.getArgs() ;
		for (Object arg : args) {
			if (arg instanceof BindingResult) {
				BindingResult result = (BindingResult) arg ;
				if (result.hasErrors()) {
					String messages = result.getAllErrors().stream().map(err -> err.getDefaultMessage()).collect(Collectors.joining(",")) ;
					throw new ParamsException(messages) ;
				}
			}
		}
	}
	
}

接口:

@PackMapping(value = "/valid/save1", method = RequestMethod.POST)
	@EnableValidate
	public Object save1(@RequestBody @Validated(Users.G1.class) Users user, BindingResult result) {
		/*Optional<List<String>> op = valid(result) ;
		if (op.isPresent()) {
			return op.get() ;
		}*/
		return "success" ;
	}

测试:

Springboot中接口参数校验N种方法你会几个?

其实在这里对于这样的异常可以用@ControllerAdvice进行全局拦截处理,统一下输出。

完毕!!!

创作不易给个关注+转发呗谢谢

 图片

图片

图片

图片

图片

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值