Spring MVC中使用MessageSource默認是寫在properties文件當中,以支持國際化。
但很多時候我們需要把數據寫到數據庫當中,而不是在properties文件當中,以方便日常維護。
1、先看Spring配置
<!-- 默認的注解映射的支持 --> <mvc:annotation-driven validator="validator" conversion-service="conversionService" /> <!-- 資源文件 --> <bean id="propertiesMessageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>resource</value> <value>validation</value> </list> </property> </bean> <bean id="databaseMessageSource" class="com.obs2.util.MessageResource"> <property name="parentMessageSource" ref="propertiesMessageSource"/> </bean> <bean id="messageInterpolator" class="com.obs2.util.MessageResourceInterpolator"> <property name="messageResource" ref="databaseMessageSource"/> </bean> <!-- 驗證器 --> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="messageInterpolator" ref="messageInterpolator"/> </bean>
這里定義了一個propertiesMessageSource,一個databaseMessageSourcer,和一個messageInterpolator。
propertiesMessageSource用於讀取properties文件
databaseMessageSourcer用於讀取數據庫的數據配置,其中,有一個屬性設置它的父MessageSource為propertiesMessageSource。意思是如果數據庫找不到對應的數據,到properties文件當中查找。
messageInterpolator是個攔截器。
2、數據庫的POJO定義:
package com.obs2.dao.impl.bean; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @SuppressWarnings("serial") @Table(name="resource") public class Resource implements Serializable { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="resource_id") private long resourceId; @Column(name="name", length=50, nullable=false) private String name; @Column(name="text", length=1000, nullable=false) private String text; @Column(name="language", length=5, nullable=false) private String language; public long getResourceId() { return resourceId; } public void setResourceId(long resourceId) { this.resourceId = resourceId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } }
定義了一張表[resource],字段有:[resource_id]、[name]、[text]、[language]
具體的DAO、Service操作方法這里忽略不寫了。
3、讀取數據庫的MessageResource類
package com.obs2.util; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.annotation.Resource; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.support.AbstractMessageSource; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import com.obs2.service.ResourceService; /** * 取得資源數據 * @author Robin * */ public class MessageResource extends AbstractMessageSource implements ResourceLoaderAware { @SuppressWarnings("unused") private ResourceLoader resourceLoader; @Resource private ResourceService resourceService; /** * Map切分字符 */ protected final String MAP_SPLIT_CODE = "|"; protected final String DB_SPLIT_CODE = "_"; private final Map<String, String> properties = new HashMap<String, String>(); public MessageResource() { // reload(); } public void reload() { properties.clear(); properties.putAll(loadTexts()); } protected Map<String, String> loadTexts() { Map<String, String> mapResource = new HashMap<String, String>(); List<com.obs2.service.bean.Resource> resources = resourceService.findAll(); for (com.obs2.service.bean.Resource item : resources) { String code = item.getName() + MAP_SPLIT_CODE + item.getLanguage(); mapResource.put(code, item.getText()); } return mapResource; } private String getText(String code, Locale locale) { String localeCode = locale.getLanguage() + DB_SPLIT_CODE + locale.getCountry(); String key = code + MAP_SPLIT_CODE + localeCode; String localeText = properties.get(key); String resourceText = code; if(localeText != null) { resourceText = localeText; } else { localeCode = Locale.ENGLISH.getLanguage(); key = code + MAP_SPLIT_CODE + localeCode; localeText = properties.get(key); if(localeText != null) { resourceText = localeText; } else { try { if(getParentMessageSource() != null) { resourceText = getParentMessageSource().getMessage(code, null, locale); } } catch (Exception e) { logger.error("Cannot find message with code: " + code); } } } return resourceText; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader()); } @Override protected MessageFormat resolveCode(String code, Locale locale) { String msg = getText(code, locale); MessageFormat result = createMessageFormat(msg, locale); return result; } @Override protected String resolveCodeWithoutArguments(String code, Locale locale) { String result = getText(code, locale); return result; } }
主要是重載AbstractMessageSource和ResourceLoaderAware,以實現Spring MVC的MessageSource國際化調用。
類中的reload()方法,我把它寫到了一個ServletListener當中,讓項目啟動時,自動加載數據到static的map中。
4、這是Listener:
package com.obs2.util; import javax.servlet.ServletContext; import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.HttpSessionEvent; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; /** * 系統啟動監聽 * @author Robin * */ public class SystemListener implements ServletContextListener { /** * context初始化時激發 */ @Override public void contextInitialized(ServletContextEvent e) { //------------------------------------------------------------ // 取得ServletContext //------------------------------------------------------------ ServletContext context = e.getServletContext(); WebApplicationContext applicationContext = WebApplicationContextUtils .getWebApplicationContext(context); //------------------------------------------------------------ // 設置國際化多語言 //------------------------------------------------------------ MessageResource messageSource = applicationContext.getBean(MessageResource.class); messageSource.reload(); } /** * context刪除時激發 */ @Override public void contextDestroyed(ServletContextEvent e) { } /** * 創建一個 session時激發 * @param e */ public void sessionCreated(HttpSessionEvent e) { } /** * 當一個 session失效時激發 * @param e */ public void sessionDestroyed(HttpSessionEvent e) { } /** * 設置 context的屬性,它將激發attributeReplaced或attributeAdded方法 * @param e */ public void setContext(HttpSessionEvent e) { } /** * 增加一個新的屬性時激發 * @param e */ public void attributeAdded(ServletContextAttributeEvent e) { } /** * 刪除一個新的屬性時激發 * @param e */ public void attributeRemoved(ServletContextAttributeEvent e) { } /** * 屬性被替代時激發 * @param e */ public void attributeReplaced(ServletContextAttributeEvent e) { } }
當然了,Listener需要加入到web.xml當中:
<!-- 系統啟動監聽 --> <listener> <listener-class>com.obs2.util.SystemListener</listener-class> </listener>
4、Interceptor攔截器
package com.obs2.util; import java.text.MessageFormat; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import javax.annotation.Resource; import javax.validation.MessageInterpolator; import org.springframework.binding.message.MessageBuilder; /** * 攔截Annotation驗證信息 * @author Robin * */ public class MessageResourceInterpolator implements MessageInterpolator { @Resource private MessageResource messageResource; public void setMessageResource(MessageResource messageResource) { this.messageResource = messageResource; } @Override public String interpolate(String messageTemplate, Context context) { String messageTemp = null; if(messageTemplate.startsWith("{") && messageTemplate.endsWith("}")) { messageTemp = messageTemplate.substring(1, messageTemplate.length() - 1); } else { return messageTemplate; } String[] params = (String[]) context.getConstraintDescriptor().getAttributes().get("params"); MessageBuilder builder = new MessageBuilder().code(messageTemp); if (params != null) { for (String param : params) { builder = builder.arg(param); } } String result = builder.build().resolveMessage(messageResource, Locale.ENGLISH).getText(); return result; } @Override public String interpolate(String messageTemplate, Context context, Locale locale) { String messageTemp = null; if(messageTemplate.startsWith("{") && messageTemplate.endsWith("}")) { messageTemp = messageTemplate.substring(1, messageTemplate.length() - 1); } else { return messageTemplate; } String[] params = (String[]) context.getConstraintDescriptor().getAttributes().get("params"); MessageBuilder builder = new MessageBuilder().code(messageTemp); if (params != null) { builder = builder.args(params); } String result = builder.build().resolveMessage(messageResource, locale).getText(); return result; } }
事實上,不使用攔截器,上面的數據庫MessageSource類已經可以工作了。但在使用hibernate的Annotation的validator時,不加入攔截器,是不行的,它不會觸發。
這類里調用了一個jar包:spring-binding-2.3.1.RELEASE.jar
自行上網搜搜下載。
一切完成,測試下,寫一個domain entity:
package com.obs2.controller.bean; import javax.validation.constraints.Min; import org.hibernate.validator.constraints.NotEmpty; import com.obs2.controller.validator.UserName; public class Account { @UserName(format="^[\\w_]+$", message="{valid.userName}", min=6, max=30) private String userName; @NotEmpty(message="{valid.required}") private String password; @Min(value=18, message="{valid.ageMin}") private int age; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
寫一個簡單的view(這里使用的是freemarker)
<#assign c=JspTaglibs["http://java.sun.com/jsp/jstl/core"] /> <#assign fmt=JspTaglibs["http://java.sun.com/jsp/jstl/fmt"] /> <#assign fn=JspTaglibs["http://java.sun.com/jsp/jstl/functions"] /> <#assign st=JspTaglibs["http://www.springframework.org/tags"] /> <#assign form=JspTaglibs["http://www.springframework.org/tags/form"] /> <head> <title>Login</title> </head> <body> <@form.form action="${request.contextPath}/passport/login" method="post" modelAttribute="account"> User name:<@form.input path="userName"/><@form.errors path="userName"/><br/> Password:<@form.password path="password"/><@form.errors path="password" /><br/> Age:<@form.input path="age"/><@form.errors path="age" /><br/> <input type="submit" value="Login" /> </@form.form> </body>
將看到:
這種方法還有一個小BUG,就是在使用annotation validator時,如果使用了替換符,似乎不起作用。暫時沒研究出來,誰搞出來了,麻煩留個言提示下。so thx.
代碼中直接調用,可以這樣寫:
resource.getMessage("valid.ageMin", new Object[]{"age",18}, request.getLocale())