参考文献

Spring国际化使用场景

  • 普通国际化文案
  • Bean Validation校验国际化文案
  • Web站点页面渲染
  • Web MVC错误消息提示

Spring国际化接口

  • 核心接口: org.springframework.context.MessageSource

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public interface MessageSource {
    /**
    * 获取国际化信息
    * @param code 表示国际化资源中的属性名;
    * @param args用于传递格式化串占位符所用的运行期参数;
    * @param defaultMessage 当在资源找不到对应属性名时,返回defaultMessage参数所指定的默认信息;
    * @param locale 表示本地化对象
    */
    @Nullable
    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
    /**
    * 与上面的方法类似,只不过在找不到资源中对应的属性名时,直接抛出NoSuchMessageException 异常
    */
    String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
    /**
    * @param MessageSourceResolvable 将属性名、参数数组以及默认信息封装起来,它的功能和第一个方法相同
    */
    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
    }
  • 主要概念

    • 文案模板编码(code)
    • 文案模板参数(args)
    • 区域(Locale)

层次性MessageSource

  • Spring 层次性接口回顾
    • org.springframework.beans.factory.HierarchicalBeanFactory
    • org.springframework.context.ApplicationContext
    • org.springframework.beans.factory.config.BeanDefinition
  • Spring 层次性国际化接口
    • org.springframework.context.HierarchicalMessageSource

Java国际化标准实现

  • 核心接口

    • 抽象实现: java.util.ResourceBundle
    • Properties资源实现: java.util.PropertyResourceBundle
    • 具体实现: java.util.ListResourceBundle
  • ResourceBundle核心特性

    • Key-Value设计
    • 层次性设计
    • 缓存设计
    • 字符编码控制: java.util.ResourceBundle.Control(@Since 1.6)
    • Control SPI扩展: java.util.spi.ResourceBundleControlProvider(@Since 1.8)

Java文本格式化

  • 核心接口: java.text.MessageFormat
  • 基本用法:
    • 设置消息格式模式: new MessageFormat(...);
    • 格式化: format(new Object[]{...});
  • 消息格式模式:
    • 格式元素: {ArgumentIndex(,FormatType,(FormatStyle))}
    • FormatType: 消息格式类型,可选项,每种类型在number,date,timechoice类型选其一
    • FormatStyle: 消息格式风格, 可选项,可包括: short,medium,long,full,integer,currency,percent
  • 高级特性
    • 重置消息格式模式
    • 重置java.util.Locale
    • 重置java.text.Format

MessageSource开箱即用实现

  • 基于ResourceBundle + MessageFormat组合 MessageSource 实现
    • org.springframework.context.support.ResourceBundleMessageSource
  • 可重载Properties + MessageFormat组合 MessageSource 实现
    • org.springframework.context.support.ReloadableResourceBundleMessageSource
  • 通过编程的方式提供国际化信息
    • org.springframework.context.support.StaticMessageSource

MessageSource内建依赖

  • MessageSource 內建 Bean 可能来源
    • 预注册 Bean 名称为:messageSource,类型为:MessageSource Bean
    • 默认內建实现 - DelegatingMessageSource
      • 层次性查找 MessageSource 对象

Spring中使用国际化的3个步骤

  • 通常在ApplicationContext类型的容器中使用国际化3个步骤
    • 创建国际化文件
    • 向容器中注册一个MessageSource类型的beanbean名称必须为:messageSource
    • 调用AbstractApplicationContext中的getMessage来获取国际化信息,其内部将交给第二步中注册的messageSource名称的bean进行处理

创建国际化文件

  • 国际化文件命名格式:名称_语言_地区.properties

  • message.properties

    1
    2
    name=您的姓名
    personal_introduction=默认个人介绍:{0},{1}
  • message_cn_ZH.properties

    1
    2
    name=姓名
    personal_introduction=个人介绍:{0},{1},{0}
  • message_en_GB.properties

    1
    2
    name=Full name
    personal_introduction=personal_introduction:{0},{1},{0}

Spring中注册国际化的bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;

@Configuration
public class Config {
   @Bean
   public ResourceBundleMessageSource messageSource() {
       ResourceBundleMessageSource result = new ResourceBundleMessageSource();
       //可以指定国际化化配置文件的位置,格式:路径/文件名称,注意不包含【语言_国家.properties】含这部分
       result.setBasenames("com/example/demo/message");
       return result;
  }
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Locale;

public class MessageSourceTest {
   @Test
   public void test1() {
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Config.class);
       context.refresh();
       //未指定Locale,此时系统会取默认的locale对象,本地默认的值中文【中国】,即:zh_CN
       System.out.println(context.getMessage("name", null, null));
       System.out.println(context.getMessage("name", null, Locale.CHINA));
//CHINA对应:zh_CN
       System.out.println(context.getMessage("name", null, Locale.UK)); //UK对应en_GB
 }
}
动态参数使用
1
2
3
4
5
6
7
8
9
10
11
@Test
public void test2() {
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
   context.register(MainConfig1.class);
   context.refresh();
   //未指定Locale,此时系统会取默认的,本地电脑默认的值中文【中国】,即:zh_CN
   System.out.println(context.getMessage("personal_introduction", new String[]
{"spring高手", "java高手"}, Locale.CHINA)); //CHINA对应:zh_CN
   System.out.println(context.getMessage("personal_introduction", new String[]
{"spring", "java"}, Locale.UK)); //UK对应en_GB
}

监控国际化文件的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;

@Configuration
public class MainConfig2 {
   @Bean
   public MessageSource messageSource() {
       ReloadableResourceBundleMessageSource result = new ReloadableResourceBundleMessageSource();
       result.setBasenames("com/example/demo/message");
       //设置缓存时间1000毫秒
       // -1:表示永远缓存
// 0:每次获取国际化信息的时候,都会重新读取国际化文件
// 大于0:上次读取配置文件的时间距离当前时间超过了这个时间,重新读取国际化文件
       result.setCacheMillis(1000);
       return result;
 }
}

国际化信息存在db中

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.StaticMessageSource;
import java.util.Locale;

public class MessageSourceFromDb extends StaticMessageSource implements InitializingBean {
   @Override
   public void afterPropertiesSet() throws Exception {
       //此处我们在当前bean初始化之后,模拟从db中获取国际化信息,然后调用addMessage来配置国际化信息
       this.addMessage("desc", Locale.CHINA, "我是从db来的信息");
       this.addMessage("desc", Locale.UK, "MessageSource From Db");
 }
}
1
2
3
4
5
6
7
8
9
10
11
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig3 {
   @Bean
   public MessageSource messageSource(){
       return new MessageSourceFromDb();
  }
}

bean名称为什么必须是messageSource

  • org.springframework.context.support.AbstractApplicationContext#refresh内部会调用org.springframework.context.support.AbstractApplicationContext#initMessageSource
  • 这个方法用来初始化MessageSource,方法内部会查找当前容器中是否有messageSource名称的bean,如果有就将其作为处理国际化的对象,如果没有找到,此时会注册一个名称为messageSource的MessageSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* Name of the MessageSource bean in the factory.
* If none is supplied, message resolution is delegated to the parent.
* @see MessageSource
*/
public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";
/**
* Initialize the MessageSource.
* Use parent's if none defined in this context.
*/
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 若在配置中已经配置了messageSource,那么将messageSource提取并记录在this.messageSource
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
// Make MessageSource aware of parent MessageSource.
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
// Only set parent context as parent MessageSource if no parent MessageSource
// registered already.
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]");
}
}
else {
// 若用户并没有定义配置文件,那么使用临时的DelegatingMessageSource以便于作为调用getMessage方法的返回
// Use empty MessageSource to be able to accept getMessage calls.
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
if (logger.isTraceEnabled()) {
logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
}
}
}