【附录】Spring容器启动流程详解 - initMessageSource()方法分析

25 阅读3分钟

此文是 【Spring 容器详解】-> 【【附录】Spring容器的启动过程】的支节点。

在Spring容器的启动过程中,AbstractApplicationContext.refresh()方法是容器刷新的核心方法。其中,initMessageSource()方法负责初始化国际化消息源(MessageSource),为应用程序提供多语言支持和消息国际化功能。

方法位置

public void refresh() throws BeansException, IllegalStateException {
    // ... 其他步骤
    
    // 初始化国际化消息源
    initMessageSource();
    
    // ... 其他步骤
}

方法作用

initMessageSource()方法的主要作用是:

  1. 初始化国际化消息源:为Spring容器配置MessageSource Bean
  2. 支持多语言:提供不同语言环境下的消息支持
  3. 消息占位符解析:支持消息模板和参数替换
  4. 默认消息源:当没有配置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容器启动过程中的执行时机:

  1. BeanFactory准备完成:所有的Bean定义已经加载
  2. 国际化支持初始化:在容器刷新早期阶段初始化
  3. 其他Bean创建之前:确保后续创建的Bean能够使用MessageSource

注意事项

  1. 文件编码:确保properties文件使用正确的编码(推荐UTF-8)
  2. 文件命名:遵循ResourceBundle的命名规范
  3. 性能考虑:大量消息文件可能影响启动性能
  4. 缓存策略:合理配置缓存时间,平衡性能和实时性
  5. 默认消息:为关键消息提供默认值,避免运行时异常

总结

initMessageSource()方法是Spring容器启动过程中的重要步骤,它为应用程序提供了强大的国际化支持。通过配置不同类型的MessageSource实现,开发者可以轻松实现多语言应用,支持不同地区用户的需求。