服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - hibernate-validator如何使用校验框架

hibernate-validator如何使用校验框架

2022-11-12 13:45胡峻峥 Java教程

高效、合理的使用hibernate-validator校验框架可以提高程序的可读性,以及减少不必要的代码逻辑,本文主要介绍了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格式

三、定义校验分组

?
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

延伸 · 阅读

精彩推荐
  • Java教程Java中的反射机制基本运用详解

    Java中的反射机制基本运用详解

    这篇文章主要介绍了Java 反射机制原理与用法,结合实例形式详细分析了Java反射机制的相关概念、原理、基本使用方法及操作注意事项,需要的朋友可以参考...

    刘瑜澄7092021-12-10
  • Java教程我从jdk1.8升级到jdk11所遇到的坑都有这些

    我从jdk1.8升级到jdk11所遇到的坑都有这些

    这篇文章主要介绍了从jdk1.8升级到jdk11将会遇到的一些坑,本文给大家分享解决方案对大家的学习或工作具有参考借鉴价值,对jdk1.8升级到jdk11相关知识感兴...

    AI码师5232021-11-22
  • Java教程Spring Cloud Eureka服务治理的实现

    Spring Cloud Eureka服务治理的实现

    服务治理是微服务框架中最为核心和基础的模块,它主要是用来实现各个微服务实例的自动化注册与发现。这篇文章主要介绍了Spring Cloud Eureka服务治理的实...

    薛定饿7012021-05-08
  • Java教程Java集合总结

    Java集合总结

    今天小编就为大家分享一篇关于Java集合总结,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...

    sdr_zd12642021-06-27
  • Java教程JDK 16:Java 16需要了解的新功能

    JDK 16:Java 16需要了解的新功能

    可以在jdk.java.net上找到适用于Linux,Windows和MacOS的JDK 16的早期版本。与JDK 15一样,JDK 16将是一个短期版本,支持六个月。将于2021年9月发布的JDK 17将是一个长...

    JDON4892020-12-08
  • Java教程Java获取当前时间戳案例详解

    Java获取当前时间戳案例详解

    这篇文章主要介绍了Java获取当前时间戳案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...

    割肉机10352021-11-14
  • Java教程SpringBoot整合JWT的入门指南

    SpringBoot整合JWT的入门指南

    JWT全称是json web token,它将用户信息加密到 token 里,服务器不保存任何用户信息,服务器通过使用保存的密钥验证 token 的正确性,只要正确即通过验证,这篇文章...

    一位不透露姓名的先生4672021-09-18
  • Java教程入门java的第一步HelloWorld

    入门java的第一步HelloWorld

    这篇文章主要介绍了入门java的第一步-Hello,World,文中通过示例代码介绍的非常详细,对大家的java初步学习具有一定的学习价值,需要的朋友可以参考下...

    xxh-我很喜欢你7542021-09-04