hibernate-validator如何使用校驗框架
一、前言
高效、合理的使用hibernate-validator校驗框架可以提高程序的可讀性,以及減少不必要的代碼邏輯。接下來會介紹一下常用一些使用方式。
二、常用注解說明
限制 | 說明 |
@Null | 限制只能為null |
@NotNull | 限制必須不為null |
@AssertFalse | 限制必須為false |
@AssertTrue | 限制必須為true |
@DecimalMax(value) | 限制必須為一個不大于指定值的數(shù)字 |
@DecimalMin(value) | 限制必須為一個不小于指定值的數(shù)字 |
@Digits(integer,fraction) | 限制必須為一個小數(shù),且整數(shù)部分的位數(shù)不能超過integer,小數(shù)部分的位數(shù)不能超過fraction |
@Future | 限制必須是一個將來的日期 |
@Max(value) | 限制必須為一個不大于指定值的數(shù)字 |
@Min(value) | 限制必須為一個不小于指定值的數(shù)字 |
@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; //業(yè)務類型 @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區(qū)別
五、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類型的參數(shù),spring校驗完成之后會將校驗結果傳給這個參數(shù)。通過BindingResult控制程序拋出自定義類型的異?;蛘叻祷夭煌Y果。
七、全局攔截校驗器
當然了,需要在借助BindingResult的前提下...
@Aspect @Component public class ControllerValidatorAspect { @Around("execution(* com.*.controller..*.*(..)) && args(..,result)") public Object doAround(ProceedingJoinPoint pjp, result result) { result.getFieldErrors(); ... } }
這種方式可以減少controller層校驗的代碼,校驗邏輯統(tǒng)一處理,更高效。
八、借助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進行通信。這非常方便,因為您不必在工廠執(zhí)行另一個調(diào)用,假設您幾乎總是會使用默認的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運行時兼容,有一個特別說明:如果你想調(diào)用BV 2.0的getClockProvider()方法,通過#unwrap(ValidatorFactory.class)獲取本機ValidatorFactory,在那里調(diào)用返回的本機引用上的getClockProvider()方法。Spring的MVC配置命名空間也使用此類,如果存在javax.validation API但未配置顯式Validator。
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參數(shù)
2、controller方法中需要校驗的參數(shù)也不需要加入@Valid或者@Validated注解
怎么樣是不是又省去了好多代碼,開不開心。
具體使用,在controller方法或者全局攔截校驗器中調(diào)用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; ... }
實現(xiàn)自定義校驗邏輯也很簡單......
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,泛型要關聯(lián)上自定義的注解和需要校驗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文件。
這里就不進行過多關于國際化相關信息的介紹了,肯定少不了區(qū)域解析器。springboot國際化相關知識請參考:Spring Boot國際化(i18n)
到此這篇關于hibernate-validator如何使用校驗框架的文章就介紹到這了,更多相關hibernate-validator校驗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Boot 整合 Apache Dubbo的示例代碼
Apache Dubbo是一款高性能、輕量級的開源 Java RPC 框架,這篇文章主要介紹了Spring Boot 整合 Apache Dubbo的方法,本文通過示例說明給大家講解的非常詳細,需要的朋友可以參考下2021-07-07詳談Servlet和Filter的區(qū)別以及兩者在Struts2和Springmvc中的應用
下面小編就為大家?guī)硪黄斦凷ervlet和Filter的區(qū)別以及兩者在Struts2和Springmvc中的應用。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08SpringBoot中MybatisX插件的簡單使用教程(圖文)
MybatisX 是一款基于 IDEA 的快速開發(fā)插件,方便在使用mybatis以及mybatis-plus開始時簡化繁瑣的重復操作,本文主要介紹了SpringBoot中MybatisX插件的簡單使用教程,感興趣的可以了解一下2023-06-06