3.9 Spring Boot国际化:动态语言切换与数据库存储方案
在Spring Boot应用中实现国际化(i18n)并支持动态语言切换与数据库存储,可以通过以下方案实现灵活的多语言管理:
一、基础配置(基于.properties
文件)
1. 添加依赖
xml
<!-- Spring Boot基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. 创建消息文件
src/main/resources/
├─ messages.properties # 默认语言(英文)
├─ messages_zh_CN.properties # 简体中文
└─ messages_ja_JP.properties # 日文
3. 配置国际化参数(application.yml)
yaml
spring:
messages:
basename: messages
encoding: UTF-8
二、动态语言切换
1. 配置LocaleResolver
java
@Configuration
public class LocaleConfig {
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
resolver.setDefaultLocale(Locale.ENGLISH); // 设置默认语言
return resolver;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang"); // 通过URL参数切换语言
return interceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
2. 前端切换语言
html
<!-- 语言切换链接 -->
<a href="?lang=en">English</a>
<a href="?lang=zh_CN">中文</a>
<a href="?lang=ja_JP">日本語</a>
三、数据库存储方案
1. 数据库表设计
sql
CREATE TABLE sys_language (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
lang_code VARCHAR(10) NOT NULL UNIQUE, -- 语言代码(如zh_CN)
lang_name VARCHAR(50) NOT NULL -- 语言名称
);
CREATE TABLE sys_message (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
message_key VARCHAR(255) NOT NULL, -- 消息键(如login.title)
content TEXT NOT NULL, -- 消息内容
lang_code VARCHAR(10) NOT NULL, -- 关联语言代码
UNIQUE KEY (message_key, lang_code)
);
2. 自定义MessageSource
java
public class DatabaseMessageSource extends AbstractMessageSource {
@Autowired
private MessageRepository messageRepository;
private final Map<String, Map<Locale, String>> messageCache = new ConcurrentHashMap<>();
@Override
protected MessageFormat resolveCode(String key, Locale locale) {
String content = getMessageFromDB(key, locale);
return new MessageFormat(content, locale);
}
private String getMessageFromDB(String key, Locale locale) {
// 1. 优先从缓存读取
if (messageCache.containsKey(key) && messageCache.get(key).containsKey(locale)) {
return messageCache.get(key).get(locale);
}
// 2. 查询数据库
String langCode = locale.toLanguageTag().replace("-", "_");
String content = messageRepository.findByKeyAndLang(key, langCode)
.orElseGet(() -> getParentMessage(key, locale));
// 3. 更新缓存
messageCache.computeIfAbsent(key, k -> new ConcurrentHashMap<>())
.put(locale, content);
return content;
}
// 清理缓存方法(用于语言更新时调用)
public void clearCache() {
messageCache.clear();
}
}
3. 注册自定义MessageSource
java
@Configuration
public class MessageSourceConfig {
@Bean
public MessageSource messageSource() {
DatabaseMessageSource messageSource = new DatabaseMessageSource();
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
四、动态语言管理接口
1. 语言管理API
java
@RestController
@RequestMapping("/api/language")
public class LanguageController {
@Autowired
private MessageRepository messageRepository;
@Autowired
private DatabaseMessageSource messageSource;
// 添加语言项
@PostMapping
public ResponseEntity<?> addMessage(@RequestBody MessageDTO dto) {
SysMessage message = new SysMessage();
message.setMessageKey(dto.getKey());
message.setContent(dto.getContent());
message.setLangCode(dto.getLangCode());
messageRepository.save(message);
messageSource.clearCache(); // 清理缓存
return ResponseEntity.ok().build();
}
// 更新语言项
@PutMapping("/{id}")
public ResponseEntity<?> updateMessage(@PathVariable Long id, @RequestBody MessageDTO dto) {
SysMessage message = messageRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Message not found"));
message.setContent(dto.getContent());
messageRepository.save(message);
messageSource.clearCache();
return ResponseEntity.ok().build();
}
}
五、高级功能扩展
1. 消息缓存优化
java
// 使用Spring Cache + Redis缓存
@Cacheable(value = "messages", key = "#key + '_' + #locale")
public String getMessage(String key, Locale locale) {
// 数据库查询逻辑
}
// 更新时清理缓存
@CacheEvict(value = "messages", allEntries = true)
public void updateMessage() {
// 更新逻辑
}
2. 默认语言回退机制
java
private String getParentMessage(String key, Locale locale) {
// 尝试获取父语言(如zh_CN -> zh)
if (locale.getCountry().length() > 0) {
Locale parentLocale = new Locale(locale.getLanguage());
String parentMessage = getMessageFromDB(key, parentLocale);
if (!parentMessage.equals(key)) {
return parentMessage;
}
}
// 最终返回默认语言
return getMessageFromDB(key, Locale.ENGLISH);
}
3. 实时热更新监听
java
@EventListener
public void handleMessageUpdateEvent(MessageUpdateEvent event) {
messageSource.clearCache(); // 清理缓存
log.info("Language cache cleared due to update");
}
六、使用示例
1. 在Thymeleaf中调用
html
<h1 th:text="#{login.title}"></h1>
<p th:text="#{login.welcome(${user.name})}"></p>
2. 在Controller中获取消息
java
@Autowired
private MessageSource messageSource;
@GetMapping("/greeting")
public String greeting(Locale locale) {
return messageSource.getMessage("greeting.message", null, locale);
}
七、验证测试
1. 数据库初始化数据
sql
INSERT INTO sys_language (lang_code, lang_name) VALUES
('en', 'English'),
('zh_CN', '简体中文'),
('ja_JP', '日本語');
INSERT INTO sys_message (message_key, content, lang_code) VALUES
('login.title', 'Login', 'en'),
('login.title', '登录', 'zh_CN'),
('login.title', 'ログイン', 'ja_JP');
2. 发送测试请求
bash
# 英文
curl http://localhost:8080/api/greeting?lang=en
# 中文
curl http://localhost:8080/api/greeting?lang=zh_CN
八、安全增强建议
- 接口权限控制:语言管理接口添加
@PreAuthorize("hasRole('ADMIN')")
- 输入校验:对语言代码进行正则校验(如
@Pattern(regexp = "^[a-z]{2}_[A-Z]{2}$")
) - 防SQL注入:使用JPA参数化查询
- 审计日志:记录语言项修改操作
通过此方案,可以实现以下核心优势:
- 动态更新:无需重启即可更新语言项
- 灵活存储:支持数据库管理多语言内容
- 高效缓存:结合缓存机制降低数据库压力
- 扩展性强:可轻松集成第三方翻译服务(如Google Translate API)