SpringBoot实现国际化的教程

 更新时间:2025年03月22日 08:59:20   作者:对酒当歌丶人生几何  
这篇文章主要介绍了SpringBoot实现国际化的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

前言

SpringBoot提供了国际化功能,其原理是将配置的各个语言资源文件信息,以Map的形式进行缓存。

当前端请求给定某个语言标识时(一般是放到请求头中),拿去指定的语言标识去获取响应的响应信息。

Springboot项目启动时,由MessageSourceAutoConfiguration类进行消息资源自动配置。

该类存在 @Conditional 条件注解,也就是说必须满足某个条件是才会进行自动装载配置。

  • ResourceBundleCondition类用于判断是否满足自动注入条件。
  • getMatchOutcome用于返回一个 ConditionOutcome 对象,用于后续判断是否满足自动注入条件。该方法会自动读取spring.messages.basename配置的资源文件地址信息,通过getResources方法获取默认的文件资源。如果该资源不存在,则不满足自动注入条件。
  • getResources明确标注了只能从classpath*下拿去资源文件,文件类型为properties
/**
         * 判断是否满足自动注入条件
         * @param context the condition context
         * @param metadata the annotation metadata
         * @return
         */
        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
            ConditionOutcome outcome = cache.get(basename);
            if (outcome == null) {
                outcome = getMatchOutcomeForBasename(context, basename);
                cache.put(basename, outcome);
            }
            return outcome;
        }

        private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
            ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
            for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
                for (Resource resource : getResources(context.getClassLoader(),name)) {
                    if (resource.exists()) {
                        return ConditionOutcome.match(message.found("bundle").items(resource));
                    }
                }
            }
            return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
        }

        //读取消息资源文件,从classpath下寻找格式为properties类型的资源文件
        private Resource[] getResources(ClassLoader classLoader, String name) {
            String target = name.replace('.', '/');
            try {
                return new PathMatchingResourcePatternResolver(classLoader)
                        .getResources("classpath*:" + target + ".properties");
            }
            catch (Exception ex) {
                return NO_RESOURCES;
            }
        }

MessageSourceProperties类则用于配置消息源的配置属性。在ResourceBundleCondition返回条件成立的情况下,会通过注解Bean进行注入。读取前缀带有spring.messages的配置信息。

  @Bean
    @ConfigurationProperties(prefix = "spring.messages")
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

MessageSourceProperties类可配置的属性并不多,具体属性含义用途如下:

#配置国际化资源文件路径,基础包名名称地址
spring.messages.basename=il8n/messages
#编码格式,默认使用UTF-8
spring.messages.encoding=UTF-8
#是否总是应用MessageFormat规则解析信息,即使是解析不带参数的消息
spring.messages.always-use-message-format=false
# 是否使用消息代码作为默认消息,而不是抛出NoSuchMessageException.建议在开发测试时开启,避免不必要的抛错而影响正常业务流程
spring.messages.use-code-as-default-message=true

MessageSourceProperties完成读取配置之后,将会自动注入MessageSource,而默认注入的MessageSource的实现类ResourceBundleMessageSourceResourceBundleMessageSourceSpringBoot实现国际化的核心。其采用的是及时加载文件的形式。

即:只有当某种特定的语言要求被返回时,才会去读取资源文件,将消息内容缓存起来并通过响应码进行返回具体消息。ResourceBundleMessageSource的源码并不复杂,这里就不展开讲解。

 @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        //消息资源绑定类,用于缓存资源消息
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
            //设置资源消息默认包名
            messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }
        if (properties.getEncoding() != null) {
            //设置编码格式
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }
        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            //设置消息资源过期时间
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }
        //是否总是应用MessageFormat规则解析信息,即使是解析不带参数的消息
        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        //是否使用消息代码作为默认消息,而不是抛出NoSuchMessageException.建议在开发测试时开启,避免不必要的抛错而影响正常业务流程
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }

一、配置消息资源文件

消息资源文件用于存储不同国家语言响应的消息,我们通过源码知道该文件必须是properties类型(在你不去更改源码的时候),因此我们需要在resources资源文件下存放消息文件。

这里我存放三个文件,message基础资源文件(内容可以为空),messages_en_US.properties英文文件,messages_zh_CN.properties中文文件。

messages_zh_CN.propertiesmessages_en_US.properties内容如下:

二、配置消息源

这里采用的是以application.properties形式进行配置,您也可以采用yaml文件形式进行配置:

#配置国际化资源文件路径
spring.messages.basename=il8n/messages
#编码格式,默认使用UTF-8
spring.messages.encoding=UTF-8
#是否总是应用MessageFormat规则解析信息,即使是解析不带参数的消息
spring.messages.always-use-message-format=false
# 是否使用消息代码作为默认消息,而不是抛出NoSuchMessageException.建议在开发测试时开启,避免不必要的抛错而影响正常业务流程
spring.messages.use-code-as-default-message=true

三、配置消息拦截器

拦截器用于从请求头中获取语言标识,以便于后续根据语言标识响应不同的响应信息。

package com.il8n.config;

import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.support.RequestContextUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author: Greyfus
 * @Create: 2024-01-12 13:06
 * @Version: 1.0.0
 * @Description:国际化拦截器
 */
@Setter
@Getter
public class IL8nLangInterceptor extends LocaleChangeInterceptor {

    private String langHeader;

    @Override
    public boolean preHandle(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws ServletException {
        String locale = request.getHeader(getLangHeader());
        if (locale != null) {
            if (iL8nCheckHttpMethod(request.getMethod())) {
                LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
                if (localeResolver == null) {
                    throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");
                }
                try {
                    localeResolver.setLocale(request, response, parseLocaleValue(locale));
                } catch (IllegalArgumentException ex) {
                    if (isIgnoreInvalidLocale()) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Ignoring invalid locale value [" + locale + "]: " + ex.getMessage());
                        }
                    } else {
                        throw ex;
                    }
                }
            }
        }
        // Proceed in any case.
        return true;
    }

    public boolean iL8nCheckHttpMethod(String currentMethod) {
        String[] configuredMethods = getHttpMethods();
        if (ObjectUtils.isEmpty(configuredMethods)) {
            return true;
        }
        for (String configuredMethod : configuredMethods) {
            if (configuredMethod.equalsIgnoreCase(currentMethod)) {
                return true;
            }
        }
        return false;
    }

}

注入消息拦截器,并设置默认语言为中文

package com.il8n.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import java.util.Locale;

/**
 * @Author: Greyfus
 * @Create: 2024-01-12 00:22
 * @Version: 1.0.0
 * @Description:语言国际化配置
 */
@Configuration
public class IL8nLangConfig implements WebMvcConfigurer {

    private static final String LANG_HEADER = "lang";
    
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
        sessionLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return sessionLocaleResolver;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        IL8nLangInterceptor lci = new IL8nLangInterceptor();
        //设置请求的语言变量
        lci.setLangHeader(LANG_HEADER);
        return lci;
    }

    /**
     * 注册拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }

}

四、编写消息工具类

消息工具类用于通过指定的消息码获取对应的响应消息

package com.il8n.config;

import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @Author: Greyfus
 * @Create: 2024-01-12 00:36
 * @Version: 1.0.0
 * @Description: 工具
 */
@Component
public class SpringUtils implements ApplicationContextAware {
    @Getter
    private static ApplicationContext applicationContext;

    public SpringUtils() {
    }

    public void setApplicationContext(@NotNull ApplicationContext applicationContext) {
        SpringUtils.applicationContext = applicationContext;
    }

    public static <T> T getBean(String name) {
        return (T) applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    public static <T> T getBean(String name, Class<T> clazz) {
        return applicationContext.getBean(name, clazz);
    }

    public static <T> Map<String, T> getBeansOfType(Class<T> type) {
        return applicationContext.getBeansOfType(type);
    }

    public static String[] getBeanNamesForType(Class<?> type) {
        return applicationContext.getBeanNamesForType(type);
    }

    public static String getProperty(String key) {
        return applicationContext.getEnvironment().getProperty(key);
    }

    public static String[] getActiveProfiles() {
        return applicationContext.getEnvironment().getActiveProfiles();
    }

}
package com.il8n.config;

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;

/**
 * @Author: Greyfus
 * @Create: 2024-01-12 00:29
 * @Version: 1.0.0
 * @Description: IL8N语言转换工具
 */
public class IL8nMessageUtils {


    private static final MessageSource messageSource;

    static {
        messageSource = SpringUtils.getBean(MessageSource.class);
    }

    /**
     * 获取国际化语言值
     *
     * @param messageCode
     * @param args
     * @return
     */
    public static String message(String messageCode, Object... args) {
        return messageSource.getMessage(messageCode, args, LocaleContextHolder.getLocale());
    }
}

五、测试国际化

编码一个Controller用于模拟测试效果,代码如下:

package com.il8n.controller;

import com.il8n.config.IL8nMessageUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: DI.YIN
 * @Date: 2025/1/6 9:37
 * @Version:
 * @Description:
 **/
@RestController
@RequestMapping("/mock")
public class IL8nTestController {

    @RequestMapping(value = "/login")
    public ResponseEntity<String> login(@RequestParam(value = "userName") String userName, @RequestParam(value = "password") String password) {
        if (!"admin".equals(userName) || !"admin".equals(password)) {
            return new ResponseEntity<>(IL8nMessageUtils.message("LoginFailure", (Object) null), HttpStatus.OK);
        }
        return new ResponseEntity<>(IL8nMessageUtils.message("loginSuccess", userName), HttpStatus.OK);
    }
}

消息拦截器会尝试从请求头中获取属性为lang的值,将其作为语言标识,因此我们在使用PostMan模拟时,需要在请求头中增加lang属性。

模拟结果如下:

中文语言标识:

英文语言标识:

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Window中安装构建神器Jenkins详解

    Window中安装构建神器Jenkins详解

    Jenkins是一款开源 CI&CD 软件,用于自动化各种任务,包括构建、测试和部署软件。支持各种运行方式,可通过系统包、Docker 或者通过一个独立的 Java 程序。是解放人工集成部署的自动化构建神器
    2021-07-07
  • SpringBoot集成MongoDB的实现

    SpringBoot集成MongoDB的实现

    本文主要介绍了SpringBoot集成MongoDB的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-01-01
  • Java中保证线程顺序执行的操作代码

    Java中保证线程顺序执行的操作代码

    本文给大家分享一篇教程关于java线程顺序执行问题,如何保证线程的顺序执行呢?今天通过实例代码给大家详细讲解下,感兴趣的朋友跟随小编一起看看吧
    2021-05-05
  • springboot获取根目录下lib目录下文件位置

    springboot获取根目录下lib目录下文件位置

    在 Spring Boot 项目中,如果你有一个 lib 目录,并且需要访问这个目录下的文件,你可以通过几种不同的方式来获取该文件的位置,下面小编就来和大家详细讲讲
    2024-12-12
  • 详解Java读取本地文件并显示在JSP文件中

    详解Java读取本地文件并显示在JSP文件中

    这篇文章主要介绍了详解Java读取本地文件并显示在JSP文件中的相关资料,这里提供实例帮助大家实现这样的功能,希望能帮助到大家,需要的朋友可以参考下
    2017-08-08
  • SpringBoot整合RabbitMQ, 实现生产者与消费者的功能

    SpringBoot整合RabbitMQ, 实现生产者与消费者的功能

    这篇文章主要介绍了SpringBoot整合RabbitMQ, 实现生产者与消费者的功能,帮助大家更好得理解和学习使用SpringBoot框架,感兴趣的朋友可以了解下
    2021-03-03
  • Spring Boot使用yml格式进行配置的方法

    Spring Boot使用yml格式进行配置的方法

    很多springboot项目使用的是yml格式,主要目的是方便对读懂其他人的项目,下面小编通过本文给大家分享Spring Boot使用yml格式进行配置的方法,需要的朋友参考下吧
    2018-04-04
  • Java 1.8使用数组实现循环队列

    Java 1.8使用数组实现循环队列

    这篇文章主要为大家详细介绍了Java 1.8使用数组实现循环队列,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-10-10
  • Android开发Kotlin实现圆弧计步器示例详解

    Android开发Kotlin实现圆弧计步器示例详解

    这篇文章主要为大家介绍了Android开发Kotlin绘制圆弧计步器示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Java图形化编程中的键盘事件设计简介

    Java图形化编程中的键盘事件设计简介

    这篇文章主要介绍了Java图形化编程中的键盘事件设计,是Java的GUI编程当中的基础部分,需要的朋友可以参考下
    2015-10-10

最新评论