前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring核心——数据校验

Spring核心——数据校验

作者头像
随风溜达的向日葵
发布2018-09-19 10:49:10
6990
发布2018-09-19 10:49:10
举报
文章被收录于专栏:技术墨客技术墨客

Java数据校验详解中详细介绍了Java数据校验相关的功能(简称Bean Validation,涵盖JSR-303、JSR-349、JSR-380),本文将在Bean Validation的基础上介绍Spring框架提供的数据校验功能。

Spring提供的数据校验功能分为2个部分,一个是Spring自定义的数据校验功能(以下称为Spring Validation),一个是符合Bean Validation规范的数据校验功能。

Spring Validation数据校验

Spring的自行开发的数据校验功能由3个部分组成:

  1. 校验器——Validator,他会运行校验代码。
  2. 校验对象,实际上就是一个JavaBean,Validator会对其进行校验。
  3. 校验结果——Errors,一次校验的结果都存放在Errors实例中。

这是Spring在Bean Validation规范制定之前就实现的数据校验功能,ValidationUtils的注释中@since标签是2003年5月6号,而JSR-303定稿时间已经是6年之后(2009年)的事了。

(文中仅为示例代码,可执行代码请到本人gitee库获取,本文代码在chkui.springcore.example.hybrid.springvalidation包中。)

Spring的数据校验功能就是实现检验器、校验对象、校验结果三个对象。先声明个一个校验对象(实体):

代码语言:javascript
复制
package chkui.springcore.example.hybrid.springvalidation.entity;
//车辆信息
public class Vehicle {
	private String name;
	private String type;
	private String engine;
	private String manufacturer;
	private Calendar productionDate; 

    /**Getter Setter*/
}

然后针对这个实体声明一个校验器。校验器要实现org.springframework.validation.Validator接口:

代码语言:javascript
复制
package chkui.springcore.example.hybrid.springvalidation.validator;

public class VehicleValidator implements Validator {
	private List<String> _TYPE = Arrays.asList(new String[] { "CAR", "SUV", "MPV" });

	public boolean supports(Class<?> clazz) {
        //将验证器和实体类进行绑定,如果这里返回false在验证过程中会抛出类型不匹配的异常
		return Vehicle.class.isAssignableFrom(clazz);
	}

	public void validate(Object target, Errors errors) { //验证数据
		Vehicle vehicle = Vehicle.class.cast(target);
		if (null == vehicle.getName()) {
            //使用验证工具绑定结果
			ValidationUtils.rejectIfEmpty(errors, "name", "name.empty", "车辆名称为空");
		}
		if (!_TYPE.contains(vehicle.getType())) {
            //向Error添加验证错误信息
			<2> errors.rejectValue("type", "type.error", "汽车类型必须是" + _TYPE);
		}
        //More validate ......
	}
}

有了验证对象(JavaBean)和对应的验证器(Validator)就完成了一组验证功能。注意VehicleValidator::validate方法传递的errors参数,验证工具会将错误实例传递进来交给开发者去组装验证结果。

代码中的ValidationUtils就是数据校验工具,他提供了2个功能:

  1. 执行校验(接下来会马上介绍)。
  2. 提供错误信息绑定的功能,例如ValidationUtils.rejectIfEmpty这一行代码。会将对应的信息写入到Errors中。

有了验证对象和验证器就可以执行验证:

代码语言:javascript
复制
public class SpringValidationApp {
	private static void springValidation(ApplicationContext ctx) {
		VehicleValidator vehicleValidator = new VehicleValidator();//创建验证器
		Vehicle vehicle = new Vehicle();//创建验证对象
		<1> ValidationError error = new ValidationError("Vehicle");//创建错误信息
		ValidationUtils.invokeValidator(vehicleValidator, vehicle, error);//执行验证
		List<FieldError> list = error.getFieldErrors();
		int count = 1;
        //输出验证结果
		for(FieldError res : list) {
			print("Error Info ", count++ , ".");
			print("Entity:", res.getObjectName());
			print("Field:", res.getField());
			print("Code:", res.getCode());
			print("Message:", res.getDefaultMessage());
			print("-");
		}
	}
}

执行完毕后,ValidationError中记录了所有校验错误信息。错误信息分为4个部分:

  • 验证的对象的名称:在执行验证器的代码中<1>部分创建错误对象时指定。Vehicle就是验证对象的名称。
  • 错误的域、错误code和错误信息:每一个错误都有对应的域、错误编码以及错误信息,在验证器<2>位置的代码就是指定错误信息。

以上错误信息可以通过error.getFieldErrors();来获取。

如果JavaBean有嵌套的结构,可以在校验器中调用其他的校验器来实现嵌套检验。先为Vehicle类增加一个Gearbox(变速箱)域:

代码语言:javascript
复制
package chkui.springcore.example.hybrid.springvalidation.entity;
//车辆信息
public class Vehicle {
	private String name;
	private String type;
	private String engine;
	private String manufacturer;
    private Gearbox gearbox; //Gearbox是另外一个实例
	private Calendar productionDate; 

    /**Getter Setter*/
}
代码语言:javascript
复制
//变速箱
public class Gearbox {
	private String name;
	private String manufacturer;

    /**Getter Setter*/
}

在校验器VehicleValidator::validate中增加对Gearbox验证:

代码语言:javascript
复制
public class VehicleValidator implements Validator {
	@Autowired
	GearboxValidator gearboxValidator; //用于校验Gearbox的校验器

	@Override
	public void validate(Object target, Errors errors) {
		Vehicle vehicle = Vehicle.class.cast(target);

		//some code ......
        
		}
		if(null == vehicle.getGearbox()) {
			errors.rejectValue("gearbox", "gearbox.error", "变速箱信息为空");
		}else {
            //指定子实体的名称
			errors.pushNestedPath("gearbox");
            //执行对Gearbox的校验
            ValidationUtils.invokeValidator(gearboxValidator, vehicle.getGearbox(), errors);
		}
	}
}

Bean Validation数据校验

Spring现在推荐使用Bean Validation来进行数据校验,而且已经整合到Spring MVC框架中。

在Spring中使用Bean ValidationJava数据校验详解一文中介绍的内容差不多——也是注解和校验器组成一个约束,通过注解来控制校验的过程。

Spring核心部分没有提供Bean Validation相关的实现类,所以需要引入对应的实现框架。本文引入的是Hibernate Validator,他包括验证器和el,详情可以看源码根目录的build.gradle文件。

首先我们向IoC容器中添加全局校验器:

代码语言:javascript
复制
@Configuration
public class SpringValidationConfig {

	@Bean("validator")
	public Validator validator() {
		return new LocalValidatorFactoryBean();
}

这一段添加Bean的代码非常简单,就是新建了一个LocalValidatorFactoryBean实例。LocalValidatorFactoryBean实现了javax.validation.Validator接口,并且会自动使用已经引入的Bean Validation框架。

然后向Vehicle增加Bean Validation相关的注解:

代码语言:javascript
复制
public class Vehicle {
	@NotBlank
	private String name;
	@NotBlank
	@VehicleType
	private String type;
	@NotBlank
	private String engine;
	@NotBlank
	private String manufacturer;
	<3> @Valid //@Valid的作用是对嵌套的解构进行校验
	private Gearbox gearbox;
	@Valid
	private Tyre tyre;
	@VehicleProductionDate
	private Calendar productionDate;

    /**Getter Setter*/

}

在上面的代码中,除了常规的@NotBlank等注解,还有@VehicleType这个自定义注解。在代码<3>的位置@Valid是告诉校验器还要对gearbox的实例进行校验,相当于前面介绍的嵌套校验功能。最后我们使用检验器来对Vehicle的实例进行校验:

代码语言:javascript
复制
public class SpringValidationApp {
	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringValidationConfig.class);
		BeanValidation(ctx);//JSR规范验证
	}

	private static void BeanValidation(ApplicationContext ctx) {
		Validator validator = ctx.getBean(Validator.class);//获取校验器
		Vehicle vehicle = new Vehicle();//新建要校验的对象
		validator.validate(vehicle).forEach(err -> { //执行校验
			print("Field: ", err.getPropertyPath());
			print("Error: ", err.getMessage());
		});
	}
}

关于Bean Validation的详细使用方法已经在 Java数据校验详解介绍。

兼容Bean Validation和Spring Validation

一些相对比较久远的项目可能会遇见在Spring Validation的基础上新增Bean Validation功能的情况。可以使用SpringValidatorAdapter适配器来解决这个问题:

代码语言:javascript
复制
public class SpringValidationApp {

	private static void adapterValidation(ApplicationContext ctx) {
		// 获取校验器
		// LocalValidatorFactoryBean继承了SpringValidatorAdapter
        // 所以这里就是获取LocalValidatorFactoryBean
		SpringValidatorAdapter adapter = ctx.getBean(SpringValidatorAdapter.class);

		Vehicle vehicle = new Vehicle();// 检验对象
		ValidationError error = new ValidationError("Vehicle");
		
		// Spring Validation
		ValidationUtils.invokeValidator(adapter, vehicle, error);//执行校验
		List<FieldError> list = error.getFieldErrors();//检验信息

		// Bean Validation 校验
		adapter.validate(vehicle).forEach(err -> { // 执行检验&输出校验结果
			print("Field: ", err.getPropertyPath());
			print("Error: ", err.getMessage());
		});
	}
}

上面的代码使用SpringValidatorAdapter分别执行了Bean ValidationSpring Validation。可以将SpringValidatorAdapter看作一个org.springframework.validation.Validator的实现类用ValidationUtils来执行校验,而验证的过程完全是按照Bean Validation的规范来执行的。

方法参数校验

除了校验一个实体类,Spring在Bean Validation的基础上使用后置处理器和AOP实现了方法参数的检验。例如下面的方法:

代码语言:javascript
复制
public interface PersonService {
	public @NotBlank String execute(@NotBlank(message = "必须设置人员名称") String name,
			@Min(value = 18, message = "年龄必须大于18") int age);
}

他表示返回数据不能为空字符串,传入的2个参数name不能为空字符串、age必须大于18。

要启用方法参数校验关键点是引入MethodValidationPostProcessor并在需要验证的Bean上增加一个@Validated注解。

先通过@Configuration引入后置处理器:

代码语言:javascript
复制
@Configuration
@ComponentScan("chkui.springcore.example.hybrid.springvalidation.service")
public class SpringValidationConfig {
	@Bean("validator")
	public Validator validator() {
		return new LocalValidatorFactoryBean();
	}

	@Bean
	public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
		MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
		postProcessor.setValidator(validator);
		return postProcessor;
	}
}

然后实现上面的PersonService接口并标记@Validated表示这个类中的方法要进行参数校验:

代码语言:javascript
复制
@Service
@Validated
public class PersonServiceImpl implements PersonService {

	@Override
	public String execute(String name, int age) {
		return "I'm " + name + ". " + age + " years old.";
	}
}

最后使用这个Service:

代码语言:javascript
复制
public class SpringValidationApp {

	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringValidationConfig.class);
		methodValidation(ctx);//方法参数校验
	}
	
	private static void methodValidation(ApplicationContext ctx) {
		//对方法进行参数校验
		try {
			PersonService personService = ctx.getBean(PersonService.class);
			personService.execute(null, 1);//传递参数
		} catch (ConstraintViolationException error) {
			error.getConstraintViolations().forEach(err -> {//输出校验错误信息
				print("Field: ", err.getPropertyPath());
				print("Error: ", err.getMessage());
			});
		}
	}
}

在运行的过程中,如果参数或返回数据不符合验证规则会抛出ConstraintViolationException异常,可以从中获取校验错误的信息。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Validation数据校验
  • Bean Validation数据校验
  • 兼容Bean Validation和Spring Validation
  • 方法参数校验
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档