高效使用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验证注解的元素值是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 {@Overridepublic 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> {@Overridepublic void initialize(LogicValidate logicValidate) {}@Overridepublic 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;@Beanpublic 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>();@Overridepublic 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)

转载于:https://www.cnblogs.com/hujunzheng/p/9952563.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/531212.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kafka-manager配置和使用

kafka-manager配置 最主要配置就是用于kafka管理器状态的zookeeper主机。这可以在conf目录中的application.conf文件中找到。 kafka-manager.zkhosts"my.zookeeper.host.com:2181" 当然也可以声明为zookeeper集群。 kafka-manager.zkhosts"my.zookeeper.host.co…

kafka告警简单方案

一、前言 为什么要设计kafka告警方案&#xff1f;现成的监控项目百度一下一大堆&#xff0c;KafkaOffsetMonitor、KafkaManager、 Burrow等&#xff0c;具体参考&#xff1a;kafka的消息挤压监控。由于本小组的项目使用的kafka集群并没有被公司的kafka-manager管理&#xff0c;…

RedisCacheManager设置Value序列化器技巧

CacheManager基本配置 请参考博文&#xff1a;springboot2.0 redis EnableCaching的配置和使用 RedisCacheManager构造函数 /*** Construct a {link RedisCacheManager}.* * param redisOperations*/ SuppressWarnings("rawtypes") public RedisCacheManager(RedisOp…

Nginx配置以及域名转发

工程中的nginx配置 #user nobody; worker_processes 24; error_log /home/xxx/opt/nginx/logs/error.log; pid /home/xxx/opt/nginx/run/nginx.pid;events {use epoll;worker_connections 102400; }http {include /home/xxx/opt/nginx/conf.d/mime.types;default_…

java接口签名(Signature)实现方案续

一、前言 由于之前写过的一片文章 &#xff08;java接口签名(Signature)实现方案 &#xff09;收获了很多好评&#xff0c;此次来说一下另一种简单粗暴的签名方案。相对于之前的签名方案&#xff0c;对body、paramenter、path variable的获取都做了简化的处理。也就是说这种方式…

支付宝敏感信息解密

支付宝官方解密文档&#xff1a;https://docs.alipay.com/mini/introduce/aes String response "小程序前端提交的";//1. 获取验签和解密所需要的参数 Map<String, String> openapiResult JSON.parseObject(response,new TypeReference<Map<String, St…

HashMap 源码阅读

前言 之前读过一些类的源码&#xff0c;近来发现都忘了&#xff0c;再读一遍整理记录一下。这次读的是 JDK 11 的代码&#xff0c;贴上来的源码会去掉大部分的注释, 也会加上一些自己的理解。 Map 接口 这里提一下 Map 接口与1.8相比 Map接口又新增了几个方法&#xff1a;   …

SpringMvc接口中转设计(策略+模板方法)

一、前言 最近带着两个兄弟做支付宝小程序后端相关的开发&#xff0c;小程序首页涉及到很多查询的服务。小程序后端服务在我司属于互联网域&#xff0c;相关的查询服务已经在核心域存在了&#xff0c;查询这块所要做的工作就是做接口中转。参考了微信小程序的代码&#xff0c;发…

SpringSecurity整合JWT

一、前言 最近负责支付宝小程序后端项目设计&#xff0c;这里主要分享一下用户会话、接口鉴权的设计。参考过微信小程序后端的设计&#xff0c;会话需要依靠redis。相关的开发人员和我说依靠Redis并不是很靠谱&#xff0c;redis在业务高峰期不稳定&#xff0c;容易出现问题&…

Springboot定时任务原理及如何动态创建定时任务

一、前言 上周工作遇到了一个需求&#xff0c;同步多个省份销号数据&#xff0c;解绑微信粉丝。分省定时将销号数据放到SFTP服务器上&#xff0c;我需要开发定时任务去解析文件。因为是多省份&#xff0c;服务器、文件名规则、数据规则都不一定&#xff0c;所以要做成可配置是有…

转载:ThreadPoolExecutor 源码阅读

前言 之前研究了一下如何使用ScheduledThreadPoolExecutor动态创建定时任务(Springboot定时任务原理及如何动态创建定时任务)&#xff0c;简单了解了ScheduledThreadPoolExecutor相关源码。今天看了同学写的ThreadPoolExecutor 的源码解读&#xff0c;甚是NB&#xff0c;必须转…

Spring BPP中优雅的创建动态代理Bean

一、前言 本文章所讲并没有基于Aspectj&#xff0c;而是直接通过Cglib以及ProxyFactoryBean去创建代理Bean。通过下面的例子&#xff0c;可以看出Cglib方式创建的代理Bean和ProxyFactoryBean创建的代理Bean的区别。 二、基本测试代码 测试实体类&#xff0c;在BPP中创建BppTest…

使用pdfBox实现pdf转图片,解决中文方块乱码等问题

一、引入依赖 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.13</version> </dependency> <dependency><groupId>org.apache.pdfbox</groupId><artif…

Spring异步调用原理及SpringAop拦截器链原理

一、Spring异步调用底层原理 开启异步调用只需一个注解EnableAsync Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Import(AsyncConfigurationSelector.class) public interface EnableAsync {/*** Indicate the async annotation type to be detec…

线程池优化之充分利用线程池资源

一、前言 最近做了电子发票的需求&#xff0c;分省开票接口和发票下载接口都有一定的延迟。为了完成开票后自动将发票插入用户微信卡包&#xff0c;目前的解决方案是利用线程池&#xff0c;将开票后插入卡包的任务&#xff08;轮询分省发票接口&#xff0c;直到获取到发票相关信…

Spring MVC源码——Root WebApplicationContext

Spring MVC源码——Root WebApplicationContext 打算开始读一些框架的源码,先拿 Spring MVC 练练手,欢迎点击这里访问我的源码注释, SpringMVC官方文档一开始就给出了这样的两段示例: WebApplicationInitializer示例: public class MyWebApplicationInitializer implements Web…

Spring MVC源码——Servlet WebApplicationContext

上一篇笔记(Spring MVC源码——Root WebApplicationContext)中记录了下 Root WebApplicationContext 的初始化代码.这一篇来看 Servlet WebApplicationContext 的初始化代码 DispatcherServlet 是另一个需要在 web.xml 中配置的类, Servlet WebApplicationContext 就由它来创建…

Springboot源码——应用程序上下文分析

前两篇(Spring MVC源码——Root WebApplicationContext 和 Spring MVC源码——Servlet WebApplicationContext)讲述了springmvc项目创建上下文的过程&#xff0c;这一篇带大家了解一下springboot项目创建上下文的过程。 SpringApplication引导类 SpringApplication类用于启动或…

基于zookeeper实现分布式配置中心(一)

最近在学习zookeeper&#xff0c;发现zk真的是一个优秀的中间件。在分布式环境下&#xff0c;可以高效解决数据管理问题。在学习的过程中&#xff0c;要深入zk的工作原理&#xff0c;并根据其特性做一些简单的分布式环境下数据管理工具。本文首先对zk的工作原理和相关概念做一下…

基于zookeeper实现分布式配置中心(二)

上一篇&#xff08;基于zookeeper实现分布式配置中心&#xff08;一&#xff09;&#xff09;讲述了zookeeper相关概念和工作原理。接下来根据zookeeper的特性&#xff0c;简单实现一个分布式配置中心。 配置中心的优势 1、各环境配置集中管理。 2、配置更改&#xff0c;实时推…