博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
高效使用hibernate-validator校验框架
阅读量:6805 次
发布时间:2019-06-26

本文共 11500 字,大约阅读时间需要 38 分钟。

一、前言

  高效、合理的使用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();    ......}

  请自行参考:

五、validator基本使用

@RestControllerpublic class TestValidatorController {    @RequestMapping("/test/validator")    public void test(@Validated BaseMessageRequestBean bean){
... }}

  这种使用方式有一个弊端,不能自定义返回异常。spring如果验证失败,则直接抛出异常,一般不可控。

六、借助BindingResult

@RestControllerpublic class TestValidatorController {    @RequestMapping("/test/validator")    public void test(@Validated BaseMessageRequestBean bean, BindingResult result){        result.getAllErrors();        ...    }}

  如果方法中有BindingResult类型的参数,spring校验完成之后会将校验结果传给这个参数。通过BindingResult控制程序抛出自定义类型的异常或者返回不同结果。

七、全局拦截校验器

  当然了,需要在借助BindingResult的前提下...

@Aspect@Componentpublic class ControllerValidatorAspect {    @Around("execution(* com.*.controller..*.*(..)) && args(..,result)")    public Object doAround(ProceedingJoinPoint pjp, result result) {        result.getFieldErrors();        ...    }}

  这种方式可以减少controller层校验的代码,校验逻辑统一处理,更高效。

 八、借助ValidatorUtils工具类

@Beanpublic 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。

@Componentpublic 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
validateResultProcess(Object obj) { Set
> results = validator.validate(obj); if (CollectionUtils.isEmpty(results)) { return Optional.empty(); } StringBuilder sb = new StringBuilder(); for (Iterator
> 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) 直接获取校验的结果。

  请参考更多功能的。

九、自定义校验器

  定义一个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)@Documentedpublic @interface LogicValidate {    String message() default "校验异常";    //分组    Class
[] groups() default {}; Class
[] payload() default {};}

  2、自定义校验器LogicValidator,泛型要关联上自定义的注解和需要校验bean的类型

public class LogicValidator implements ConstraintValidator
{ @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
cache = new ConcurrentReferenceHashMap
(); @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国际化相关知识请参考:

转载地址:http://uwnwl.baihongyu.com/

你可能感兴趣的文章
Oracle 10g新增列方式指定HINT
查看>>
RAC 环境下参数文件(spfile)管理
查看>>
Tomcat优化
查看>>
Linux系统启动过程故障排查
查看>>
linux下常用命令
查看>>
canvas drag 实现拖拽拼图小游戏
查看>>
返回一个首尾相连的整数数组中最大子数组的和数
查看>>
线程安全和线程不安全的理解
查看>>
工厂方法模式(java,c++,objective-c)
查看>>
CentOS 6.4安装配置LNMP服务器(Nginx+PHP+MySQL)
查看>>
内核同步之自旋锁与读写自旋锁
查看>>
python读取大文件
查看>>
百度官方WordPress收录插件
查看>>
gitlab 的 CI/CD 配置管理 (二)
查看>>
you may safely reboot your system
查看>>
SCP报错
查看>>
mysql(六)
查看>>
安保方案
查看>>
linux 日期时间计算
查看>>
华为93系统交换机配置跨×××路由和跨×××策略路由
查看>>