文章目录
- 1. MessageSource源码
- 2. 项目环境搭建
- 1. 创建项目服务auth
- 2. 工具类 I18nUtils
- 3. 自定义异常 CommonException
- 4. 统一异常处理 GlobalExceptionHandler
 
- 3. 业务实现
- 1. 实体类 UserEntity
- 2. 请求实体 UserQo
- 3. 控制层 UserController
- 4. 业务逻辑层 UserService
- 5. 将异常信息写入i18n资源文件
- 6. 项目测试
 
- 4. 多服务下使用 i18n
- 1. 调整项目结构
- 2. 父模块 auth
- 3. 子模块 auth-core
- 4. 子模块 auth-model
 
 
 
在我们开发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;
}
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
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;
    }
}
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));
    }
}
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());
    }
}
3. 业务实现
1. 实体类 UserEntity
@Data
public class UserEntity {
    private Integer id;
    private String name;
    private String password;
    private Date createTime;
}
2. 请求实体 UserQo
@Data
public class UserQo {
    private Integer id;
    @ApiModelProperty("用户名")
    private String name;
    @ApiModelProperty("密码")
    private String password;
}
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");
    }
}
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");
        }
    }
}
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
messages_zh_CN.properties:
exception.name.can.not.repeat=用户名不能为重复,用户名为:{0}
exception.insert.data.to.db=新增数据到数据库异常
6. 项目测试
用户名重复时抛出异常CommonException,异常会被捕获并进行统一异常处理:

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

4. 多服务下使用 i18n
1. 调整项目结构
将原来的 auth 服务拆分为为 auth-model和 auth-core 服务,他们都在auth服务下:

其中auth-model模块主要请求Qo,响应Vo,实体类Entity,在响应的依赖中导入对应的服务即可。
2. 父模块 auth
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <relativePath/>
    </parent>
    <artifactId>auth</artifactId>
    <groupId>com.hh</groupId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>auth-core</module>
        <module>auth-model</module>
    </modules>
</project>
3. 子模块 auth-core
① pom文件
<?xml version="1.0" encoding="UTF-8"?>
<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>auth</artifactId>
        <groupId>com.hh</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>auth-core</artifactId>
    
    <dependencies>
        <!--依赖auth-module模块-->
        <dependency>
            <groupId>com.hh</groupId>
            <artifactId>auth-model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        
         <!--省略.....-->
    </dependencies>
</project>
② 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
③ 抛出异常信息,并对消息进行国际化处理:
@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");
        }
    }
}
④ 因为是auth-core模块抛出的异常消息,因此需要写在i18n/core下面的资源文件中,如果是auth-model模块抛出的异常,则写在auth-model服务i18n/model下面的资源文件中。
4. 子模块 auth-model
<?xml version="1.0" encoding="UTF-8"?>
<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>auth</artifactId>
        <groupId>com.hh</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>auth-model</artifactId>
    
    <dependencies>
        <!--省略.....-->
    </dependencies>
</project>



















