相信很多小夥伴在用國際化配置文件來配置錯誤消息提示,但總是無法映射到自己配置的消息,樓主也折騰了一下午,因爲網上的大量配置都是基於XML的,但樓主用的是基於Java的配置,其實原理是一樣的,樓主走了不少彎路,接下來和大家分享下。
首先我們通過Spring的源代碼來分析下Valid的實現流程,當前端控制器器發現控制器有@Valid這個 annotation時,會去找實現這個annotion的類,定義這個annotion的接口在org.springframwork.validation這個包裏,在這個包裏,定義這個annotation的是 Validator這個接口,另外spring還定義了一個SmartValidator這個接口,這個接口繼承了原始的Valiatoto接口,我也不明白他爲什麼要這樣做.我們可以看到, SpringValidatorAdapter這個類實現了這個接口,看名字,我們就知道它用了適配器的模式,所以整個大邏輯就在這個類裏了,如果我們自定義了 validation類,那麼Spring就會用我們自定義的bean,否則就用Spring自己的校驗類,具體校驗邏輯我們就不看,校驗完後,結果會被交給DataBinder,這個類實現了Spring的Error接口,Error類定義了對應錯誤信息的處理機制 ,它根據errorCode去Valid Bean 中綁定的ResourceMessgae去找信息,這樣Spring就會通過key來找到對應的信息。如果我們沒有自定義Valid類時,Spring會用它自己的校驗類,處理邏輯是相同,只不過它自己會從類的根目錄下去找ValidationMessages.propertis,這個名字是它自己定義的,如果沒有找到,他就會返回defaultMessage,也就是我在校驗規則時寫的錯誤信息。
好了,原理粗略的瞭解下,那麼核心就是要讓Spring使用我們自定義的ResourceMessage.剛開始我只是在RootConfig中定義了自己的校驗類,並且配置了ResourceMessage.
@Bean public LocalValidatorFactoryBean localValidatorFactoryBean() { LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); localValidatorFactoryBean.setProviderClass(HibernateValidator.class); localValidatorFactoryBean.setValidationMessageSource(ResourceBundleMessageSource()); return localValidatorFactoryBean; } @Bean public ResourceBundleMessageSource ResourceBundleMessageSource() { ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); resourceBundleMessageSource.setBasename("i18n/ValidationMessages"); resourceBundleMessageSource.setCacheSeconds(60); resourceBundleMessageSource.setDefaultEncoding("UTF-8"); return resourceBundleMessageSource; }
可是無論我怎麼配置都映射不到國際化信息,機智的我就想是不是Spring根本就沒用我的類,於是我就在根目錄下寫了個properties文件,果然映射了,爲什麼他沒有用我定義的類那,其實很簡單,我只是定義這個類,並沒有地方用啊。所以我們要讓Spring去用這個類.SpringMvc部分的配置代碼繼承了 WebMvcConfigureAdapter 這個類,在這個類中我找了原因。
@Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { } /** * {@inheritDoc} * <p>This implementation returns {@code null} */ @Override public Validator getValidator() { return null; } /** * {@inheritDoc} * <p>This implementation is empty. */ @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { } /** * {@inheritDoc} * <p>This implementation is empty. */ @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { }
SpringMvc會通過 getValidator來獲取自定義的類,而如果我們沒有覆寫這個方法的時候,它直接就返回null了,那它就用自己默認的校驗類了,所以我們必須將自定義的校驗Bean 返回給SpringMvc,問題終於解決了,其實這個問題還是自己對Spring基於java配置不太熟悉造成的,其實基於XML的配置也一樣,雖然你定義了校驗Bean,但還是要告知Spring的,也就是 <mvc:annotation-driven validator="xxxxx"> 這個配置,有了這個配置,springMvc纔會用你自己定義的處理類。總結一句話就是,大家配置好校驗Bean時,一定要讓SpringMvc知道,因爲Spring獲取校驗類是從SpringMVC中獲取的,其實Valid本來就屬於SpringMvc的,而我錯誤的理解認爲在Spring中,只要定義,了校驗Bean,Spring在初始化時,就自動替換了默認的校驗類。好了,貼粗自己的基於Java配置SpringMvc部分代碼(並不是Spring 和 Servlet的配置代碼,只是針對SpringMVC的配置代碼)。
@Configuration @EnableWebMvc @ComponentScan("org.gameloft.www") public class RestServiceConfiguration extends WebMvcConfigurerAdapter { private static Logger logger = LogManager.getLogger(RestServiceConfiguration.class); public RestServiceConfiguration() { logger.info("Init RestService "); } /** * 註冊攔截器 * @param registry */ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RequestProcessingTimeInterceptor()); } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Override public Validator getValidator() { logger.info("Init Validator "); return localValidatorFactoryBean(); } @Bean public LocalValidatorFactoryBean localValidatorFactoryBean() { LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); localValidatorFactoryBean.setProviderClass(HibernateValidator.class); localValidatorFactoryBean.setValidationMessageSource(ResourceBundleMessageSource()); return localValidatorFactoryBean; } @Bean public ResourceBundleMessageSource ResourceBundleMessageSource() { ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); resourceBundleMessageSource.setBasename("i18n/ValidationMessages"); resourceBundleMessageSource.setCacheSeconds(60); resourceBundleMessageSource.setDefaultEncoding("UTF-8"); return resourceBundleMessageSource; } }