高效使用hibernate-validator校验框架
一、前言
高效、合理的使用hibernate-validator校验框架可以提高程序的可读性,以及减少不必要的代码逻辑。接下来会介绍一下常用一些使用方式。
二、常用注解说明
限制 | 说明 |
@Null | 限制只能为null |
@NotNull | 限制必须不为null |
@AssertFalse | 限制必须为false |
@AssertTrue | 限制必须为true |
@DecimalMax(value) | 限制必须为一个不大于指定值的数字 |
@DecimalMin(value) | 限制必须为一个不小于指定值的数字 |
@Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction |
@Future | 限制必须是一个将来的日期 |
@Max(value) | 限制必须为一个不大于指定值的数字 |
@Min(value) | 限制必须为一个不小于指定值的数字 |
@Past | 限制必须是一个过去的日期 |
@Pattern(value) | 限制必须符合指定的正则表达式 |
@Size(max,min) | 限制字符长度必须在min到max之间 |
@Past | 验证注解的元素值(日期类型)比当前时间早 |
@NotEmpty | 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
@NotBlank | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 |
三、定义校验分组
- public class ValidateGroup {
- public interface FirstGroup {
- }
- public interface SecondeGroup {
- }
- public interface ThirdGroup {
- }
- }
四、定义校验Bean
- @Validated
- @GroupSequence({ValidateGroup.FirstGroup.class, BaseMessageRequestBean.class})
- public class BaseMessageRequestBean {
- //渠道类型
- @NotNull(message = "channelType为NULL", groups = ValidateGroup.FirstGroup.class)
- private String channelType;
- //消息(模板消息或者普通消息)
- @NotNull(message = "data为NUll", groups = ValidateGroup.FirstGroup.class)
@Valid- private Object data;
- //业务类型
- @NotNull(message = "bizType为NULL", groups = ValidateGroup.FirstGroup.class)
- private String bizType;
- //消息推送对象
- @NotBlank(message = "toUser为BLANK", groups = ValidateGroup.FirstGroup.class)
- private String toUser;
- private long createTime = Instant.now().getEpochSecond();
- ......
- }
请自行参考:@Validated和@Valid区别
五、validator基本使用
- @RestController
- public class TestValidatorController {
- @RequestMapping("/test/validator")
- public void test(@Validated BaseMessageRequestBean bean){
...- }
- }
这种使用方式有一个弊端,不能自定义返回异常。spring如果验证失败,则直接抛出异常,一般不可控。
六、借助BindingResult
- @RestController
- public class TestValidatorController {
- @RequestMapping("/test/validator")
- public void test(@Validated BaseMessageRequestBean bean, BindingResult result){
- result.getAllErrors();
- ...
- }
- }
如果方法中有BindingResult类型的参数,spring校验完成之后会将校验结果传给这个参数。通过BindingResult控制程序抛出自定义类型的异常或者返回不同结果。
七、全局拦截校验器
当然了,需要在借助BindingResult的前提下...
- @Aspect
- @Component
- public class ControllerValidatorAspect {
- @Around("execution(* com.*.controller..*.*(..)) && args(..,result)")
- public Object doAround(ProceedingJoinPoint pjp, result result) {
- result.getFieldErrors();
- ...
- }
- }
这种方式可以减少controller层校验的代码,校验逻辑统一处理,更高效。
八、借助ValidatorUtils工具类
- @Bean
- public Validator validator() {
- return new LocalValidatorFactoryBean();
- }
LocalValidatorFactoryBean官方示意
LocalValidatorFactoryBean是Spring应用程序上下文中javax.validation(JSR-303)设置的中心类:它引导javax.validation.ValidationFactory并通过Spring Validator接口以及JSR-303 Validator接口和ValidatorFactory公开它。界面本身。通过Spring或JSR-303 Validator接口与该bean的实例进行通信时,您将与底层ValidatorFactory的默认Validator进行通信。这非常方便,因为您不必在工厂执行另一个调用,假设您几乎总是会使用默认的Validator。这也可以直接注入Validator类型的任何目标依赖项!从Spring 5.0开始,这个类需要Bean Validation 1.1+,特别支持Hibernate Validator 5.x(参见setValidationMessageSource(org.springframework.context.MessageSource))。这个类也与Bean Validation 2.0和Hibernate Validator 6.0运行时兼容,有一个特别说明:如果你想调用BV 2.0的getClockProvider()方法,通过#unwrap(ValidatorFactory.class)获取本机ValidatorFactory,在那里调用返回的本机引用上的getClockProvider()方法。Spring的MVC配置命名空间也使用此类,如果存在javax.validation API但未配置显式Validator。
- @Component
- public class ValidatorUtils implements ApplicationContextAware {
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- ValidatorUtils.validator = (Validator) applicationContext.getBean("validator");
- }
- private static Validator validator;
- public static Optional<String> validateResultProcess(Object obj) {
- Set<ConstraintViolation<Object>> results = validator.validate(obj);
- if (CollectionUtils.isEmpty(results)) {
- return Optional.empty();
- }
- StringBuilder sb = new StringBuilder();
- for (Iterator<ConstraintViolation<Object>> iterator = results.iterator(); iterator.hasNext(); ) {
- sb.append(iterator.next().getMessage());
- if (iterator.hasNext()) {
- sb.append(" ,");
- }
- }
- return Optional.of(sb.toString());
- }
- }
为什么要使用这个工具类呢?
1、controller方法中不用加入BindingResult参数
2、controller方法中需要校验的参数也不需要加入@Valid或者@Validated注解
怎么样是不是又省去了好多代码,开不开心。
具体使用,在controller方法或者全局拦截校验器中调用 ValidatorUtils.validateResultProcess(需要校验的Bean) 直接获取校验的结果。
请参考更多功能的ValidatorUtils工具类。
九、自定义校验器
定义一个MessageRequestBean,继承BaseMessageRequestBean,signature字段需要我们自定义校验逻辑。
- @Validated
- @GroupSequence({ValidateGroup.FirstGroup.class, ValidateGroup.SecondeGroup.class, MessageRequestBean.class})
- @LogicValidate(groups = ValidateGroup.SecondeGroup.class)
- public class MessageRequestBean extends BaseMessageRequestBean {
- //签名信息(除该字段外的其他字段按照字典序排序,将值顺序拼接在一起,进行md5+Base64签名算法)
- @NotBlank(message = "signature为BLANK", groups = ValidateGroup.FirstGroup.class)
- private String signature;
- ...
- }
实现自定义校验逻辑也很简单......
1、自定义一个带有 @Constraint注解的注解@LogicValidate,validatedBy 属性指向该注解对应的自定义校验器
- @Target({TYPE})
- @Retention(RUNTIME)
- //指定验证器
- @Constraint(validatedBy = LogicValidator.class)
- @Documented
- public @interface LogicValidate {
- String message() default "校验异常";
- //分组
- Class<?>[] groups() default {};
- Class<? extends Payload>[] payload() default {};
- }
2、自定义校验器LogicValidator,泛型要关联上自定义的注解和需要校验bean的类型
- public class LogicValidator implements ConstraintValidator<LogicValidate, MessageRequestBean> {
- @Override
- public void initialize(LogicValidate logicValidate) {
- }
- @Override
- public boolean isValid(MessageRequestBean messageRequestBean, ConstraintValidatorContext context) {
- String toSignature = StringUtils.join( messageRequestBean.getBizType()
- , messageRequestBean.getChannelType()
- , messageRequestBean.getData()
- , messageRequestBean.getToUser());
- String signature = new Base64().encodeAsString(DigestUtils.md5(toSignature));
- if (!messageRequestBean.getSignature().equals(signature)) {
- context.disableDefaultConstraintViolation();
- context.buildConstraintViolationWithTemplate("signature校验失败")
- .addConstraintViolation();
- return false;
- }
- return true;
- }
- }
可以通过ConstraintValidatorContext禁用掉默认的校验配置,然后自定义校验配置,比如校验失败后返回的信息。
十、springboot国际化信息配置
- @Configuration
- @ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
- @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
- @Conditional(ResourceBundleCondition.class)
- @EnableConfigurationProperties
- @ConfigurationProperties(prefix = "spring.messages")
- public class MessageSourceAutoConfiguration {
- private static final Resource[] NO_RESOURCES = {};
- /**
- * Comma-separated list of basenames, each following the ResourceBundle convention.
- * Essentially a fully-qualified classpath location. If it doesn't contain a package
- * qualifier (such as "org.mypackage"), it will be resolved from the classpath root.
- */
- private String basename = "messages";
- /**
- * Message bundles encoding.
- */
- private Charset encoding = Charset.forName("UTF-8");
- /**
- * Loaded resource bundle files cache expiration, in seconds. When set to -1, bundles
- * are cached forever.
- */
- private int cacheSeconds = -1;
- /**
- * Set whether to fall back to the system Locale if no files for a specific Locale
- * have been found. if this is turned off, the only fallback will be the default file
- * (e.g. "messages.properties" for basename "messages").
- */
- private boolean fallbackToSystemLocale = true;
- /**
- * Set whether to always apply the MessageFormat rules, parsing even messages without
- * arguments.
- */
- private boolean alwaysUseMessageFormat = false;
- @Bean
- public MessageSource messageSource() {
- ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
- if (StringUtils.hasText(this.basename)) {
- messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
- StringUtils.trimAllWhitespace(this.basename)));
- }
- if (this.encoding != null) {
- messageSource.setDefaultEncoding(this.encoding.name());
- }
- messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
- messageSource.setCacheSeconds(this.cacheSeconds);
- messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
- return messageSource;
- }
- public String getBasename() {
- return this.basename;
- }
- public void setBasename(String basename) {
- this.basename = basename;
- }
- public Charset getEncoding() {
- return this.encoding;
- }
- public void setEncoding(Charset encoding) {
- this.encoding = encoding;
- }
- public int getCacheSeconds() {
- return this.cacheSeconds;
- }
- public void setCacheSeconds(int cacheSeconds) {
- this.cacheSeconds = cacheSeconds;
- }
- public boolean isFallbackToSystemLocale() {
- return this.fallbackToSystemLocale;
- }
- public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
- this.fallbackToSystemLocale = fallbackToSystemLocale;
- }
- public boolean isAlwaysUseMessageFormat() {
- return this.alwaysUseMessageFormat;
- }
- public void setAlwaysUseMessageFormat(boolean alwaysUseMessageFormat) {
- this.alwaysUseMessageFormat = alwaysUseMessageFormat;
- }
- protected static class ResourceBundleCondition extends SpringBootCondition {
- private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<String, ConditionOutcome>();
- @Override
- public ConditionOutcome getMatchOutcome(ConditionContext context,
- AnnotatedTypeMetadata metadata) {
- String basename = context.getEnvironment()
- .getProperty("spring.messages.basename", "messages");
- ConditionOutcome outcome = cache.get(basename);
- if (outcome == null) {
- outcome = getMatchOutcomeForBasename(context, basename);
- cache.put(basename, outcome);
- }
- return outcome;
- }
- private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context,
- String basename) {
- ConditionMessage.Builder message = ConditionMessage
- .forCondition("ResourceBundle");
- for (String name : StringUtils.commaDelimitedListToStringArray(
- StringUtils.trimAllWhitespace(basename))) {
- for (Resource resource : getResources(context.getClassLoader(), name)) {
- if (resource.exists()) {
- return ConditionOutcome
- .match(message.found("bundle").items(resource));
- }
- }
- }
- return ConditionOutcome.noMatch(
- message.didNotFind("bundle with basename " + basename).atAll());
- }
- private Resource[] getResources(ClassLoader classLoader, String name) {
- try {
- return new PathMatchingResourcePatternResolver(classLoader)
- .getResources("classpath*:" + name + ".properties");
- }
- catch (Exception ex) {
- return NO_RESOURCES;
- }
- }
- }
- }
从上面的MessageSource自动配置可以看出,可以通过spring.message.basename指定要配置国际化文件位置,默认值是“message”。spring boot默认就支持国际化的,默认会去resouces目录下寻找message.properties文件。
这里就不进行过多关于国际化相关信息的介绍了,肯定少不了区域解析器。springboot国际化相关知识请参考:Spring Boot国际化(i18n)
高效使用hibernate-validator校验框架的更多相关文章
- springboot使用hibernate validator校验,Bean Validation校验
第一个地址:springboot使用hibernate validator校验,Bean Validation校验
- springboot使用hibernate validator校验
一.参数校验 在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要 ...
- springboot使用hibernate validator校验方式
一.参数校验 在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要 ...
- Hibernate Validator验证框架中@NotEmpty、@NotBlank、@NotNull 的区别
Hibernate Validator验证框架中@NotEmpty.@NotBlank.@NotNull的主要使用情况 @NotEmpty 用在集合类上面 @NotBlank 用在String上 ...
- spring MVC 使用 hibernate validator验证框架,国际化配置
spring mvc使用hibernate validator框架可以实现的功能: 1. 注解java bean声明校验规则. 2. 添加message错误信息源实现国际化配置. 3. 结合sprin ...
- Hibernate Validator校验参数全攻略
1. 前言 数据字段一般都要遵循业务要求和数据库设计,所以后端的参数校验是必须的,应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的. 2. 数据校验的痛点 为了保证数据语义的正确,我们 ...
- spring boot中使用javax.validation以及org.hibernate.validator校验入参
这里springboot用的版本是:<version>2.1.1.RELEASE</version> 自带了hibernate.validator,所以不用添加额外依赖 1.创 ...
- Java笔记 #07# Hibernate Validator
Hibernate Validator是Spring Boot默认附带的标准校验API(javax.validation)实现. 应用实例(配合切面) 采用注解定义切面.java @Aspect @C ...
- hibernate validate验证框架中@NotEmpty、@NotbBank、@NotNull的区别
Hibernate Validator验证框架中@NotEmpty.@NotBlank.@NotNull 的区别 Hibernate Validator验证框架中@NotEmpty.@NotBlank ...
- hibernate.validator 与 jackson
1.使用hibernate.validator校验非空,在FormData类中 name字段上面加@NotEmpty @NotEmpty(message = "姓名必填") pri ...
随机推荐
- Confluence 6 配置文件和key
找到配置文件 缓存的配置文件是存储在 <confluence-home>/shared-home/config/cache-settings-overrides.properties 中的 ...
- iframe与主框架跨域相互访问方法
iframe 与主框架相互访问方法 http://blog.csdn.net/fdipzone/article/details/17619673/ 1.同域相互访问 假设A.html 与 b.htm ...
- (一)STL体系结构基础介绍
一.STL六大部件 容器(Containers):存放元素,内存由分配器搞定 分配器(Allocator):支持容器的内存分配 算法:操作容器元素的函数.与OO不同(面向对象将元素与函数放到一个类里) ...
- kafka架构浅显理解
Kafka的概念: 1. AMQP协议 Advanced Message Queuing Protocol (高级消息队列协议) The Advanced Message Queuing Protoc ...
- 继续JS之DOM对象二
前面在JS之DOM中我们知道了属性操作,下面我们来了解一下节点操作.很重要!! 一.节点操作 创建节点:var ele_a = document.createElement('a');添加节点:ele ...
- 雅礼 noip2018 模拟赛day3 T2
典型的状压思想 设0表示黑球,1表示白球,用一串01序列代表剩下的球的状态,记f[i]表示在i状态下取球的最大期望 那么可以利用记忆化搜索更新,每一层枚举可能拿走的球然后向下搜索,同时记忆化即可 在状 ...
- https请求抛出异常
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;// SecurityProtocolType.Tls1.2;
- salt上编写了备份日志的脚本
我在salt上编写了备份日志的脚本,在/opt/CardServer下的主程序目录只保留当天的日志,/opt/log_del目录会保存7天的日志.salt * state.sls script.log ...
- mongodb 安装时错误
1.安装MongoDB进度条长时间不动 根据在网上搜的步骤安装mongoDB到这步,就基本上卡死不动,在网上查到的办法是死等,等了半个小时,但运气不好半个小时也不一定安装成功. 如果进行到这步,卡死在 ...
- C# 位移运算
一:“<<”和“>>”运算符用于执行移位运算,分别称为左移位运算符和右移位运算符.对于X<<N和X>>N形式的运算,含义是将X向左或向右移动N位,得到的 ...