Java for Web学习笔记(七七):Validation(1)启动验证
2017-09-30 15:40
645 查看
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个部分定义Validator,也就是Spring validator bean,为Validator进行消息本地化
方法验证的处理器
在Spring MVC使用同一验证bean
步骤1:在root上下文中设置Spring validator bean
在RootContextConfiguration中:@Bean public 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) */ @Bean public 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; @Override public Validator getValidator() { return this.validator; }
相关文章相关链接:
我的Professional Java for Web Applications相关文章
相关文章推荐
- Java for Web学习笔记(七八):Validation(2)验证标记
- Java for Web学习笔记(七九):Validation(3)自定义验证限制
- Java for Web学习笔记(二一):EL(1)什么是EL
- Java for Web学习笔记(二七):JSTL(3)Core Tag(中)
- Java for Web学习笔记(二十):Session(4)在集群中使用Session
- Java for Web学习笔记(四四):WebSocket(1)演化历程
- Java for Web学习笔记(五一):Log(3)代码中使用log4j2
- Java for Web学习笔记(五二):Spring框架简介(1)特点简述
- Java for Web学习笔记(三八):自定义tag(6)一些注意
- 【学习笔记】JavaWeb 服务启动时,在后台启动加载一个线程
- Java for Web学习笔记(四五):WebSocket(2)JavaScript Client
- Java for Web学习笔记(九):Servlet(7)上传文件
- Java for Web学习笔记(四六):WebSocket(3)Java Server
- Java for Web学习笔记(四一):Filter(3)用于Log
- Java for Web学习笔记(十六):JSP(6)jspx
- Java for Web学习笔记(八):Servlet(6)doGet()和doPost()是线程还是队列
- Java for Web学习笔记(五六):Spring框架简介(5)自动识别
- Java for Web学习笔记(六):Servlet(4)HttpServletResponse
- Java for Web学习笔记(二九):JSTL(5)FMT Tag(上)
- Java for Web学习笔记(二四):EL(4)流(Stream)