Spring Boot国际化的两种实现及logback配置

Spring Boot国际化的基本步骤

  • 定义国际化资源文件 xxx.properties
  • 配置文件application.yml中定义国际化资源文件的路径
  • 确定并实现对应国际化策略(一般使用拦截器实现)

几种国际化实现策略

1)部署时通过环境变量确定国际化语言,部署完成后不能改变。适合场景:软件部署后,只服务于同一语言环境的用户,部署后不需要改变。如,在国内部署,部署为中文,访问者均使用中文环境;国外部署,访问者均使用英文环境。
2)通过HTTP请求中Url中的特定参数来标识国际化语言,所有的GET/POST等请求,均需要在Url中携带该特定参数。如,http://127.0.0.1:8080/?lang=en_US
3)通过HTTP请求中携带的Cookie、Header或Session信息中的特定参数标识国际化语言,只需要在请求发送时在Cookie、Header或Session中携带同一的特定参数即可。如,所有的请求发送时,均使用统一的Ajax请求接口,统一添加Cookie信息即可。

优缺点:
第1)种方案,方案最简单易行,不需要拦截每个HTTP请求,没有性能损耗。但是,使用场景有限,只适用于同一语言环境内运行,服务端部署后不能提供跨语言的服务。
第2)种方案,修改Url中的参数即可实现中英文的随意切换,比较灵活,也适合多个应用之间协同提供服务时语言环境的传递。比如,A应用通过URL跳转到B应用,A应用的语言环境就可以通过URL参数的方式带到B应用。但是Url中总要携带语言参数,显得冗余。
第3)种方案,修改HTTP请求中的Cookie等信息即可,是目前比较常用的一种方案。前端Ajax请求时Cookie中携带统一的语言类型字段即可,后端也只需要从Cookie中解析出语言环境即可。

国际化实现

创建国际化资源

resources文件夹下创建i18n文件夹,创建默认的属性文件messages.properties及标识中英文的其他两个属性文件messages_zh_CN.properties,messages_en_US.properties即可。
本文示例创建两组属性文件,因为不同业务的属性文件通常分开管理,避免资源文件后期膨胀过大。
i18n下创建login文件夹,其中创建login.properties、login_zh_CN.properties、login_en_US.properties三个属性文件;创建errorMessage文件夹,属性文件同login。
在这里插入图片描述

配置文件application.yml或application.properties中配置spring.messages.basename

本文使用application.yml。

spring:
  messages:
    encoding: UTF-8
    # MessageSourceAutoConfiguration.class中加载,classpath*:{basename}.properties
    basename: i18n/login/login,i18n/errorMessage/errorMessage

此处配置错误,获取属性值时,会抛出org.springframework.context.NoSuchMessageException错,具体到No message found under code '你的键名' for locale 'zh_CN'.
具体问题解决,参考Spring Boot 国际化功能实现出现 No message found under code ‘xx’ for locale 'xx’问题处理

Yml与logback配置

application.yml

spring:
  profiles:
    active: dev
  application:
    name: demo
  log:
    path: ../log
    level: DEBUG
  messages:
    encoding: UTF-8
    # MessageSourceAutoConfiguration.class中加载,classpath*:{basename}.properties
    basename: i18n/login/login,i18n/errorMessage/errorMessage

server:
  port: 10095
  servlet:
    context-path: /demo

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />

    <springProperty scope="context" name="springAppName" source="spring.application.name"/>
    <springProperty scope="context" name="springLogPath" source="spring.log.path"/>
    <springProperty scope="context" name="LOG_LEVEL" source="spring.log.level"/>
    <property name="LOG_HOME" value="${springLogPath}"/>
    <property name="LOG_FILE" value="${springAppName}"/>
    <property name="LOG_PATTERN"
              value="[${springAppName}] [%-5p] [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%C{1}:%M:%L] [%thread] %X{traceId} %m%n"/>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${LOG_FILE}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- daily rollover 保存历史记录到这个文件夹一日起为后缀 -->
            <fileNamePattern>${LOG_HOME}/${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- keep 60 days' worth of history -->
            <maxHistory>60</maxHistory>
            <maxFileSize>100MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
    </appender>
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${LOG_FILE}Error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- daily rollover 保存历史记录到这个文件夹一日起为后缀 -->
            <fileNamePattern>${LOG_HOME}/${LOG_FILE}Error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- keep 60 days' worth of history -->
            <maxHistory>60</maxHistory>
            <maxFileSize>100MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>

    <root>
        <level value="${LOG_LEVEL}"/>
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>
</configuration>

注意:logback按照时间和大小切分日志,需要使用ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy,参考:
SpringBoot配置Logback 日志按时间和大小分割失效问题

具体实现

此处只实现第2)/3)种方案,第1)种方案,直接初始化一个Bean从环境变量中读取并初始化语言环境即可。

Url实现方式

配置默认的语言环境和语言环境变化的拦截器。拦截器解析Url中携带的lang=zh_CN或lang=en_US实现中英文的切换(Get/Post同样处理,均解析Url中的参数)。

package com.springboot.demo.web.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;

@Configuration
public class I18nConfig implements WebMvcConfigurer {
    /**
     * 国际化,设置默认的语言为中文
     *
     * @return LocaleResolver
     */
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver slr = new SessionLocaleResolver();
        slr.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return slr;
    }

    /**
     * 国际化,设置url为识别参数,请求的url形式为url?lang=zh_CN 或 url?lang=en_US
     *
     * @return LocaleChangeInterceptor
     */
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        lci.setParamName("lang");
        return lci;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}

新建资源文件服务类

package com.springboot.demo.web.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Locale;

@Slf4j
@Component
public class I18nService {
    @Resource
    private MessageSource messageSource;

    /**
     * 获取国际化资源
     * @param key 国际化资源的key
     * @return 国际化内容
     */
    public String getMessage(String key) {
        String defaultValue = "";
        if (StringUtils.isEmpty(key)) {
            return defaultValue;
        }
        Locale locale = LocaleContextHolder.getLocale();
        if (locale == null) {
            locale = Locale.SIMPLIFIED_CHINESE;
        }
        try {
            return messageSource.getMessage(key, null, locale);
        } catch (Exception ex) {
            log.warn("No message value for key: {}, locale: {}", key, locale, ex);
        }
        return defaultValue;
    }

}

使用时注入该Bean即可

package com.springboot.demo.web.controller;

import com.springboot.demo.web.model.ResultInfo;
import com.springboot.demo.web.service.I18nService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class LoginController {
    private static final String LOGIN_KEY = "login";

    private static final String LOGIN_FAILED_KEY = "login.failed";

    @Resource
    private I18nService i18NService;

    @GetMapping("/login")
    public ResultInfo login() {
        log.info("login.");
        ResultInfo resultInfo = new ResultInfo();
        resultInfo.setMsg(i18NService.getMessage(LOGIN_KEY));
        return resultInfo;
    }

    @GetMapping("/login/failed")
    public ResultInfo loginFailed() {
        log.info("login failed.");
        ResultInfo resultInfo = new ResultInfo();
        resultInfo.setMsg(i18NService.getMessage(LOGIN_FAILED_KEY));
        return resultInfo;
    }
}

Cookie中携带语言标识

Session、Header中携带语言标识,同样处理方式。

新建拦截器,设置中英文

package com.springboot.demo.web.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;

@Component
@Slf4j
public class I18nInterceptor implements HandlerInterceptor {
    private static final String COOKIE_NAME_LANG = "lang";

    private static final String ENGLISH_LANG = "en";

    private static final String ENGLISH_COUNTRY = "US";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        // 1、获取请求的Cookies
        Cookie[] cookies = request.getCookies();

        // 2、遍历Cookie 查看是否有目标Cookie
        String lang = "";
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (COOKIE_NAME_LANG.equals(cookie.getName())) {
                    lang = cookie.getValue();
                    break;
                }
            }
        }

        // 3、设置到语言环境中
        LocaleContextHolder.setLocale(getLocale(lang));
        return true;
    }

    private Locale getLocale(String lang) {
        // 默认中国大陆-简体中文 lang=zh country=CN
        Locale defaultLocale = Locale.SIMPLIFIED_CHINESE;
        if (StringUtils.isEmpty(lang)) {
            return defaultLocale;
        }
        // 英语采用 en_US
        if (lang.toLowerCase(Locale.ENGLISH).contains(ENGLISH_LANG)) {
            return new Locale(ENGLISH_LANG, ENGLISH_COUNTRY);
        }
        return defaultLocale;
    }
}


拦截器注册到spring容器中

package com.springboot.demo.web.config;

import com.springboot.demo.web.interceptor.I18nInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class I18nConfig implements WebMvcConfigurer {
    @Resource
    private I18nInterceptor i18nInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(i18nInterceptor);
    }
}

资源文件服务类I18nService和使用方式同Url实现方式。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值