一、前言
高效、合理的使用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格式 |
三、定义校验分组
1
2
3
4
5
6
7
8
9
10
|
public class ValidateGroup { public interface FirstGroup { } public interface SecondeGroup { } public interface ThirdGroup { } } |
四、定义校验Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@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基本使用
1
2
3
4
5
6
7
|
@RestController public class TestValidatorController { @RequestMapping ( "/test/validator" ) public void test( @Validated BaseMessageRequestBean bean){ ... } } |
这种使用方式有一个弊端,不能自定义返回异常。spring如果验证失败,则直接抛出异常,一般不可控。
六、借助BindingResult
1
2
3
4
5
6
7
8
|
@RestController public class TestValidatorController { @RequestMapping ( "/test/validator" ) public void test( @Validated BaseMessageRequestBean bean, BindingResult result){ result.getAllErrors(); ... } } |
如果方法中有BindingResult类型的参数,spring校验完成之后会将校验结果传给这个参数。通过BindingResult控制程序抛出自定义类型的异常或者返回不同结果。
七、全局拦截校验器
当然了,需要在借助BindingResult的前提下...
1
2
3
4
5
6
7
8
9
|
@Aspect @Component public class ControllerValidatorAspect { @Around ( "execution(* com.*.controller..*.*(..)) && args(..,result)" ) public Object doAround(ProceedingJoinPoint pjp, result result) { result.getFieldErrors(); ... } } |
这种方式可以减少controller层校验的代码,校验逻辑统一处理,更高效。
八、借助ValidatorUtils工具类
1
2
3
4
|
@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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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字段需要我们自定义校验逻辑。
1
2
3
4
5
6
7
8
9
10
|
@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 属性指向该注解对应的自定义校验器
1
2
3
4
5
6
7
8
9
10
11
|
@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的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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国际化信息配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
@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如何使用校验框架的文章就介绍到这了,更多相关hibernate-validator校验内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!
原文链接:https://www.cnblogs.com/hujunzheng/p/9952563.html