1.spring为java项目提供国际化支持
message_zh_CN.properties(中文简体)
message_en_US.properties(美国英文)
等等中配置key=value
key可以是错误提示信息,页面显示信息等等。
使用配置的配置
@Autowired
private MessageSource messageSource;
messageSource.getMessage(key,null,"default value",Locale.getDefault()); //如果没有配置,会返回默认值
messageSource.getMessage(key,null,Locale.getDefault()); //如果没有配置会抛出异常
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
2.上述使用缺点
上述国际化使用适合做小型的web项目,配置key不多,或者项目的使用国家或地区很少。
如果配置项非常多的话,那么properties文件将会非常大,而且配置项非常多。如果支持全世界所有国家和地区时,那么就要有许多个properties文件,能够使用,但相对比较麻烦。
针对上述缺点,能否将上述的配置key及value写入数据中了?
3.具体解决方案
在数据库中定义表,表中基础字段国家或地区代表符号,如zh_CN(简体中文),en_US(美国英语)等,key及value,然后再增加一个basename,这个字段是参考spring源码做的,下面详细讲解。
创建sql
CREATE TABLE t_messages (
`basename` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`locale_lang` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`k` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`v` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ;
basename基础文件目录
locale_lan国家或地区代表符号
k key值
v value值
a方案
对待该表就想对待普通的业务表,针对该表建立MessageMapper文件,创建针对该表的增删改查方法,在service层控制事务管理及缓存等等,当任何一个controller需要国际化返回结果的时候,就可以通过调MessageService方法来得到对应的value.
此处mapper里面的方法仅仅是举例,详细的可根据项目需求自己修改
@Mapper
public interface MessageMapper {
String columnList = " basename,locale_lan,k,v ";
@Insert("insert into t_message(" +columnList+") values(#{basename},#{locale_lan},#{k},#{v})")
boolean insertMessage(Message message);
@Select("select " + columnList + "from t_message where basename = #{basename} and locale_lan = #{language}")
AddressEntity getMessage(String basename,String language);
}
b方案
a方案是一种实现方法,但是spring既然已经实现properties文件的具体用法,对spring进行二次开发,开发适合自己项目的业务,岂不是更好吗?
spring 实现国际化源码简单分析
MessageSource接口
public interface MessageSource {
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}
HierarchicalMessageSource接口
其继承了MessageSource接口,增加了父MessageSource的设置方法
public interface HierarchicalMessageSource extends MessageSource {
void setParentMessageSource(MessageSource parent);
MessageSource getParentMessageSource();
}
DelegatingMessageSource类
该类主要是委派父MessageSource处理getMessage();
public class DelegatingMessageSource extends MessageSourceSupport implements HierarchicalMessageSource;
@Override
public String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
if (this.parentMessageSource != null) {
return **this.parentMessageSource.getMessage(code, args, defaultMessage, locale);**
}
else {
**return renderDefaultMessage(defaultMessage, args, locale);**
}
}
@Override
public String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException {
if (this.parentMessageSource != null) {
**return this.parentMessageSource.getMessage(code, args, locale);**
}
else {
throw new NoSuchMessageException(code, locale);
}
}
@Override
public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
if (this.parentMessageSource != null) {
**return this.parentMessageSource.getMessage(resolvable, locale);**
}
else {
if (resolvable.getDefaultMessage() != null) {
**return renderDefaultMessage(resolvable.getDefaultMessage(), resolvable.getArguments(), locale);**
}
String[] codes = resolvable.getCodes();
String code = (codes != null && codes.length > 0 ? codes[0] : null);
throw new NoSuchMessageException(code, locale);
}
}
MessageSourceSupport 该类为获取字符串后的格式化处理的类
private final Map<String, Map<Locale, MessageFormat>> messageFormatsPerMessage = new HashMap();
格式化存储的结构类型,大Map的key为basename,嵌套的Map,key为Locale,value为MessageFormat,不同地区可以设置不同的格式化样式
核心方法:return messageFormat.format(this.resolveArguments(code, locale));
this.resolveArguments(code, locale)方法获取对应的value
AbstractMessageSource为MessageSource接口的核心实现类
核心方法的实现方法都是final的,不允许重写
public final String getMessage(String code, Object[] args, String defaultMessage, Locale locale) ;
public final String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
AbstractResourceBasedMessageSource继承了AbstractMessageSource类
AbstractResourceBasedMessageSource的实现类有
1.ResourceBundleMessageSource
2.ReloadableResourceBundleMessageSource
3.ReloadableResourceBundleMessageSource
关注的主要是ResourceBundleMessageSource类
当调用ResourceBundleMessageSource 对象的getMessage()方法时候
其会调用其的protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
return ResourceBundle.getBundle(basename, locale, this.getBundleClassLoader(), new ResourceBundleMessageSource.MessageSourceControl());
}方法,所以重写该方法可以实现自我配置的类。
b方案具体实现,具体的查库方法没有贴出来
public class DatabaseAndResourceBundleMessageSource
extends ResourceBundleMessageSource {
private static final Logger log = LoggerFactory.getLogger(DatabaseAndResourceBundleMessageSource2.class);
@Autowired
DataSource dataSource;
@Autowired
MessageDBHandler databaseMessageHandler;
@Override
protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
try {
ResourceBundle delegate = super.doGetBundle(basename, locale);//父类MessageSource实现
return new ResourceBundle() {
@Override
protected Object handleGetObject(String key) {
return Optional.<Object>ofNullable(databaseMessageHandler.loadMessageFromDb(basename, locale.getLanguage(), key)).orElseGet(() -> delegate.getObject(key)); //依据key获取value
}
@Override
public Enumeration<String> getKeys() {
log.info("getKeys");
Set<String> l = databaseMessageHandler.loadKeysFromDb(basename, locale.getLanguage());
l.addAll(delegate.keySet());
return Collections.enumeration(l);
}
};
} catch (MissingResourceException e) {
return new ResourceBundle() {
@Override
protected Object handleGetObject(String key) {
return databaseMessageHandler.loadMessageFromDb(basename, locale.getLanguage(), key);
}
@Override
public Enumeration<String> getKeys() {
return Collections.enumeration(databaseMessageHandler.loadKeysFromDb(basename, locale.getLanguage())); //获取同一basename和language下的所有keys
}
};
}
}
}
@Configuration
public class MessageConfig {
@Value("message.basename:app/notice,app/mail")
private String[] baseNames;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Bean("messageSource")
public MessageSource myMessageSource(){
ResourceBundleMessageSource messageSource = new DatabaseAndResourceBundleMessageSource();
messageSource.setBasenames(baseNames);
return messageSource;
}
}
上述描述了一大堆,不好理解,下面的方便理解
spring解析properties文件使用的是Map<String,Map<Locale,ResourceBundle>>数据结构,ResourceBundle类中有Set<String> set = new HashSet<>();
这个set就是封装properties中对应的key-value,在ResourceBundleMessageSource对象中doGetBundle()方法中就是获取ResourceBundle对象的,所以重写doGetBundle()方法,就可以将数据库中的key-value加入到已经存在的key-value中了。如果仅仅以语言进行区别,可以使用Locale.getDefault().getLanguage()即可,如果想以国家和地区来区分就可以使用Locale.getDefault().toString(),两者是有区别的。