Java for Web学习笔记(七七):Validation(1)启动验证

来源:互联网 发布:c语言字符串变量赋值 编辑:程序博客网 时间:2024/05/16 02:13

Bean validation和Hibernate Validator

我们经常要验证数据的输入和输出是否合规,例如不允许为null,不允许为空,对于email地址,其正则表达式如下:

/* 正则表达式: * ^[a-z0-9`!#$%^&*'{}?/+=|_~-] : ^ 匹配输入字符串的开始位置 []表示匹配内部的任何一个 * + 表示前面的表达式出现1次或者多次 * (\\.[a-z0-9`!#$%^&*'{}?/+=|_~-]+) : ()  匹配这一pattern并获取这一pattern *                                     \\. .在正则表达式里面的\.,因为放在string里面,所以是\\. * * 表示前面的出现0次或者多次 * @  * ? 表示前面的子表达式零次或一次。 * ([a-z0-9]([a-z0-9-]*[a-z0-9])?) :? 匹配前面的子表达式零次或一次 *  $ 匹配输入字符串的结束位置 * */String regexp = "^[a-z0-9`!#$%^&*'{}?/+=|_~-]+(\\.[a-z0-9`!#$%^&*'{}?/+=|_~-]+)*"                 + "@"                 + "([a-z0-9]([a-z0-9-]*[a-z0-9])?)+(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$";

J2EE提供Bean validation为验证提供方便。JSR-303是Bean validation的v1.0规范,在Java EE 6中使用,它定义了annotation和API,JSR-349是Bean validation的v1.1规范,在Java EE 7中用。我们在http://beanvalidation.org/网站上看到最新版本的是v2.0,即JSR380,计划在Java EE 8中提供。在V2.0中,对email的的验证,可以直接通过annotation了。标记方式简化代码,下面是v2.0的范例。

import javax.validation.constraints.Email;import javax.validation.constraints.NotNull;public class User {    private String email;    @NotNull @Email    public String getEmail() {        return email;    }    public void setEmail(String email) {        this.email = email;    }    }
JSR-349只是规范,需要具体的实现,一般使用Hibernate Validator。Hibernate Validator是Bean validation的灵感来源,所以走在Hibernate Validator之前,v5.0支持JSR-349,现在到6.0.2.Final版本,支持Bean Validation 2.0,即JSR-380。我们需要在pom.xml中引入:
<!-- Bean Validation API: 使用v2.0 --><dependency>    <groupId>javax.validation</groupId>    <artifactId>validation-api</artifactId>    <version>2.0.0.Final</version>    <scope>compile</scope></dependency><!-- 对Bean Validation API v2.0的具体实现 --><dependency>    <groupId>org.hibernate</groupId>    <artifactId>hibernate-validator</artifactId>    <version>6.0.2.Final</version>    <scope>runtime</scope></dependency>

手动验证的小例子

前面给出User类,要求email非null,并符合email的格式,下面给出一个小例子,在代码中验证User对象的合法性。
public void test(){ValidatorFactory factory = Validation.buildDefaultValidatorFactory();Validator validator = factory.getValidator();User user = new User();Set<ConstraintViolation<User>> violations = validator.validate(user);if(violations.size() > 0){violations.forEach(v -> logger.error(v));throw new ConstraintViolationException(violations);}return "abc";}
这里显然违反了email不能为null的要求,执行的时候会抛出异常:
javax.validation.ConstraintViolationException: email: 不能为null
我们将具体的ConstraintViolation打印出来:
ConstraintViolationImpl{interpolatedMessage='不能为null', propertyPath=email, rootBeanClass=class cn.wei.chapter16.site.hr_portal.User, messageTemplate='{javax.validation.constraints.NotNull.message}'}

在Spring框架中配置Validation

手动验证的方式,在代码中会很繁复,给出手动了例子,主要是了解一下Bean validation是如何工作的,这些都应该能够自动进行。在Spring框架中配置Validation包括以下3个部分

  1. 定义Validator,也就是Spring validator bean,为Validator进行消息本地化
  2. 方法验证的处理器
  3. 在Spring MVC使用同一验证bean

步骤1:在root上下文中设置Spring validator bean

在RootContextConfiguration中:

@Beanpublic MessageSource messageSource(){    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();    messageSource.setCacheSeconds(-1); //Cache forever    messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());    messageSource.setBasenames("/WEB-INF/i18n/messages", "/WEB-INF/i18n/titles","/WEB-INF/i18n/validation");    return messageSource;}/* 1)定义Spring validator bean。    Spring提供了两个Bean,LocalValidatorFactoryBean和CustomValidatorBean。    LocalValidatorFactoryBean :This is the central class for javax.validation (JSR-303) setup in a Spring application context: It bootstraps a javax.validation.ValidationFactory and exposes it through the Spring org.springframework.validation.Validator interface as well as through the JSR-303 javax.validation.Validator interface and the javax.validation.ValidatorFactory interface itself.     LocalValidatorFactoryBean支持javax.validation.ValidationFactory接口,也支持javax.validation.Validator接口,由于它extends SpringValidatorAdapter,因此也支持org.springframework.validation.Validator接口,属于N合一的Bean。    org.springframework.validation.Validator提供validate(Object target, org.springframework.validation.Errors errors)接口,将错误输出到Errors。下面是一个controller方法的例子,对页面的form的输入数据进行验证,如果错误,存放到Errors中:    public ModelAndView createEmployee(Map<String, Object> model,@Valid EmployeeForm form, Errors errors)*/@Beanpublic LocalValidatorFactoryBean localValidatorFactoryBean(){    LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();    /* 1.1)自动寻找Validator实现。    LocalValidatorFactoryBean自动检查在classpath中的Bean Validation的实现,将javax.validation.ValidatorFactory作为其缺省备选,本例将自动找到Hibernate Validator。但是如果在classpath下面有超过一个实现(例如运行在完全的J2EE web应用服务器,如GlassFish或WebSphere),这时通过下面方式指定采用哪个,以避免不可测性。    validator.setProviderClass(HibernateValidator.class);    但这样的缺点在于是complie的而不是runtime的。要runtime,可以采用    validator.setProviderClass(Class.forName("org.hibernate.validator.HibernateValidator"));    但如果类写错了,无法在compile的时候查出 */    // validator.setProviderClass(Class.forName("org.hibernate.validator.HibernateValidator"));     /* 1.2)为Validator进行消息本地化    缺省的使用classpath路径下的ValidationMessages.properties, ValidationMessages_[language].properties, ValidationMessages_[language]_[region].properties),但在Bean validation1.1开始,可以自行提供国际化方式。*/    validator.setValidationMessageSource(this.messageSource());    return validator;}

本地化验证的小例子

我们将前面手动验证的代码做一点小修改,测试一下本地化的情况。
public class User {    //... ...    @NotNull(message = "{validate.user.notnull}") //在WEB-INF/i18n/中进行相关的设置    @Email(message = "{validate.user.email}")    public String getEmail() {        return email;    }}
已经在Root上下文中配置了localValidatorFactoryBean,可以直接注入使用。
@Inject LocalValidatorFactoryBean validator;public void test2(){User user = new User();user.setEmail("abc");Set<ConstraintViolation<User>> violations = validator.validate(user);if(violations.size() > 0){violations.forEach(v -> logger.error(v));throw new ConstraintViolationException(violations);}}
我们看到输出log为
ConstraintViolationImpl{interpolatedMessage='要求电子邮件的格式', propertyPath=email, rootBeanClass=class cn.wei.chapter16.site.hr_portal.User, messageTemplate='{validate.user.email}'}
interpolatedMessage已经根据messageTemplate,根据locale进行了本地化,抛出的异常为:
javax.validation.ConstraintViolationException: email: 要求电子邮件的格式

步骤2:在root上下文中设置方法验证

Bean Validation使用@javax.validation.Constraint标记或者自定义标记在允许在field、方法和方法的参数。

  • field:当调用对象的一个受验方法时,验证器对该field时进行检验。
  • method:将在方法执行后检查该方法的返回值。如果我们加在一个getter上,和加载field上的效果一样。
  • method parameter:在方法前检查输出的参数。

对于method的输入和输出的限制,一般应在interface上注明,确保实现着和使用者清晰,这就是所谓的PbC(programming by contract)。使用PbC,需要创建一个proxy来验证具体实现的类,相关注入要调用proxy。一个完整的Java EE 7 web应用服务器提供的DI proxied,如果使用简单servlet容器,如tomcat,需要提供其他的DI解决方案。Spring framwork就是其中的解决方案,它的DI(依赖注入,dependency injecttion)解决这个问题。

Spring framework使用bean post-processor的概念在完成startup之前来配置,个性化,甚至替换bean。,设置 BeanPostProcessor的实现将在一个bean注入到其他需要它的bean之前完成。我们接触过的实现有:

  • AutowiredAnnotationBeanPostProcessor:这是framework自动生成的bean,用来扫描@Autowired,@Inject的
  • InitDestroyAnnotationBeanPostProcessor是寻找InitializingBean的实现(@PostConstruct)和DisposableBean的实现(@PreDestroy)
  • AsyncAnnotationBeanPostProcessor是用来替换bean的,寻找带有@Async的方法,替换为proxy,是这些方法可以异步运行

对于方法的输入输出的验证,通过MethodValidationPostProcessor来调用proxy。MethodValidationPostProcessor是一个BeanPostProcessor的实现,和AsyncAnnotationBeanPostProcessor一样,都实现了org.springframework.aop.framework.ProxyConfig,也就是采用proxy来进行替代,但不同的是,这个方法验证后处理器不能由spring自动生产,需要通过代码。

 @Bean public MethodValidationPostProcessor methodValidationPostProcessor(){     MethodValidationPostProcessor processor = new MethodValidationPostProcessor();     processor.setValidator(this.localValidatorFactoryBean());     return processor; }
笔者曾经犯了个低级错误,将类的标记@Configuration写成了@Configurable,一旦执行processor.setValidator(),就会报错。但是比较奇怪的是如果不进行验证validator的配置,似乎也运行流畅。
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'methodValidationPostProcessor' defined in cn.wei.flowingflying.customer_support.config.RootContextConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.validation.beanvalidation.MethodValidationPostProcessor]: Factory method 'methodValidationPostProcessor' threw exception; nested exception is java.lang.IllegalArgumentException: No target ValidatorFactory set

步骤3:在Spring MVC使用同一验证bean

Spring的MVC controller对form对象和参数的验证使用Spring validator对象。LocalValidatorFactoryBean实现了spring validator的api,但缺省地,Spring MVC会创建另一个独立的Spring validator对象。要使用统一bean,我们需要手动进行设置。因为是在MVC中使用,因此必须要加上@EnableWebMvc,否则不能在controller中检查方法的参数输入,即Errors errors都是no error的。

在SerlvetContextConfiguration中重写WebMvcConfigurerAdapter的方法getValidator():

@Inject SpringValidatorAdapter validator;      @Overridepublic Validator getValidator() {    return this.validator;}

相关文章相关链接: 我的Professional Java for Web Applications相关文章

阅读全文
0 0