简述
初始化MessageSource,用于支持国际化(i18n)消息。
这是refresh()方法的第7步,在BeanFactory后置处理和BeanPostProcessor注册之后执行。
MessageSource提供了解析消息的能力,支持参数化和国际化。
核心流程
- 检查BeanFactory中是否已有名为"messageSource"的Bean
- 如果有,直接使用
- 如果没有,创建默认的DelegatingMessageSource
- 将MessageSource注册为单例Bean
源码分析
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
/** MessageSource的Bean名称 */
public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";
/** MessageSource实例 */
private MessageSource messageSource;
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// ... 前面的步骤
try {
// ... invokeBeanFactoryPostProcessors
// ... registerBeanPostProcessors
// 初始化MessageSource
initMessageSource();
// ... 后续步骤
}
catch (BeansException ex) {
// 异常处理
}
}
}
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 检查是否已有messageSource Bean
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
// 如果有,获取并使用
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
// 如果父上下文也是MessageSource,设置父MessageSource
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
// 只有当前MessageSource还没有父MessageSource时才设置
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]");
}
}
else {
// 如果没有,创建默认的DelegatingMessageSource
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
// 注册为单例Bean
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
if (logger.isTraceEnabled()) {
logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
}
}
}
@Nullable
protected MessageSource getInternalParentMessageSource() {
return (getParent() instanceof AbstractApplicationContext ?
((AbstractApplicationContext) getParent()).messageSource : getParent());
}
}
MessageSource接口
public interface MessageSource {
/**
* 解析消息
* @param code 消息代码
* @param args 消息参数(用于占位符)
* @param defaultMessage 默认消息
* @param locale 区域设置
*/
@Nullable
String getMessage(String code, @Nullable Object[] args,
@Nullable String defaultMessage, Locale locale);
/**
* 解析消息(必须存在,否则抛出异常)
*/
String getMessage(String code, @Nullable Object[] args, Locale locale)
throws NoSuchMessageException;
/**
* 解析消息(使用MessageSourceResolvable)
*/
String getMessage(MessageSourceResolvable resolvable, Locale locale)
throws NoSuchMessageException;
}
DelegatingMessageSource
默认的MessageSource实现,简单地委托给父MessageSource:
public class DelegatingMessageSource extends MessageSourceSupport
implements HierarchicalMessageSource {
@Nullable
private MessageSource parentMessageSource;
@Override
@Nullable
public String getMessage(String code, @Nullable Object[] args,
@Nullable String defaultMessage, Locale locale) {
if (this.parentMessageSource != null) {
return this.parentMessageSource.getMessage(code, args, defaultMessage, locale);
}
else if (defaultMessage != null) {
return renderDefaultMessage(defaultMessage, args, locale);
}
else {
return null;
}
}
}
如果没有配置MessageSource且没有父上下文,DelegatingMessageSource会简单地返回null或默认消息。
自定义MessageSource
在SpringBoot应用中,通常使用ResourceBundleMessageSource或ReloadableResourceBundleMessageSource:
@Configuration
public class MessageSourceConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
// 设置消息文件的基础名称(不包含语言代码和.properties)
messageSource.setBasenames("messages", "validation");
// 设置默认编码
messageSource.setDefaultEncoding("UTF-8");
// 设置缓存时间(秒),-1表示永久缓存
messageSource.setCacheSeconds(3600);
// 设置是否使用系统区域设置作为默认
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
}
SpringBoot自动配置:
@Bean
@ConditionalOnMissingBean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
messageSource.setBasenames(
StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(properties.getBasename())));
}
messageSource.setDefaultEncoding(properties.getEncoding().name());
messageSource.setCacheSeconds((int) properties.getCacheDuration().getSeconds());
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
消息文件示例
messages.properties(默认):
welcome.message=Welcome to our application
user.greeting=Hello, {0}!
error.notfound=Resource not found
messages_zh_CN.properties(中文):
welcome.message=欢迎使用我们的应用
user.greeting=你好,{0}!
error.notfound=资源未找到
messages_en_US.properties(英文):
welcome.message=Welcome to our application
user.greeting=Hello, {0}!
error.notfound=Resource not found
使用MessageSource
1. 通过ApplicationContext
@Component
public class MyService {
@Autowired
private ApplicationContext context;
public void doSomething() {
// 获取消息(使用当前Locale)
String message = context.getMessage("welcome.message", null, LocaleContextHolder.getLocale());
// 带参数的消息
String greeting = context.getMessage("user.greeting", new Object[]{"张三"}, Locale.CHINA);
// 输出:你好,张三!
}
}
2. 直接注入MessageSource
@Component
public class MyService {
@Autowired
private MessageSource messageSource;
public void doSomething() {
String message = messageSource.getMessage("welcome.message", null,
LocaleContextHolder.getLocale());
}
}
3. 在Controller中使用
@RestController
public class MyController {
@Autowired
private MessageSource messageSource;
@GetMapping("/greeting")
public String greeting(@RequestParam String name, Locale locale) {
return messageSource.getMessage("user.greeting", new Object[]{name}, locale);
}
}
4. 在视图模板中使用
Thymeleaf示例:
<!-- 使用th:text -->
<p th:text="#{welcome.message}">Default welcome message</p>
<!-- 带参数 -->
<p th:text="#{user.greeting(${userName})}">Hello</p>
MessageSource的层级结构
ApplicationContext可以有父子关系,MessageSource也支持层级结构:
Parent ApplicationContext (messageSource1)
└── Child ApplicationContext (messageSource2)
子上下文的MessageSource查找顺序:
1. 先在子MessageSource中查找
2. 如果找不到,在父MessageSource中查找
3. 如果还找不到,返回默认消息或抛出异常
SpringBoot配置
application.properties:
# 消息文件基础名称(默认是messages)
spring.messages.basename=messages,config.i18n.messages
# 编码格式
spring.messages.encoding=UTF-8
# 缓存时间(秒),-1表示永久缓存,开发环境可以设置为0
spring.messages.cache-duration=3600
# 找不到对应语言的消息时,是否回退到系统Locale
spring.messages.fallback-to-system-locale=true
# 消息代码不存在时,是否使用代码作为默认消息
spring.messages.use-code-as-default-message=false
参考调用栈
initMessageSource:857, AbstractApplicationContext (org.springframework.context.support)
refresh:538, AbstractApplicationContext (org.springframework.context.support)
refresh:143, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:755, SpringApplication (org.springframework.boot)
refreshContext:402, SpringApplication (org.springframework.boot)
run:312, SpringApplication (org.springframework.boot)