記錄一個使用Hibernate Validator驗證信息參數化遇到的問題


Hibernate Validator框架支持驗證信息的參數化。

以Length注解為例:

public class User
{
    private String name;

    @Length(message="{MSG_W00001}",min=1,max=10)
    public String getName(){
    return name;
    }
}

配置信息如下所示:

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="messages/message" />
    <property name="useCodeAsDefaultMessage" value="true" />
</bean>
    
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
    <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
    <property name="validationMessageSource" ref="messageSource"/>
</bean>

MessageResource:

MSG_W00001 = length must be between {min} and {max}

當name不滿足Length驗證時,期待的驗證信息應該是:
length must be between 1 and 10
但實際上驗證信息卻是:
length must be between min and max

網上搜索一番也沒找到答案,只好自己動手解決問題。在Hibernate Validator(ver 4.3.2)默認是依賴ResourceBundleMessageInterpolator類來獲得最終的驗證信息,核心函數如下:

private String interpolateMessage(String message, Map<String, Object> annotationParameters, Locale locale) {
    LocalisedMessage localisedMessage = new LocalisedMessage( message, locale );
    String resolvedMessage = null;

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

    // if the message is not already in the cache we have to run 
//step 1-3 of the message resolution
if ( resolvedMessage == null ) { ResourceBundle userResourceBundle = userResourceBundleLocator .getResourceBundle( locale ); ResourceBundle defaultResourceBundle = defaultResourceBundleLocator .getResourceBundle( locale ); String userBundleResolvedMessage; resolvedMessage = message; boolean evaluatedDefaultBundleOnce = false; do { // search the user bundle recursive (step1) userBundleResolvedMessage = replaceVariables( resolvedMessage, userResourceBundle, locale, true ); // exit condition - we have at least tried to validate
//against the default bundle and there was no
// further replacements if ( evaluatedDefaultBundleOnce && !hasReplacementTakenPlace( userBundleResolvedMessage, resolvedMessage ) ) { break; } // search the default bundle non recursive (step2) resolvedMessage = replaceVariables( userBundleResolvedMessage,
defaultResourceBundle, locale, false ); evaluatedDefaultBundleOnce = true; } while ( true ); } // cache resolved message if ( cacheMessages ) { String cachedResolvedMessage =
resolvedMessages.putIfAbsent( localisedMessage, resolvedMessage );
if ( cachedResolvedMessage != null ) { resolvedMessage = cachedResolvedMessage; } } // 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; }

可以看到step 4正是我們期待的把{min}和{max}替換成Length里min和max的處理,可是為什么不起作用呢?
理論上驗證信息應該經歷以下變化:
{MSG_W00001} -> length must be between {min} and {max} -> length must be between 1 and 10


可是通過調試卻發現在step 4處理前驗證信息變成了下面的樣子:
length must be between min and max
這說明step1-3的處理有問題。具體來看step1-3的處理主要通過replaceVariables和resolveParameter方法來實現:

private String replaceVariables(String message, ResourceBundle bundle, Locale locale, boolean recurse) {
    Matcher matcher = MESSAGE_PARAMETER_PATTERN.matcher( message );
    StringBuffer sb = new StringBuffer();
    String resolvedParameterValue;
    while ( matcher.find() ) {
        String parameter = matcher.group( 1 );
        resolvedParameterValue = resolveParameter(
                parameter, bundle, locale, recurse
        );

        matcher.appendReplacement( sb, Matcher.quoteReplacement( resolvedParameterValue ) );
    }
    matcher.appendTail( sb );
    return sb.toString();
}

private String resolveParameter(String parameterName, ResourceBundle bundle, Locale locale, boolean recurse) {
    String parameterValue;
    try {
        if ( bundle != null ) {
            parameterValue = bundle.getString( removeCurlyBrace( parameterName ) );
            if ( recurse ) {
                parameterValue = replaceVariables( parameterValue, bundle, locale, recurse );
            }
        }
        else {
            parameterValue = parameterName;
        }
    }
    catch ( MissingResourceException e ) {
        // return parameter itself
        parameterValue = parameterName;
    }
    return parameterValue;
}

resolveParameter方法里,recurse被設置為ture,所以會遞歸的查找和替換參數。
首先{MSG_W00001}把替換成length must be between {min} and {max}。
然后會繼續在MessageResource中查找min和max。根據resolveParameter方法如果找不到則會被替換。顯然我們
在MessageResource沒有定義min和max,但是問什么{min}和{max}會被替換成min和max呢?
問題出在<property name="useCodeAsDefaultMessage" value="true" />這個設置上,當找不到min和max對應的值時bundle.getString函數
返回了它的參數,而不是拋出MissingResourceException異常,導致{min}和{max}會被替換成min和max。
把配置修改成<property name="useCodeAsDefaultMessage" value="false" />,驗證信息就變成了我們所期待的"length must be between 1 and 10"。

 

至此這個問題就解決了。可是感覺Hibernate Validator在這個問題上處理有很問題,很難會讓人聯想到useCodeAsDefaultMessage這個設定會
對驗證信息的參數化造成影響。ResourceBundleMessageInterpolator應該不依賴於全局的useCodeAsDefaultMessage設定才更合理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM