上篇文章 我们介绍了Springboot静态国际化的使用,实际工作中使用静态配置文件的形式的国际化不够灵活,扩展起来也相对麻烦,如果需要修改其中的国际化信息就要重新加载对应的配置文件或者是重启项目,很不友好,所以我们需要实现动态配置国际化信息。 最直接的方式就是通过数据库将国际化信息进行持久化,可以随时更新国际化信息。本文将介绍如何动态配置国际化信息。
动态配置国际化信息
引入依赖
为了方便,这里使用JPA、MySQL进行数据库方面的操作,引入相关依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
表结构及数据
新建一张表i18n_msg
表结构如下:
sql
CREATE TABLE `i18n_msg` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`locale` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '语言类型',
`code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`msg` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息,可以使用{}占位符',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
先添加4条数据,方便后面使用:
JPA配置
配置文件application.properties
配置:
properties
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_practice?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
## 每次启动项目,数据库中没有表会新建一张表,如果有表表内有数据不会清空,只会更新
spring.jpa.hibernate.ddl-auto=update
# 打印SQL语句
spring.jpa.show-sql=true
JPA实体类:
java
@Entity
@Table(name = "i18n_msg")
@Data
public class I18nMsgEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "locale",length = 50)
private String locale;
@Column(name = "code",length = 50)
private String code;
@Column(name = "msg",length = 50)
private String msg;
}
JpaRepository实现类,进行CRUD操作:
java
/**
* @author 公众号-索码理(suncodernote)
* @date 2024/2/1 23:00
*/
public interface I18nMsgRepository extends JpaRepository<I18nMsgEntity,Long> {
I18nMsgEntity findByCodeAndLocale(String code, String locale);
}
java
/**
* @author 公众号-索码理(suncodernote)
*/
public interface I18nMsgService {
I18nMsgEntity findByCodeAndLocale(String code, String locale);
}
java
@Service
public class I18nMsgServiceImpl implements I18nMsgService {
@Resource
private I18nMsgRepository i18nMsgRepository;
@Override
public I18nMsgEntity findByCodeAndLocale(String code, String locale) {
return i18nMsgRepository.findByCodeAndLocale(code, locale);
}
}
动态国际化信息提供类
创建一个接口I18nDynamicMsgProvider
,该接口用于提供动态国际化信息。
java
/**
* 动态国际化接口
* @author 公众号-索码理(suncodernote)
*/
public interface I18nDynamicMsgProvider {
/**
* 获取动态国际化信息
* @param locale 指定区域
* @param code 国际化code
* @return
*/
I18DynamicMessage getDynamicMsg(Locale locale, String code);
}
I18nDynamicMsgProvider
接口实现类,在该类中通过区域和国际化信息code获取对应的国际化信息。
java
@Component
public class MyI18nDynamicMsgProvider implements I18nDynamicMsgProvider{
@Autowired
private I18nMsgService i18nMsgService;
@Override
public I18DynamicMessage getDynamicMsg(Locale locale, String code) {
I18DynamicMessage i18DynamicMessage = new I18DynamicMessage();
I18nMsgEntity i18nMsgEntity = i18nMsgService.findByCodeAndLocale(code, locale.toString());
if (i18nMsgEntity != null) {
String msg = i18nMsgEntity.getMsg();
i18DynamicMessage.setMsg(msg);
i18DynamicMessage.setCode(code);
i18DynamicMessage.setLocale(locale.toString());
}
return i18DynamicMessage;
}
}
自定义动态国际化信息源
要自定义动态国际化信息源需要实现AbstractMessageSource
抽象类,重写resolveCode(String , Locale)
方法即可。
java
@Component
public class DynamicMessageSource extends AbstractMessageSource {
private final I18nDynamicMsgProvider i18nMessageProvider;
public DynamicMessageSource(I18nDynamicMsgProvider i18nMessageProvider) {
this.i18nMessageProvider = i18nMessageProvider;
}
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
I18DynamicMessage i18nMessage = i18nMessageProvider.getDynamicMsg(locale, code);
if (i18nMessage != null) {
return createMessageFormat(i18nMessage.getMsg() , locale);
}
return null;
}
}
测试
java
@SpringBootTest
class SpringbootPracticeApplicationTests {
@Autowired
private DynamicMessageSource dynamicMessageSource;
@Test
public void testDynamicMessageSource(){
String code = "dynamic.hello";
Locale locale = Locale.CHINA;
String message = dynamicMessageSource.getMessage(code, null, locale);
System.out.println(message);
System.out.println();
//占位符替换
code = "dynamic.welcome";
message = dynamicMessageSource.getMessage(code, new String[]{"索码理"}, locale);
System.out.println(message);
System.out.println();
locale = Locale.ENGLISH;
message = dynamicMessageSource.getMessage(code, new String[]{"suncodernote"}, locale);
System.out.println(message);
}
}
测试结果:
sql
Hibernate: select i1_0.id,i1_0.code,i1_0.locale,i1_0.msg from i18n_msg i1_0 where i1_0.code=? and i1_0.locale=?
动态国际化配置,你好
Hibernate: select i1_0.id,i1_0.code,i1_0.locale,i1_0.msg from i18n_msg i1_0 where i1_0.code=? and i1_0.locale=?
欢迎关注公众号,索码理
Hibernate: select i1_0.id,i1_0.code,i1_0.locale,i1_0.msg from i18n_msg i1_0 where i1_0.code=? and i1_0.locale=?
Welcome to follow WeChat Public Number, suncodernote!
从测试结果看到不管是有没有占位符,中文还是英文,国际化信息都能很好的展示,也说明了动态国际化配置的成功。
总结
本文只是简单介绍动态国际化信息的配置,有待优化,比如将国际化信息放入到缓存中,感兴趣的可以参考上面的示例,写一个自己的动态国际化配置。