`
jinnianshilongnian
  • 浏览: 21434720 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2405078
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:2997748
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5631488
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:257577
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1593183
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:248974
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5847568
Group-logo
跟我学Nginx+Lua开...
浏览量:698167
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:780468
社区版块
存档分类
最新评论

springmvc集成JSR-303的解析消息文件的默认实现浅析

 
阅读更多

springmvc如何集成JSR-303进行数据验证在之前的如下文章中已经介绍过了:

SpringMVC数据验证——第七章 注解式控制器的数据验证、类型转换及格式化——跟着开涛学SpringMVC

 

举个例子:

比如我的验证

@Length(min = 5, max = 200, message = "{message.title.length.not.valid}")
@Column(name = "title")
private String title;

有朋友想得到min、max及此时的title值,可以在消息文件中通过:

写道
message.content.length.not.valid=内容长度必须在{min}到{max}个字符之间

当然也可以使用{value} 获取此时的title值

 

这到底是怎么工作的呢? 

在JSR-303中,使用javax.validation.MessageInterpolator来解析消息,而如果:

    <!-- 以下 validator  ConversionService 在使用 mvc:annotation-driven 会 自动注册-->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        <!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->
        <property name="validationMessageSource" ref="messageSource"/>
    </bean>

即此时使用的hibernate实现,注入了spring的messageSource来解析消息时:

public void setValidationMessageSource(MessageSource messageSource) {
    this.messageInterpolator = HibernateValidatorDelegate.buildMessageInterpolator(messageSource);
   } 
/**
	 * Inner class to avoid a hard-coded Hibernate Validator 4.1+ dependency.
	 */
	private static class HibernateValidatorDelegate {

		public static MessageInterpolator buildMessageInterpolator(MessageSource messageSource) {
			return new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(messageSource));
		}
	}

   即内部委托给了org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator#ResourceBundleMessageInterpolator:

 

并使用如下代码解析消息:

public String interpolate(String message, Context context) {
		// probably no need for caching, but it could be done by parameters since the map
		// is immutable and uniquely built per Validation definition, the comparison has to be based on == and not equals though
		return interpolateMessage( message, context.getConstraintDescriptor().getAttributes(), defaultLocale );
	}

此处可以看到context.getConstraintDescriptor().getAttributes(),其作用是获取到注解如@Length上的所有数据,具体代码实现如下:

private Map<String, Object> buildAnnotationParameterMap(Annotation annotation) {
		final Method[] declaredMethods = ReflectionHelper.getDeclaredMethods( annotation.annotationType() );
		Map<String, Object> parameters = new HashMap<String, Object>( declaredMethods.length );
		for ( Method m : declaredMethods ) {
			try {
				parameters.put( m.getName(), m.invoke( annotation ) );
			}
			catch ( IllegalAccessException e ) {
				throw log.getUnableToReadAnnotationAttributesException( annotation.getClass(), e );
			}
			catch ( InvocationTargetException e ) {
				throw log.getUnableToReadAnnotationAttributesException( annotation.getClass(), e );
			}
		}
		return Collections.unmodifiableMap( parameters );
	}

循环每一个方法 并获取值放入map,接着进入方法:

private String interpolateMessage(String message, Map<String, Object> annotationParameters, Locale locale) 

具体实现思路如下:

 

1、首先查询缓存中是否存在,如果存在直接获取缓存中解析的消息:

if ( cacheMessages ) {
    resolvedMessage = resolvedMessages.get( localisedMessage );
}

2、如果没有,按照JSR-303规定的使用三步获取:

 

首先委托给ResourceBundle获取消息值:

	ResourceBundle userResourceBundle = userResourceBundleLocator
					.getResourceBundle( locale );
			ResourceBundle defaultResourceBundle = defaultResourceBundleLocator
					.getResourceBundle( locale ); 

 

2.1、委托给用户定义的resourceBundle进行解析(即我们之前指定的messageSource),递归的查找消息并替换那些转义的:

// search the user bundle recursive (step1)
userBundleResolvedMessage = replaceVariables(
    resolvedMessage, userResourceBundle, locale, true
);

转义的包括:

\\{、\\}、\\\\。

 

所谓递归的查找意思就是如:

a=hello {b}  

b=123

会在解析a时再递归解析b,如果{b}就是一个字符串,而不想被解析,可以通过\\{b\\}转移完成;

替换完转义字符后,还是会再递归的查找下去。

 

2.2、使用默认的resourceBundle(即默认找org.hibernate.validator.ValidationMessages.properties)按照和2.1一样的步骤执行:

// search the default bundle non recursive (step2)
resolvedMessage = replaceVariables( userBundleResolvedMessage, defaultResourceBundle, locale, false );
evaluatedDefaultBundleOnce = true;

2.3、解析完成后,接着替换注解变量值:

// resolve annotation attributes (step 4)
resolvedMessage = replaceAnnotationAttributes( resolvedMessage, annotationParameters );

// last but not least we have to take care of escaped literals
resolvedMessage = resolvedMessage.replace( "\\{", "{" );
resolvedMessage = resolvedMessage.replace( "\\}", "}" );
resolvedMessage = resolvedMessage.replace( "\\\\", "\\" );
return resolvedMessage;

如之前说的

@Length(min = 5, max = 200, message = "{message.title.length.not.valid}")

消息:

标题长度必须在{min}到{max}个字符之间

 

那么,如果没有在之前的resourceBundle中得到替换,那么会被注解的值替换掉。

即得到标题长度必须在5到200个字符之间。

 

此处有一个小问题:

如果你的messageSource添加了:

<property name="useCodeAsDefaultMessage" value="true"/>

意思就是如果找不到key对应的消息,则使用code作为默认消息;这样会引发一个问题就是,根据code找消息,永远能找到,即不可能成功执行【2.3】。

 

如“标题长度必须在{min}到{max}个字符之间”,如果消息文件中没有min 和 max,实际得到的是:

”标题长度必须在min到max个字符之间“,不是我们期望的;

 

如“标题长度必须在\\{min\\}到max个字符之间”,实际也会获取到:

”标题长度必须在min到max个字符之间“,也不是我们期望的。

 

所以实际使用时useCodeAsDefaultMessage应该为false。

 

4
4
分享到:
评论
8 楼 Janle 2017-03-05  
<!-- 国际化的消息资源文件 -->
    <mvc:annotation-driven validator="validator"/>
    <!-- 以下 validator  ConversionService 在使用 mvc:annotation-driven 会 自动注册-->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        <!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->
        <property name="validationMessageSource" ref="messageSource"/>
    </bean>
    <mvc:interceptors>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
            <property name="paramName" value="language"/>
        </bean>
    </mvc:interceptors>
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
        <property name="cookieName" value="language"/>
        <property name="cookieMaxAge" value="3600"/>
        <property name="defaultLocale" value="zh_CN"/>
    </bean>

    <bean id="validateMessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basename" value="classpath:messages/validationMessages"></property>
        <property name="useCodeAsDefaultMessage" value="false"/>
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="cacheSeconds" value="60"/>
    </bean>

具体validationMessages_zh_CN.properties配置
user.username.illegal=用户名格式不正确
password.length.illegal=密码[${validatedValue}]长度必须为{min}到{max}个字符

在运行时候直接弹出错误的是{user.username.illegal},并没有解析出message中指定的配置错误信息
大神帮忙看看什么情况
7 楼 jinnianshilongnian 2013-10-08  
jluwang 写道
jinnianshilongnian 写道
jluwang 写道
你好,关于validationEngine有个问题想请教一下
如果一个表单的输入绕过了前台验证,但是没通过spring mvc的JSR303校验,并且将错误信息记录到BindingResult对象中,那么如何用validationEngine的方式提示BindingResult里面的信息?


你可以参考
https://github.com/zhangkaitao/es/blob/master/web/src/main/webapp/WEB-INF/tags/showFieldError.tag



https://github.com/zhangkaitao/es/blob/master/web/src/main/webapp/WEB-INF/tags/showGlobalError.tag


不好意思,现在才看到,代码很好用,非常感谢!!

不过还是遇到了一点小问题,validationEngine其中有个showOneMessage不起作用(好像官方demo就不起作用)

另外由于jquery.validationEngine-zh_CN.js文件里面无法用el获取jsp的变量,导致进行ajax校验的时候没有方便的办法去传递额外的参数
我想到的办法是要么把这里面的js代码放到一个jsp文件里,用<%@include%>包含进去.要么就不用validationEngine,还是沿用jquery的ajax..不知道您有没有更方便的途径处理ajax校验

我使用@include包含的,通过ajax没必要 ajax的话 你可以看看es, 里边可以直接返回错误消息的
6 楼 jluwang 2013-09-29  
jinnianshilongnian 写道
jluwang 写道
你好,关于validationEngine有个问题想请教一下
如果一个表单的输入绕过了前台验证,但是没通过spring mvc的JSR303校验,并且将错误信息记录到BindingResult对象中,那么如何用validationEngine的方式提示BindingResult里面的信息?


你可以参考
https://github.com/zhangkaitao/es/blob/master/web/src/main/webapp/WEB-INF/tags/showFieldError.tag



https://github.com/zhangkaitao/es/blob/master/web/src/main/webapp/WEB-INF/tags/showGlobalError.tag


不好意思,现在才看到,代码很好用,非常感谢!!

不过还是遇到了一点小问题,validationEngine其中有个showOneMessage不起作用(好像官方demo就不起作用)

另外由于jquery.validationEngine-zh_CN.js文件里面无法用el获取jsp的变量,导致进行ajax校验的时候没有方便的办法去传递额外的参数
我想到的办法是要么把这里面的js代码放到一个jsp文件里,用<%@include%>包含进去.要么就不用validationEngine,还是沿用jquery的ajax..不知道您有没有更方便的途径处理ajax校验
5 楼 jinnianshilongnian 2013-09-16  
jluwang 写道
你好,关于validationEngine有个问题想请教一下
如果一个表单的输入绕过了前台验证,但是没通过spring mvc的JSR303校验,并且将错误信息记录到BindingResult对象中,那么如何用validationEngine的方式提示BindingResult里面的信息?


你可以参考
https://github.com/zhangkaitao/es/blob/master/web/src/main/webapp/WEB-INF/tags/showFieldError.tag



https://github.com/zhangkaitao/es/blob/master/web/src/main/webapp/WEB-INF/tags/showGlobalError.tag
4 楼 jluwang 2013-09-16  
你好,关于validationEngine有个问题想请教一下
如果一个表单的输入绕过了前台验证,但是没通过spring mvc的JSR303校验,并且将错误信息记录到BindingResult对象中,那么如何用validationEngine的方式提示BindingResult里面的信息?
3 楼 EricLiang 2013-09-02  
jinnianshilongnian 写道

我前台验证使用validationEngine; jsr303只提供后台验证

validationEngine这个很好,后来想了一下,前后台校验还是要分开的,一个后台可能会有多个前台(web、android)
2 楼 jinnianshilongnian 2013-08-29  
EricLiang 写道
关于JSR303校验,采用spring mvc后有个遗憾一直未能解决:如何统一前后台的校验,后台使用JSR303很好,如果需要前台校验岂不是再重新实现一遍,在springmvc 框架下面能不能实现前后台统一校验?并且可以配置校验模式:前台、后台、前后台。
初步有个想法:页面上使用ajax请求提交,在校验时想通过后台controller来处理(使用JSR303校验),问题是前台如何写?以前使用JSR303校验都是页面使用spring form标签同步提交的.
看了你的很多系列文章很有收获,所以向你请教一下

我前台验证使用validationEngine; jsr303只提供后台验证
1 楼 EricLiang 2013-08-28  
关于JSR303校验,采用spring mvc后有个遗憾一直未能解决:如何统一前后台的校验,后台使用JSR303很好,如果需要前台校验岂不是再重新实现一遍,在springmvc 框架下面能不能实现前后台统一校验?并且可以配置校验模式:前台、后台、前后台。
初步有个想法:页面上使用ajax请求提交,在校验时想通过后台controller来处理(使用JSR303校验),问题是前台如何写?以前使用JSR303校验都是页面使用spring form标签同步提交的.
看了你的很多系列文章很有收获,所以向你请教一下

相关推荐

Global site tag (gtag.js) - Google Analytics