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

来源:互联网 发布:数控车床电脑自动编程 编辑:程序博客网 时间:2024/06/05 01:46

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

    博客分类: 
  • springmvc杂谈
  • java开发常见问题分析
 

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

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

 

举个例子:

比如我的验证

Java代码  收藏代码
  1. @Length(min = 5, max = 200, message = "{message.title.length.not.valid}")  
  2. @Column(name = "title")  
  3. private String title;  

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

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

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

 

这到底是怎么工作的呢? 

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

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

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

Java代码  收藏代码
  1. public void setValidationMessageSource(MessageSource messageSource) {  
  2.     this.messageInterpolator = HibernateValidatorDelegate.buildMessageInterpolator(messageSource);  
  3.    }   
Java代码  收藏代码
  1. /** 
  2.      * Inner class to avoid a hard-coded Hibernate Validator 4.1+ dependency. 
  3.      */  
  4.     private static class HibernateValidatorDelegate {  
  5.   
  6.         public static MessageInterpolator buildMessageInterpolator(MessageSource messageSource) {  
  7.             return new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(messageSource));  
  8.         }  
  9.     }  

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

 

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

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

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

Java代码  收藏代码
  1. private Map<String, Object> buildAnnotationParameterMap(Annotation annotation) {  
  2.         final Method[] declaredMethods = ReflectionHelper.getDeclaredMethods( annotation.annotationType() );  
  3.         Map<String, Object> parameters = new HashMap<String, Object>( declaredMethods.length );  
  4.         for ( Method m : declaredMethods ) {  
  5.             try {  
  6.                 parameters.put( m.getName(), m.invoke( annotation ) );  
  7.             }  
  8.             catch ( IllegalAccessException e ) {  
  9.                 throw log.getUnableToReadAnnotationAttributesException( annotation.getClass(), e );  
  10.             }  
  11.             catch ( InvocationTargetException e ) {  
  12.                 throw log.getUnableToReadAnnotationAttributesException( annotation.getClass(), e );  
  13.             }  
  14.         }  
  15.         return Collections.unmodifiableMap( parameters );  
  16.     }  

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

Java代码  收藏代码
  1. private String interpolateMessage(String message, Map<String, Object> annotationParameters, Locale locale)   

具体实现思路如下:

 

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

Java代码  收藏代码
  1. if ( cacheMessages ) {  
  2.     resolvedMessage = resolvedMessages.get( localisedMessage );  
  3. }  

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

 

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

Java代码  收藏代码
  1. ResourceBundle userResourceBundle = userResourceBundleLocator  
  2.                 .getResourceBundle( locale );  
  3.         ResourceBundle defaultResourceBundle = defaultResourceBundleLocator  
  4.                 .getResourceBundle( locale );   

 

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

Java代码  收藏代码
  1. // search the user bundle recursive (step1)  
  2. userBundleResolvedMessage = replaceVariables(  
  3.     resolvedMessage, userResourceBundle, locale, true  
  4. );  

转义的包括:

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

 

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

a=hello {b}  

b=123

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

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

 

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

Java代码  收藏代码
  1. // search the default bundle non recursive (step2)  
  2. resolvedMessage = replaceVariables( userBundleResolvedMessage, defaultResourceBundle, locale, false );  
  3. evaluatedDefaultBundleOnce = true;  

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

Java代码  收藏代码
  1. // resolve annotation attributes (step 4)  
  2. resolvedMessage = replaceAnnotationAttributes( resolvedMessage, annotationParameters );  
  3.   
  4. // last but not least we have to take care of escaped literals  
  5. resolvedMessage = resolvedMessage.replace( "\\{""{" );  
  6. resolvedMessage = resolvedMessage.replace( "\\}""}" );  
  7. resolvedMessage = resolvedMessage.replace( "\\\\", "\\" );  
  8. return resolvedMessage;  

如之前说的

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

消息:

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

 

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

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

 

此处有一个小问题:

如果你的messageSource添加了:

Java代码  收藏代码
  1. <property name="useCodeAsDefaultMessage" value="true"/>  

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

 

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

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

 

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

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

 

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

0 0
原创粉丝点击