搭建 Spring , Spring MVC , Mybatis , freemarker 等集成开发环境,为了增加数据校验和国际化花了好几天的时间 ( 其实可以手动编写校验代码,或者使用 spring mvc自带校验但不怎么好,故实现 hibernate validation) ,本来如果阅读源代码会可能更快,但是没有时间静下心来阅读,只有工作间隙中去网上查资料,但是尝试了很多次都没能成功,直到今天发现了 Spring mvc 集成 hibernate 的 validation 框架的潜规则后才算成功, Spring MVC 校验有两种: 1 是继承org.springframework.validation.Validator 类, 2 是 使用 JSR303 规范 Hibernate Validator ,下面讲第 2 种实现。
前提 spring 和 spring mvc 是 3.2.5 版, hibernate 的 validation 是 4.2.0 版, mybatis是 3.2.3 , freemarker 是 2.3.20 版(这里再废话下,网上找了一堆全是抄来抄去,就算抄也先验证 ok 后再转发)。这里主要采用的是 spring mvc 与 freemarker 结合做web 页面传入到 controller 的数据验证及国际化验证消息。目前我验证 freemarker 的 ftl文件有两种方式: (1) 结合 spring mvc 的 spring.tld 和 spring-form.tld 标签库实现; (2)结合 spring mvc 的 spring.ftl 宏定义文件实现。
两种实现都需要配合错误消息文件,消息文件: A 自定义消息文件名, B 使用hibernate 的默认的消息文件名,它是在 hibernate-validator-4.2.0.Final.jar 包中的/org/hibernate/validator/ValidationMessages.properties 中。建议使用 hibernate 的默认名称。
以下把两种共同的配置如下
1. web.xml 的配置
< web-app xmlns = "http://java.sun.com/xml/ns/javaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version = "3.0" >
< display-name > mwsys-shop </ display-name >
< context-param >
< param-name > contextConfigLocation </ param-name >
< param-value > /WEB-INF/classes/config/spring/*.xml </ param-value >
</ context-param >
< context-param >
< param-name > log4jConfigLocation </ param-name >
< param-value > /WEB-INF/classes/log4j.properties </ param-value >
</ context-param >
< context-param >
< param-name > webAppRootKey </ param-name >
< param-value > mwsys-shop </ param-value >
</ context-param >
< listener >
< listener-class > freemarker.ext.jsp.EventForwarding </ listener-class >
</ listener >
< listener >
< listener-class >
org.springframework.web.context.ContextLoaderListener
</ listener-class >
</ listener >
< listener >
< listener-class >
org.springframework.web.util.Log4jConfigListener
</ listener-class >
</ listener >
< servlet >
< servlet-name > remoting </ servlet-name >
< servlet-class > org.springframework.web.servlet.DispatcherServlet </ servlet-class >
< init-param >
< param-name > contextConfigLocation </ param-name >
< param-value > /WEB-INF/classes/config/spring/spring-mvc-main.xml </param-value >
</ init-param >
< load-on-startup > 1 </ load-on-startup >
</ servlet >
< servlet-mapping >
< servlet-name > remoting </ servlet-name >
< url-pattern > /* </ url-pattern >
</ servlet-mapping >
</ web-app >
2. spring mvc 配置 spring-mvc-main.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<context:component-scan base-package="com.maowu.shop.view" />
<mvc:annotation-driven />
<!-- FreeMarker configuration -->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/ftl/" />
<property name="freemarkerVariables">
<map>
<entry key="xml_escape" value-ref="fmXmlEscape" />
<entry key="BASEPATH" value="http://localhost:8080/shop/"></entry>
<entry key="IMGBASEPATH" value="http://localhost:8080/shop/res/file/"></entry>
<entry key="RESBASEPATH" value="../../"></entry>
</map>
</property>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="contentType" value="text/html;charset=UTF-8" />
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="suffix" value=".ftl" />
</bean>
<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape" />
<!-- exception handling configuration -->
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 定义默认的异常处理页面,当该异常类型的注册时使用 -->
<property name="defaultErrorView" value="error/error" />
<!-- 定义异常处理页面用来获取异常信息的变量名,默认名为 exception -->
<property name="exceptionAttribute" value="ex" />
<!-- 全局异常记录到日志中,若 warnLogCategory 不为空, spring 就会使用apache 的 org.apache.commons.logging.Log 日志工具,记录这个异常级别是 warn -->
<property name="warnLogCategory" value="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" />
<property name="exceptionMappings">
<map>
<entry key="RemoteAccessException" value="error/remote_error" />
</map>
</property>
</bean>
<!-- bind i18n messages properties,this just setting zh_CN message -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="useCodeAsDefaultMessage" value="false"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="fallbackToSystemLocale" value="true"/>
<property name="basenames">
<list>
<value>classpath:bundle/messages</value>
<value>classpath:bundle/ValidationMessages</value>
</list>
</property>
</bean>
<!-- setting validation implementor,actually HibernateValidator is java validation interface default implementor-->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<property name="validationMessageSource" ref="messageSource"/>
</bean>
</beans>
3. UserVo 的源代码
import javax.validation.constraints.Max;
import javax.validation.constraints.Size;
public class UserVo
{
@Size (min = 2, max = 6, message = "{size.u.name.len}" )
private String name ;
@Max (value = 1, message = "{Max.sex}" )
private int sex ;
public String getName()
{
return name ;
}
public void setName(String name)
{
this . name = name;
}
public int getSex()
{
return sex ;
}
public void setSex( int sex)
{
this . sex = sex;
}
@Override
public String toString()
{
return String. format ( "UserVo [name=%s, sex=%s]" , name , sex );
}
}
4. 需要把 spring.tld 和 spring-form.tld 两个文件加入 webapp 的目录下
5. ValidationMessages.properties 国际化消息(校验的错误消息)
size.u.name.len=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A\u957F\u5EA6\u5728{ min}\u548C{max}\u4E4B\u95F4
Max.sex= \u8D85\u8FC7\u6700\u5927\u503C{value}
6. messages.properties 国家化消息(非校验的错误消息)
u.reg.welcome=\u6B22\u8FCE\u6CE8\u518C
7. 成功后的 user_reg_ok.ftl 页面:
数据没问题,注册成功!
以下是不同部分
spring.ftl 结合 Hibernate Validator
1 ) Controller 的代码
@RequestMapping (value = "user/reg0" , method = RequestMethod. GET )
public String reg0(ModelMap model)
{
UserVo userVo = new UserVo();
model.addAttribute( "userVo" , userVo);
return "user/user_reg0" ;
}
@RequestMapping (value = "user/reg0" , method = RequestMethod. POST )
public String reg0( @Valid @ModelAttribute UserVo userVo, BindingResult result, ModelMap model)
{
System. out .println(userVo.toString());
if (result.hasErrors())
{
return "user/user_reg0" ;
}
else
{
return "user/user_reg_ok" ;
}
}
2 ) user_reg0.ftl 代码
<#import "/spring.ftl" as spring />
<html>
<style>
.error {
color: #ff0000;
font-weight: bold;
}
</style>
<body>
<div><br/></div>
<@spring.message code= "u.reg.welcome" />
<br/>
<form method= "POST" action= "/shop/user/reg0" id= "user_validator" >
<@spring.bind "userVo.name" />
<input type= "input" name= "name" value= "" />
<@spring.showErrors "<br/>" />
<@spring.bind "userVo.sex" />
<input type= "input" name= "sex" value= "" />
<input type= "submit" value= " 提交 " />
<@spring.showErrors "<br/>" />
</form>
</body>
</html>
第二种: 使用 spring.tld 和 spring-form.tld 结合 Hibernate Validator
1) Controller 代码
@RequestMapping (value = "user/reg1" , method = RequestMethod. GET )
public String reg1(ModelMap model)
{
return "user/user_reg1" ;
}
@RequestMapping (value = "user/reg1" , method = RequestMethod. POST )
public String reg1( @Valid @ModelAttribute UserVo userVo, BindingResult result, ModelMap model)
{
System. out .println(userVo.toString());
if (result.hasErrors())
{
return "user/user_reg1" ;
}
else
{
return "user/user_reg_ok" ;
}
}
2) user_reg1.ftl 代码文件
<#assign spring=JspTaglibs[ "/WEB-INF/spring.tld" ]/>
<#assign form=JspTaglibs[ "/WEB-INF/spring-form.tld" ]/>
<html>
<style>
.error {
color: #ff0000;
font-weight: bold;
}
</style>
<body>
<@spring.message code= "u.reg.welcome" />
<div><br/></div>
<@form.form method= "POST" action= "/shop/user/reg1" commandName="userVo" id= "user_validator" >
<input type= "input" name= "name" value= "" />
<@form.errors path = "name" />
<input type= "input" name= "sex" value= "" />
<@form.errors path = "sex" />
<input type= "submit" value= " 提交 " />
</@form.form>
</body>
</html>
以上就是两种配置的实际可以运行的源码,调试时注意各文件的存放路径。
以下是对上面的两种的总结:
验证错误消息文件名字:是默认名 ValidationMessages.properties ,编译后存放在classes 目录下则:消息 key 名可以自定义,消息内容可以包含参数(如上面代码中的“ {min} ”)
验证错误消息文件名字:是自定义名 ErrorMessages.properties ,编译后存放在classes 目录下则:消息 key 名不可自定义,需用 hibernate validation 的消息 key 格式(下面会讲解),消息内容不可包含参数(如上面代码中的“ {min} ”)
验证错误消息文件名字:是默认名 ValidationMessages.properties ,编译后不放在classes 目录下则:消息 key 名不可自定义,需用 hibernate validation 的消息 key 格式(下面会讲解),消息内容不可包含参数(如上面代码中的“ {min} ”)
如果消息内容中包含参数则会报: java.lang.IllegalArgumentException: can't parse argument number: xxx 异常。( xxx 是参数名)
以下是对 hibernate validation 默认的错误消息文件及默认错误消息键值说明:
默认的提供的错误消息文件名如下:
ValidationMessages.properties
ValidationMessages_de.properties
ValidationMessages_en.properties
ValidationMessages_es.properties
ValidationMessages_fr.properties
ValidationMessages_hu.properties
ValidationMessages_mn_MN.properties
ValidationMessages_pt_BR.properties
ValidationMessages_tr.properties
ValidationMessages_zh_CN.properties
默认的错误消息 Key :验证约束注解的全限定类名 .message ,默认将为验证的对象自动生成如下错误消息键:
验证错误注解简单类名 . 验证对象名 . 字段名
验证错误注解简单类名 . 字段名
验证错误注解简单类名 . 字段类型全限定类名
验证错误注解简单类名
使用的优先级是:从高到低,即最前边的具有最高的优先级,而且以上所有默认的错误消息键优先级高于自定义的错误消息键。
总结:一般来说消息 key 是要自定义的,而且消息内容可以传参数,所以我们使用默认文件名且存放在 classes 目录下。