高效使用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.pswp.cn/news/531212.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/531212.shtml
英文地址,請注明出處:http://en.pswp.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;實時推…