使用springboot开发程序时,如果有国际市场的需求,一般要考虑国际化,在spring中本身对国际化就有很好的支持,下面介绍如何使用springboot开发国际化服务。
正常来说,引入 spring-boot-starter-web
模块后自动就会包括了国际化支持,所以demo程序引入的依赖包就非常简单:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.9</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
国际化要根据语言环境的不同返回对应的国际化资源内容,一般会定义多个 properties 结尾的资源文件:messages.properties、messages_en_US.properties、messages_zh_CN.properties,分别是默认的配置、英文配置、中文配置。
中文的信息写在 messages_zh_CN.properties 文件中:
hello=\u4f60\u597d\uff0c\u4e16\u754c\uff01
英文的信息写在 messages_en_US.properties 文件中:
hello=hello,world!
上面三个配置文件都放在resources资源目录下的i18n文件夹中,并且指定配置文件的前缀messages,所以在application.yml中指定资源文件位置:
server:
port: 7777
spring:
application:
name: test-i18n
messages:
basename: i18n.messages
cache-duration: 3600
encoding: UTF-8
lifecycle:
timeout-per-shutdown-phase: 45s # 关闭服务缓冲时间,默认30s
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+08
在springboot中,实现国际化是通过 org.springframework.context.MessageSource 实现的,这个类在不指定情况下会根据当前系统的language确认语言环境。而在真实环境中,与前端交互一般是通过请求头中的Accept-Language确认语言环境,这里定义一个拦截器拦截前端传递过来的请求头中包含的语音环境信息,并将这个信息设置到 org.springframework.web.servlet.LocaleResolver 中,这样在后续处理时就可以在上下文中获取到语言信息。
所以开发国际化项目后端代码编写流程:
1、定义Locale相关的配置信息:
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
/**
* @Author xingo
* @Date 2024/12/11
*/
@Configuration
public class LocaleConfig {
/**
* 默认解析器 其中locale表示默认语言,当请求中未包含语种信息,则设置默认语种
* 当前默认为简体中文,zh_CN
*/
@Bean
public SessionLocaleResolver localeResolver() {
// AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return localeResolver;
}
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
// 设置消息源
bean.setValidationMessageSource(resourceBundleMessageSource());
return bean;
}
@Bean(name = "messageSource")
public MessageSource resourceBundleMessageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.toString());
// 多语言文件
messageSource.addBasenames("i18n/messages");
// 可以根据业务的不同将国际化信息存放在多个文件中
// messageSource.addBasenames("i18n/security");
// messageSource.addBasenames("i18n/oss");
// messageSource.addBasenames("i18n/excel");
return messageSource;
}
}
2、定义拦截器处理请求头的语言信息:
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.support.RequestContextUtils;
import java.util.Locale;
/**
* @Author xingo
* @Date 2024/12/11
*/
@Slf4j
@Component
public class LocaleInterceptor extends LocaleChangeInterceptor {
private static final String LOCALE = "Accept-Language";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String newLocale = request.getHeader(LOCALE);
if (newLocale != null) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
try {
localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
} catch (Exception e) {
log.error("LocaleInterceptor error", e);
localeResolver.setLocale(request, response, Locale.SIMPLIFIED_CHINESE);
}
} else {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
localeResolver.setLocale(request, response, Locale.SIMPLIFIED_CHINESE);
}
return true;
}
}
3、将拦截器注入到springboot中:
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Author xingo
* @Date 2024/12/11
*/
@Configuration
@RequiredArgsConstructor
public class WebMvcConfiguration implements WebMvcConfigurer {
private final LocaleInterceptor localeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeInterceptor)
.addPathPatterns("/**"); // 所有的请求都要拦截
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry // 允许所有路径
.addMapping("/**")
// 允许携带凭证
.allowCredentials(true)
// 允许所有请求方法
.allowedMethods("*")
// 允许所有请求域名
.allowedOriginPatterns("*")
// 允许所有请求头
.allowedHeaders("*")
// 允许所有响应头
.exposedHeaders("*");
}
}
以上步骤完成后,就实现了一个国际化系统的基本框架,为了使用方便,还需要定义一个工具类用于获取国际化信息:
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Locale;
/**
* @Author xingo
* @Date 2024/12/11
*/
@Slf4j
@Component
public class I18NUtils {
public static final List<String> LOCALES = List.of("zh_CN", "en_US");
private static MessageSource messageSource;
public I18NUtils(MessageSource messageSource) {
I18NUtils.messageSource = messageSource;
}
public static String get(String key) {
String i18n = key;
try {
i18n = messageSource.getMessage(key, null, LocaleContextHolder.getLocale());
} catch (Exception ex) {
log.warn("[I18NUtils]WithoutArg:{}|{}", key, ex.getMessage());
}
return i18n;
}
public static String getCustomLocale(String key, Locale locale) {
String i18n = null;
try {
i18n = messageSource.getMessage(key, null, locale);
} catch (Exception ex) {
log.error("[I18NUtils]WithoutArgWithLocale:{}", ex.getMessage());
}
return i18n;
}
public static String get(String key, Object... arg) {
String i18n = null;
try {
i18n = messageSource.getMessage(key, arg, LocaleContextHolder.getLocale());
} catch (Exception ex) {
log.error("[I18NUtils]WithoutArg:{}", ex.getMessage());
}
return i18n;
}
}
大功告成!写一个测试接口看看是否符合预期:
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.xingo.config.I18NUtils;
/**
* @Author xingo
* @Date 2024/12/11
*/
@RestController
public class TestController {
@GetMapping("/hello")
public String hello() {
System.out.println(LocaleContextHolder.getLocale());
return I18NUtils.get("hello");
}
}
使用postman请求接口,通过调整请求头中的 Accept-Language 观察返回值!