Spring-国际化

前言

Spring MessageSourceJava Locale息息相关, 因此我们有必要先了解Locale, 可以参考使用Java Locale进行国际化

官方文档

原文链接: context-functionality-messagesource

ApplicationContext 接口扩展了一个名为 MessageSource 的接口,因此提供了国际化(i18n)功能。 Spring 还提供了 HierarchicalMessageSource 接口,可以分层解析消息。这些接口一起提供了 Spring 影响消息解析的基础。在这些接口上定义的方法包括:

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从 MessageSource 检索消息的基本方法。如果没有找到指定语言环境的消息,则使用默认消息。使用标准库提供的 MessageFormat 功能,传入的任何参数都成为替换值。

  • String getMessage(String code, Object[] args, Locale loc):与前面的方法基本相同,但有一个区别:不能指定默认消息。如果找不到消息,则抛出 NoSuchMessageException

  • String getMessage(MessageSourceResolvable resolvable, Locale locale):上述方法中使用的所有属性也都封装在一个名为MessageSourceResolvable的类中,您可以在此方法中使用该类。

加载ApplicationContext时,它会自动搜索上下文中定义的 MessageSource beanbean 名称必须指定为 messageSource。如果找到这样的 bean,则对上述方法的所有调用都将委托给消息源。如果未找到消息源,则 ApplicationContext 会尝试查找包含同名 bean 的父级。如果是,则使用该 bean作为 MessageSource。如果 ApplicationContext 找不到任何消息源,则会实例化一个空的 DelegatingMessageSource,以便能够接受对上面定义的方法的调用。

Spring 提供了三个 MessageSource 实现,ResourceBundleMessageSourceReloadableResourceBundleMessageSourceStaticMessageSource。它们都实现 HierarchicalMessageSource 以进行嵌套消息传递。 StaticMessageSource 很少使用,但提供了向源添加消息的编程方式。

实现

添加国际化文件

# greet.properties
greet=hello {0}
# greet_en_US.properties
greet=hello {0}
# greet.properties
你好啊 {0}

测试

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;

import java.nio.charset.StandardCharsets;
import java.util.Locale;

@Configuration
public class AppConfig {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        String message = context.getMessage("illegal.argument", new Object[]{"taskId"}, "xxx", Locale.US);
        System.out.println(message);
        String greet = context.getMessage("greet", new Object[]{"打工人"}, Locale.CHINESE);
        System.out.println(greet);
        // 当无法找到code时,使用默认值
        greet = context.getMessage("greet111", null, "你好啊 陌生人", Locale.CHINESE);
        System.out.println(greet);
    }

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        // 添加国际化文件基础名
        messageSource.addBasenames("greet");
        messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
        return messageSource;
    }
}

控制台输出:

你好啊 打工人
你好啊 陌生人

进阶

自定义查找路径

上一节介绍了如何配置和读取国际化, 以默认的方式, 比如, 文件的查找路径, 默认是classpath:, 如果需要配置自定义路径, 比如, 我希望在classpath:/i18n目录下寻找国际化文件, 那么只需要在messageSource.addBasenames方法的参数加上路径前缀即可:

messageSource.addBasenames("classpath:/i18n/greet");

举一反三, 你也可以配置WEB-INF文件夹下的资源.

追加BeanNames

由于MessageSource是单例的, 很可能被封装在基础组件中(这意味着不能随意修改MessageSource对象创建时的代码/配置), 那么当业务扩展, 我们需要添加更多资源时, 如何解决.

考虑使用BeanPostProcessor:

@Component("msbpp")
public class MessageSourceBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof AbstractResourceBasedMessageSource) {
            AbstractResourceBasedMessageSource ms = (AbstractResourceBasedMessageSource) bean;
            ms.addBasenames("classpath:/i18n/greet");
        }
        return bean;
    }
}

或者考虑可扩展性, 我们可以约定/预留一个配置文件如ms.locations, 其中专门配置路径, 然后在postProcessAfterInitialization方法中遍历追加.

Web应用中的Locale

上面的例子中, 获取国际化信息时, Locale参数都是写死的, 但一般大型网站都是支持多语言的, 并且可以在前台切换, 那么, 如何感知并传递Locale就是个问题, Spring提供了LocaleChangeInterceptor拦截器的方式, 用于动态更新当前请求所包含的Locale信息, 我们只需要在配置MVC时将此拦截器注册进去即可.

当需要获取时, 使用LocaleContextHolder获取Locale实例.

LocaleChangeInterceptor的工作原理

默认通过获取请求参数locale(http://xxx.xxx/yyy/zzz?locale=xx_YY)得到当前请求的locale信息, 通过LocaleResolver实例将其解析/保存, 伪代码:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws ServletException {
    String newLocale = request.getParameter(getParamName());
    this.localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
    return true;
}

查看setLocale的实现, 有两个:

在这里插入图片描述

但可用的只有CookieLocaleResolver, 因为AcceptHeaderLocaleResolver不支持此方法, 否则会报错:

@Override
public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
    throw new UnsupportedOperationException(
        "Cannot change HTTP accept header - use a different locale resolution strategy");
}

CookieLocaleResolver可以将locale其写入响应Cookie, 以便后续请求可以直接从Cookie中获取

也可以通过LocaleContextHolder.getLocale()获取

配置与使用

配置

@EnableWebMvc
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        // 忽视无效locale
        interceptor.setIgnoreInvalidLocale(true);
        registry.addInterceptor(interceptor).addPathPatterns("/*");
    }
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(5000);
    }

    @Bean
    public LocaleResolver localeResolver(){
        CookieLocaleResolver acceptHeaderLocaleResolver = new CookieLocaleResolver();
        acceptHeaderLocaleResolver.setDefaultLocale(Locale.CHINA);
        return acceptHeaderLocaleResolver;
    }
}

使用

@Resource MessageSource messageSource;
messageSource.getMessage("greet", new Object[]{"developer"}, LocaleContextHolder.getLocale());

效果

注意事项

  1. 本示例采用UTF8编码, 如果页面出现乱码, 可以下载Chrome编码插件

您可以在Gitee上找到本文的源代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值