此文是 【Spring 容器详解】-> 【【附录】Spring容器的启动过程】的支节点。
在Spring容器的启动过程中,AbstractApplicationContext.refresh()方法是容器刷新的核心方法。其中,initMessageSource()方法负责初始化国际化消息源(MessageSource),为应用程序提供多语言支持和消息国际化功能。
方法位置
public void refresh() throws BeansException, IllegalStateException {
// ... 其他步骤
// 初始化国际化消息源
initMessageSource();
// ... 其他步骤
}
方法作用
initMessageSource()方法的主要作用是:
- 初始化国际化消息源:为Spring容器配置MessageSource Bean
- 支持多语言:提供不同语言环境下的消息支持
- 消息占位符解析:支持消息模板和参数替换
- 默认消息源:当没有配置MessageSource时提供默认实现
核心实现逻辑
1. 检查是否已存在MessageSource
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 检查是否已经存在MessageSource Bean
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
MessageSource messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
// 设置到ApplicationContext中
this.messageSource = messageSource;
// 如果父上下文存在,设置父MessageSource
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
}
}
2. 创建默认MessageSource
// 如果没有配置MessageSource,创建默认的DelegatingMessageSource
else {
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
}
3. 设置父MessageSource
protected MessageSource getInternalParentMessageSource() {
return (getParent() instanceof AbstractApplicationContext) ?
((AbstractApplicationContext) getParent()).getInternalMessageSource() : getParent().getMessageSource();
}
重要的MessageSource类型
1. ResourceBundleMessageSource
基于Java的ResourceBundle实现,支持properties文件:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>
对应的properties文件:
# messages.properties (默认语言)
welcome.message=Welcome to our application
user.notfound=User not found
# messages_zh_CN.properties (中文)
welcome.message=欢迎使用我们的应用程序
user.notfound=用户未找到
# messages_en_US.properties (英文)
welcome.message=Welcome to our application
user.notfound=User not found
2. ReloadableResourceBundleMessageSource
支持热重载的ResourceBundle实现:
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages"/>
<property name="cacheSeconds" value="3600"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>
3. StaticMessageSource
用于测试或简单场景的静态消息源:
@Bean
public MessageSource messageSource() {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage("welcome.message", Locale.ENGLISH, "Welcome!");
messageSource.addMessage("welcome.message", Locale.CHINESE, "欢迎!");
return messageSource;
}
使用方式
1. 在Controller中使用
@Controller
public class UserController {
@Autowired
private MessageSource messageSource;
@GetMapping("/welcome")
public String welcome(Model model, Locale locale) {
String message = messageSource.getMessage("welcome.message", null, locale);
model.addAttribute("message", message);
return "welcome";
}
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id, Model model, Locale locale) {
try {
User user = userService.findById(id);
model.addAttribute("user", user);
return "user";
} catch (UserNotFoundException e) {
String errorMessage = messageSource.getMessage("user.notfound",
new Object[]{id}, locale);
model.addAttribute("error", errorMessage);
return "error";
}
}
}
2. 在Service中使用
@Service
public class UserService {
@Autowired
private MessageSource messageSource;
public void createUser(User user, Locale locale) {
// 验证用户数据
if (user.getEmail() == null || user.getEmail().isEmpty()) {
String errorMessage = messageSource.getMessage("user.email.required", null, locale);
throw new ValidationException(errorMessage);
}
// 创建用户逻辑
userRepository.save(user);
String successMessage = messageSource.getMessage("user.created.success",
new Object[]{user.getUsername()}, locale);
log.info(successMessage);
}
}
3. 在JSP/Thymeleaf中使用
JSP页面:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<title><spring:message code="page.title"/></title>
</head>
<body>
<h1><spring:message code="welcome.message"/></h1>
<p><spring:message code="user.count" arguments="${userCount}"/></p>
</body>
</html>
Thymeleaf页面:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{page.title}">页面标题</title>
</head>
<body>
<h1 th:text="#{welcome.message}">欢迎信息</h1>
<p th:text="#{user.count(${userCount})}">用户数量</p>
</body>
</html>
高级特性
1. 消息参数和占位符
# 支持参数的消息
greeting.message=Hello {0}, welcome to {1}!
user.info=User {0} has {1} posts and {2} comments
// 使用参数
String greeting = messageSource.getMessage("greeting.message",
new Object[]{"John", "Spring"}, locale);
String userInfo = messageSource.getMessage("user.info",
new Object[]{"Alice", 5, 12}, locale);
2. 嵌套消息
# 支持嵌套的消息键
error.validation=Validation failed
error.validation.email=Email format is invalid
error.validation.password=Password must be at least 8 characters
3. 默认消息
// 提供默认消息,避免KeyNotFoundException
String message = messageSource.getMessage("nonexistent.key",
null, "Default message", locale);
4. 消息代码解析
// 解析消息代码,支持嵌套结构
String message = messageSource.getMessage("user.profile.title", null, locale);
// 会依次查找:user.profile.title -> user.profile -> user
配置示例
1. XML配置
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>messages</value>
<value>validation</value>
<value>errors</value>
</list>
</property>
<property name="defaultEncoding" value="UTF-8"/>
<property name="fallbackToSystemLocale" value="false"/>
<property name="useCodeAsDefaultMessage" value="false"/>
</bean>
2. Java配置
@Configuration
public class MessageConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("messages", "validation", "errors");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setFallbackToSystemLocale(false);
messageSource.setUseCodeAsDefaultMessage(false);
return messageSource;
}
}
3. 属性文件配置
# application.properties
spring.messages.basename=messages,validation,errors
spring.messages.encoding=UTF-8
spring.messages.fallback-to-system-locale=false
spring.messages.use-code-as-default-message=false
spring.messages.cache-duration=3600
执行时机
initMessageSource()方法在Spring容器启动过程中的执行时机:
- BeanFactory准备完成:所有的Bean定义已经加载
- 国际化支持初始化:在容器刷新早期阶段初始化
- 其他Bean创建之前:确保后续创建的Bean能够使用MessageSource
注意事项
- 文件编码:确保properties文件使用正确的编码(推荐UTF-8)
- 文件命名:遵循ResourceBundle的命名规范
- 性能考虑:大量消息文件可能影响启动性能
- 缓存策略:合理配置缓存时间,平衡性能和实时性
- 默认消息:为关键消息提供默认值,避免运行时异常
总结
initMessageSource()方法是Spring容器启动过程中的重要步骤,它为应用程序提供了强大的国际化支持。通过配置不同类型的MessageSource实现,开发者可以轻松实现多语言应用,支持不同地区用户的需求。