Spring Boot之国际化

文章目录

  • 前言
  • 一、国际化底层原理
  • 二、国际化使用方法
    • 1.默认解析器AcceptHeaderLocaleResolver
    • 2.自定义解析器SessionLocaleResolver 和CookieLocaleResolver


前言

国际化(Internationalization 简称 I18n,其中“I”和“n”分别为首末字符,18 则为中间的字符数)是指软件开发时应该具备支持多种语言和地区的功能。换句话说就是,开发的软件需要能同时应对不同国家和地区的用户访问,并根据用户地区和语言习惯,提供相应的、符合用具阅读习惯的页面和数据,例如,为中国用户提供汉语界面显示,为美国用户提供提供英语界面显示。
国际化应用的场景也比较多,比如各种异常提示信息国际化、页面显示字段的国际化、参数校验提示信息国际化等等,下面将详细讲解Spring Boot国际化配置及其使用方法。

一、国际化底层原理

1.Spring Boot使用 ResourceBundleMessageSource 管理国际化资源文件
通过国际化的自动化配置MessageSourceAutoConfiguration 对 ResourceBundleMessageSource 提供了默认配置,其部分源码如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

    private static final Resource[] NO_RESOURCES = {};

    // 将 MessageSourceProperties 以组件的形式添加到容器中
    // MessageSourceProperties 下的每个属性都与以 spring.messages 开头的属性对应
    @Bean
    @ConfigurationProperties(prefix = "spring.messages")
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

    //Spring Boot 会从容器中获取 MessageSourceProperties
    // 读取国际化资源文件的 basename(基本名)、encoding(编码)等信息
    // 并封装到 ResourceBundleMessageSource 中
    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        //读取国际化资源文件的 basename (基本名),并封装到 ResourceBundleMessageSource 中
        if (StringUtils.hasText(properties.getBasename())) {
            messageSource.setBasenames(StringUtils
                    .commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }
        //读取国际化资源文件的 encoding (编码),并封装到 ResourceBundleMessageSource 中
        if (properties.getEncoding() != null) {
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }
        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }
        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }
    ...
}

从源码中可以窥见国际化的基本细节:

  • MessageSourceProperties 以组件的形式添加到容器中;
  • MessageSourceProperties 的属性与配置文件中以“spring.messages”开头的配置进行了绑定;
  • 从容器中获取 MessageSourceProperties 组件,并从中读取国际化资源文件的 basename(文件基本名)、 encoding(编码)等信息,将它们封装到 ResourceBundleMessageSource 中;
  • 将 ResourceBundleMessageSource 以组件的形式添加到容器中,进而实现对国际化资源文件的管理。

查看 MessageSourceProperties 类源码如下:

public class MessageSourceProperties {
    private String basename = "messages";
    private Charset encoding;
    @DurationUnit(ChronoUnit.SECONDS)
    private Duration cacheDuration;
    private boolean fallbackToSystemLocale;
    private boolean alwaysUseMessageFormat;
    private boolean useCodeAsDefaultMessage;

    public MessageSourceProperties() {
        this.encoding = StandardCharsets.UTF_8;
        this.fallbackToSystemLocale = true;
        this.alwaysUseMessageFormat = false;
        this.useCodeAsDefaultMessage = false;
    }
    ...
}

通过阅读以上,可以得到以下 3 点:

  • MessageSourceProperties 为 basename、encoding 等属性提供了默认值;
  • basename 表示国际化资源文件的基本名,其默认取值为“messages”,即 Spring Boot 默认会获取类路径下的 message.properties 以及 message_XXX.properties 作为国际化资源文件;
  • 在 application.porperties/yml 等配置文件中,使用配置参数“spring.messages.basename”即可重新指定国际化资源文件的基本名。

通过以上源码分析,Spring Boot 已经对国际化资源文件的管理提供了默认自动配置,其中不包含"_XXX"区域代码的文件为默认语言配置,我们这里只需要在 Spring Boot 全局配置文件中,使用配置参数“spring.messages.basename”指定我们自定义的国际资源文件的基本名即可,代码如下(当指定多个资源文件时,用逗号分隔):

spring.messages.basename=i18n.messages

2. 区域信息解析器自动配置
我们知道,Spring MVC 进行国际化时有 2 个十分重要的对象,Locale是区域信息对象,LocaleResolver是区域信息解析器,容器中的组件,负责获取区域信息对象,通过这两个对象可以对区域信息进行切换,从而达到切换语言的目的。
Spring Boot 在 WebMvcAutoConfiguration 中为区域信息解析器(LocaleResolver)进行了自动配置,源码如下:

    @Bean
    @ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
    @SuppressWarnings("deprecation")
    public LocaleResolver localeResolver() {
        if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
            return new FixedLocaleResolver(this.webProperties.getLocale());
        }
        if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
            return new FixedLocaleResolver(this.mvcProperties.getLocale());
        }
        AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
        Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
                : this.mvcProperties.getLocale();
        localeResolver.setDefaultLocale(locale);
        return localeResolver;
    }

从以上源码可知:

  • 该方法默认向容器中添加了一个区域信息解析器(LocaleResolver)组件,它会根据请求头中携带的“Accept-Language”参数,获取相应区域信息(Locale)对象。
  • 该方法上使用了 @ConditionalOnMissingBean 注解,其参数 name 的取值为 localeResolver(与该方法注入到容器中的组件名称一致),该注解的含义为:当容器中不存在名称为 localResolver 组件时,该方法才会生效。换句话说,当我们手动向容器中添加一个名为“localeResolver”的组件时,Spring Boot 自动配置的区域信息解析器会失效,而我们定义的区域信息解析器则会生效。
    看到这里我们就看明白了,没有指定的LocaleResolver组件时,会采用这个默认的LocaleResolver,他取的是http请求头中“Accept-Language”参数的值确定语言。

二、国际化使用方法

Springboot国际化存在三种使用方式:

  • AcceptHeaderLocaleResolver 默认解析器,通过请求头的 Accept-Language 字段来判断当前请求所属的环境的
  • SessionLocaleResolver Locale 保存在 HttpSession 对象中
  • CookieLocaleResolver LOCAL保存在Cookie中

1.默认解析器AcceptHeaderLocaleResolver

  • 修改Springboot application.yml配置
spring:
  messages:
    basename: i18n/messages #配置国际化资源文件路径
  • 创建国际化资源文件
    Spring Boot之国际化_第1张图片
    messages.properties不带后缀为默认语言资源
  • 编写国际化文本
    简体中文 messages_zh_CN.properties
unknown.exception=未知异常,请联系管理员!
user.login.notExists={0} 用户不存在!

英文 messages_en_US.properties

unknown.exception=Unknown error,Please contact the administrator!
user.login.notExists={0} user not exists!

messages.properties文件内容就和简体中文文件一致,如果未设置Locale参数,默认就为该文件内容,此文件也可不用

unknown.exception=未知异常,请联系管理员!
user.login.notExists={0} 用户不存在!
  • 获取国际化文本
    下面是封装的获取工具类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author Mr.bin
 * @version 1.0.0
 * @ClassName MessageUtils.java
 * @Description 国际化支持工具类
 * @createTime 2022年04月24日 16:53:00
 */
@Component
public class MessageUtils {

    private static MessageSource messageSource;

    @Autowired
    private MessageSource messageSourceCopy;

    @PostConstruct
    private void init() {
        setMessageSource(messageSourceCopy);
    }

    private static synchronized void setMessageSource(MessageSource ms) {
        messageSource = ms;
    }

    public static String getMessage(String code, Object ... arg) {
        Object[] args = arg.clone();
        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
    }
}

java代码中使用MessageUtils实现国际化操作

Assert.isTrue(count == 0, MessageUtils.getMessage("message.gateway.publish.api.url.exist", requestUrl));
  • 国际化测试
    Spring Boot之国际化_第2张图片

2.自定义解析器SessionLocaleResolver 和CookieLocaleResolver

所谓自定义包含两层意思,第一是指改变获取语言参数的方式,例如从session中获取,或者从cookie中获取,第二种是指改变语言参数的名称,例如Spring Boot默认的解析器是获取请求头中的“Accept-Language”参数,如果不想用这个参数想再自定义一个,就采用这种方式。
无论是哪一种自定义LocalResolver都需要定义LocaleChangeInterceptor,用于拦截请求中的语言参数(不配置的话默认是 locale),这个参数则指定了当前的环境信息,例如现在要从"language"这个参数中获取语言信息:

@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
   LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
   localeChangeInterceptor.setParamName("language");
   return localeChangeInterceptor;
}

还需要将LocaleChangeInterceptor添加到应用程序的注册表拦截器中。 配置类应扩展WebMvcConfigurerAdapter类,或者扩展WebMvcConfigurer ,并覆盖addInterceptors()方法。

@Override
public void addInterceptors(InterceptorRegistry registry) {
	//添加locale拦截器
	registry.addInterceptor(localeChangeInterceptor());
}

接下来需要确定应用程序的LocaleResolver
如果使用CookieLocaleResolver

	@Bean
	public LocaleResolver localeResolver(){
		CookieLocaleResolver localeResolver = new CookieLocaleResolver();
		localeResolver.setCookieName("localeCookie");
		//设置默认区域
		localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
		localeResolver.setCookieMaxAge(3600);//设置cookie有效期.
		return localeResolver;
	}

如果使用SessionLocaleResolver

	@Bean
	LocaleResolver localeResolver() {
		SessionLocaleResolver localeResolver = new SessionLocaleResolver();
		localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
		return localeResolver;
	}

你可能感兴趣的:(Spring,Boot,I18N,spring,boot,java,spring)