• SpringBoot - SpringBoot整合i18n实现消息国际化


    在我们开发WEB项目的时候,项目可能涉及到在国外部署或者应用,也有可能会有国外的用户对项目进行访问,那么在这种项目中, 为客户展现的页面或者操作的信息就需要根据客户系统的环境来使用不同的语言,这就是我们所说的项目国际化。

    1. MessageSource源码

    Spring中定义了一个MessageSource接口,以用于支持信息的国际化和包含参数的信息的替换,MessageSource接口源码如下:

    public interface MessageSource {
    
       /**
        * 解析code对应的信息进行返回,如果对应的code不能被解析则返回默认信息defaultMessage。
        *
        * @param code 需要进行解析的code,对应资源文件中的一个属性名
        * @param args 当对应code对应的信息不存在时需要返回的默认值
        * @param defaultMessage 需要用来替换code对应的信息中包含参数的内容,如:{0},{1,date},{2,time}
        * @param locale 对应的Locale
        * @return 国际化翻译值
        */
       @Nullable
       String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
    
       /**
        * 解析code对应的信息进行返回,如果对应的code不能被解析则抛出异常NoSuchMessageException
        *
        * @param code 需要进行解析的code,对应资源文件中的一个属性名
        * @param args 当对应code对应的信息不存在时需要返回的默认值
        * @param locale 需要用来替换code对应的信息中包含参数的内容,如:{0},{1,date},{2,time}
        * @return 国际化翻译值
        * @throws NoSuchMessageException 如果对应的code不能被解析则抛出该异常
        */
       String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
    
       /**
        * 通过传递的MessageSourceResolvable对应来解析对应的信息
        *
        * @param resolvable  MessageSourceResolvable
        * @param locale 对应的Locale
        * @return 国际化翻译值
        * @throws NoSuchMessageException 如不能解析则抛出该异常
        */
       String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2. 项目环境搭建

    1. 创建项目服务auth

    ① 创建一个服务auth,导入常用依赖,项目整体结构为:

    在这里插入图片描述

    ② SpringBoot配置文件

    server:
      port: 8080
    
    spring:
      datasource:
        druid:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/storage?useUnicode=true&characterEncoding=utf-8&useSSL=false
          username: root
          password: root
      # 资源信息
      messages:
        encoding: utf-8
        # 国际化资源文件路径(配置文件路径)
        basename: i18n/messages
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    2. 工具类 I18nUtils
    // @Autowired 自动装配仅在托管类中有效(例如,注释为@ Component,@ Service或在应用程序上下文xml中定义)。
    @Component
    @Slf4j
    public class I18nUtils {
        
        // 如果当前bean不加@Component注解,则messageSource无法注入,始终为null
        private static MessageSource messageSource;
    
        @Autowired
        public void setMessageSource(MessageSource messageSource) {
            I18nUtils.messageSource = messageSource;
        }
    
        /**
         * 解析code对应的信息进行返回,如果对应的code不能被解析则抛出异常NoSuchMessageException
         *
         * @param code 需要进行解析的code,对应资源文件中的一个属性名
         * @param args 当对应code对应的信息不存在时需要返回的默认值
         * @return 国际化翻译值
         */
        public static String i18n(String code, Object... args) {
            return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
        }
    
        /**
         * 解析code对应的信息进行返回,如果对应的code不能被解析则返回默认信息defaultMessage。
         *
         * @param code 需要进行解析的code,对应资源文件中的一个属性名
         * @param defaultMessage 当对应code对应的信息不存在时需要返回的默认值
         * @param args 需要用来替换code对应的信息中包含参数的内容,如:{0},{1,date},{2,time}
         * @return 对应的Locale
         */
        public static String i18nOrDefault(String code, String defaultMessage, Object... args) {
            return messageSource.getMessage(code, args, defaultMessage, LocaleContextHolder.getLocale());
        }
    
        /**
         * 因为i18n方法如果获取不到对应的键值,会抛异常NoSuchMessageException
         * 本方法是对i18n方法的封装。当报错时并不抛出异常,而是返回source
         *
         * @param source 模板
         * @param args   参数
         * @return 返回I18n(正常结束)或者source(抛出异常)
         * @see #i18n(String, Object...)
         */
        @NonNull
        public static String tryI18n(@NonNull String source, @NonNull Object... args) {
            String res;
            try {
                res = i18n(source, args);
            } catch (Exception ignored) {
                res = source;
            }
            return res;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    3. 自定义异常 CommonException
    public class CommonException extends RuntimeException {
    
        public CommonException(String i18eCode) {
            // 根据资源文件的属性名以及当前语言环境,获取国际化信息
            super(I18nUtils.tryI18n(i18eCode));
        }
    
        public CommonException(String i18eCode, Object... args) {
            // 根据资源文件的属性名,属性值中的参数以及当前语言环境,获取国际化信息
            // args用来替换资源文件属性值中的占位符参数
            super(I18nUtils.tryI18n(i18eCode, args));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    4. 统一异常处理 GlobalExceptionHandler
    @RestControllerAdvice
    @Slf4j
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(CommonException.class)
        public ApiResponse<Object> handleCommonException(CommonException e) {
            log.error(e.getMessage(), e);
            return new ApiResponse<>(-1,"error",e.getMessage());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3. 业务实现

    1. 实体类 UserEntity
    @Data
    public class UserEntity {
    
        private Integer id;
    
        private String name;
    
        private String password;
    
        private Date createTime;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    2. 请求实体 UserQo
    @Data
    public class UserQo {
    
        private Integer id;
    
        @ApiModelProperty("用户名")
        private String name;
    
        @ApiModelProperty("密码")
        private String password;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    3. 控制层 UserController
    @RestController
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @PostMapping("/add")
        public ApiResponse<Object> add(@Validated @RequestBody UserQo userQo){
            userService.insertUser(userQo);
            return new ApiResponse<>(0,"success");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    4. 业务逻辑层 UserService
    @Service
    public class UserService {
    
        @Resource
        private UserDao userDao;
    
        public void insertUser(UserQo userQo){
            UserEntity userEntity = userDao.findByName(userQo.getName());
            if(Objects.nonNull(userEntity)){
                // i18n带有参数
                String name = userQo.getName();
                throw new CommonException("exception.name.can.not.repeat",name);
            }
            userEntity = new UserEntity();
            BeanUtils.copyProperties(userQo,userEntity);
            userEntity.setCreateTime(new Date());
            int count = userDao.insert(userEntity);
            if(count==1){
                // i18n不带有参数
                throw new CommonException("exception.insert.data.to.db");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    5. 将异常信息写入i18n资源文件

    messages.properties:

    exception.name.can.not.repeat=exception name can not repeat,the name is {0}
    exception.insert.data.to.db=exception insert data to db
    
    • 1
    • 2

    messages_zh_CN.properties:

    exception.name.can.not.repeat=用户名不能为重复,用户名为:{0}
    exception.insert.data.to.db=新增数据到数据库异常
    
    • 1
    • 2
    6. 项目测试

    用户名重复时抛出异常CommonException,异常会被捕获并进行统一异常处理:

    在这里插入图片描述

    添加数据到数据库中时抛出异常CommonException,异常会被捕获并进行统一异常处理:

    在这里插入图片描述

    4. 多服务下使用 i18n

    1. 调整项目结构

    将原来的 auth 服务拆分为为 auth-model和 auth-core 服务,他们都在auth服务下:

    在这里插入图片描述

    其中auth-model模块主要请求Qo,响应Vo,实体类Entity,在响应的依赖中导入对应的服务即可。

    2. 父模块 auth

    pom文件

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.5.6version>
            <relativePath/>
        parent>
    
        <artifactId>authartifactId>
        <groupId>com.hhgroupId>
        <version>1.0-SNAPSHOTversion>
    
        <packaging>pompackaging>
    
        <modules>
            <module>auth-coremodule>
            <module>auth-modelmodule>
        modules>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    3. 子模块 auth-core

    ① pom文件

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>authartifactId>
            <groupId>com.hhgroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>auth-coreartifactId>
        
        <dependencies>
            
            <dependency>
                <groupId>com.hhgroupId>
                <artifactId>auth-modelartifactId>
                <version>1.0-SNAPSHOTversion>
            dependency>
            
             
        dependencies>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    ② SpringBoot 配置:

    server:
      port: 8080
    
    spring:
      datasource:
        druid:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/storage?useUnicode=true&characterEncoding=utf-8&useSSL=false
          username: root
          password: root
      # 资源信息
      messages:
        encoding: utf-8
        # 国际化资源文件路径,需要配置当前模块和依赖模块的资源文件路径
        basename: i18n/core,i18n/module
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ③ 抛出异常信息,并对消息进行国际化处理:

    @Service
    public class UserService {
    
        @Resource
        private UserDao userDao;
    
        public void insertUser(UserQo userQo){
            UserEntity userEntity = userDao.findByName(userQo.getName());
            if(Objects.nonNull(userEntity)){
                // i18n带有参数
                String name = userQo.getName();
                throw new CommonException("exception.name.can.not.repeat",name);
            }
            userEntity = new UserEntity();
            BeanUtils.copyProperties(userQo,userEntity);
            userEntity.setCreateTime(new Date());
            int count = userDao.insert(userEntity);
            if(count==1){
                // i18n不带有参数
                throw new CommonException("exception.insert.data.to.db");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    ④ 因为是auth-core模块抛出的异常消息,因此需要写在i18n/core下面的资源文件中,如果是auth-model模块抛出的异常,则写在auth-model服务i18n/model下面的资源文件中。

    4. 子模块 auth-model
    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>authartifactId>
            <groupId>com.hhgroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>auth-modelartifactId>
        
        <dependencies>
            
        dependencies>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    【问题记录】解决Git上传文件到GitHub时收到 “GH001: Large files detected” 错误信息!
    【数据结构】顺序表和链表
    洛谷刷题C语言:距离函数、闰年展示、计算阶乘、猴子吃桃、培训
    C++ vector 效率之capacity()、resize()、reserve()
    源码分析: kafka 的微服务架构
    Docker之构建镜像
    Redis之与SSM集成Spring注解式缓存
    react-router-dom 实用技巧及3种传参方式
    Vue 中的 computed 和 watch 的区别
    观点:DeFi和区块链游戏 两者中谁才是杀手级应用?
  • 原文地址:https://blog.csdn.net/qq_42764468/article/details/127560300