Springboot实现国际化(MessageSource)
背景
- 在当今全球化的数字时代,软件应用的用户群体不再局限于单一语言或地区。无论是面向全球市场的电商平台,还是提供跨国服务的企业级应用,都需要能够适应不同语言环境,以满足全球用户的个性化需求。这就引出了一个关键的技术需求——国际化(
Internationalization
,通常简称为i18n
)。 - Spring Boot 作为一款强大的后端开发框架,提供了多种方式来实现国际化,而 MessageSource 是其中一种非常实用且广泛采用的实现方式。接下来,我们将以
MessageSource
为例子实现国际化。
配置文件
# 国际化配置
spring:messages:default-locale: zh-CN # 默认中文encoding: UTF-8 # 确认国际化文件采用utf-8格式,否则乱码basename: i18n/messages # 放在resources/i18n目录下
配置类
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;import java.util.Locale;/*** MessageSourceConfig 消息源配置** @author xxx* @since 2025/9/4*/
@Configuration
public class MessageSourceConfig {@Value("${spring.messages.default-locale:}")private String defaultLocale;@Value("${spring.messages.encoding}")private String encoding;@Value("${spring.messages.basename}")private String basename;@Beanpublic MessageSource messageSource() {ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();messageSource.setBasename(basename);messageSource.setDefaultEncoding(encoding);messageSource.setDefaultLocale(Locale.forLanguageTag(defaultLocale));return messageSource;}
}
工具类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;import java.util.Locale;/*** MessageSourceUtil 消息工具类** @author xxx* @since 2025/9/4*/
@Component
public class MessageSourceUtil {private static MessageSource messageSource;@Autowiredpublic MessageSourceUtil(MessageSource messageSource) {MessageSourceUtil.messageSource = messageSource;}public static String getMessage(String code, Object[] args) {Locale locale = LocaleContextHolder.getLocale();return messageSource.getMessage(code, args, locale);}
}
国际化文件
修改属性文件编码格式
国际化文件
在 resources/i18n 目录下新建:
messages_en_US.properties
PERM_NO_PERMISSION=No permission to operate
messages_zh_CN.properties
PERM_NO_PERMISSION=没有权限执行该操作
业务错误枚举类
import cn.com.techvision.mdm.common.utils.MessageSourceUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;/*** BusinessErrorConstant 业务错误枚举值** @author xxx* @since 2025/8/16*/
@AllArgsConstructor
@Getter
public enum BusinessErrorConstant {PERM_NO_PERMISSION(403, "PERM-ERR-001"),;/*** 状态码*/private final int statusCode;/*** 业务错误码*/private final String errCode;public static String getErrMsg(BusinessErrorConstant businessErrorConstant, Object... elements) {return String.format(MessageSourceUtil.getMessage(businessErrorConstant.name(), null), elements);}
}
在响应体中使用
响应体结构
import cn.com.xxx.BusinessErrorConstant;
import lombok.Data;
import lombok.NoArgsConstructor;/*** 响应包的基类*/
@Data
@NoArgsConstructor
public class BaseResponse<T> {private int status;private String errCode;private String msg;private T data;private BaseResponse(int status, String errCode, String msg, T data) {this.status = status;this.errCode = errCode;this.msg = msg;this.data = data;}public static <T> BaseResponse<T> error(BusinessErrorConstant businessErrorConstant, Object... args) {return new BaseResponse<>(businessErrorConstant.getStatusCode(), businessErrorConstant.getErrCode(),BusinessErrorConstant.getErrMsg(businessErrorConstant, args), null);}
}
测试
测试类
@RestController
@RequestMapping("/system/org/{orgId}/project/{projectId}/test")
@Slf4j
public class TestController {@GetMapping("/i18n-res-err")public BaseResponse<String> testI18nResErr(){return BaseResponse.error(BusinessErrorConstant.PERM_NO_PERMISSION);}
}
测试用例
Accept-Language=en-US
curl --location --request GET 'http://localhost:24433/system/org/1/project/-1/test/i18n-res-err' \
--header 'Accept-Language: en-US' \
--header 'Content-Type: application/json' \
--data-raw ''
响应结果
{"status": 403,"errCode": "PERM-ERR-001","msg": "No permission to operate","data": null
}
Accept-Language=zh-CN
curl --location --request GET 'http://localhost:24433/system/org/1/project/-1/test/i18n-res-err' \
--header 'Accept-Language: zh-CN' \
--header 'Content-Type: application/json' \
--data-raw ''
响应结果
{"status": 403,"errCode": "PERM-ERR-001","msg": "没有权限执行该操作","data": null
}
在自定义业务异常中使用
import cn.com.xxx.BusinessErrorConstant;
import cn.com.xxx.MessageSourceUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;/*** BusinessException 业务异常** @author xxx* @since 2025/9/4*/
@EqualsAndHashCode(callSuper = true)
@Data
@Getter
public class BusinessException extends RuntimeException {/*** 状态码*/private final int statusCode;/*** 业务错误码*/private final String code;/*** 错误信息*/private final String message;public BusinessException(BusinessErrorConstant constant) {super(MessageSourceUtil.getMessage(constant.name(), null));this.statusCode = constant.getStatusCode();this.code = constant.getErrCode();this.message = MessageSourceUtil.getMessage(constant.name(), null);}public BusinessException(BusinessErrorConstant constant, Object... elements) {super(String.format(MessageSourceUtil.getMessage(constant.name(), null), elements));this.statusCode = constant.getStatusCode();this.code = constant.getErrCode();this.message = String.format(MessageSourceUtil.getMessage(constant.name(), null), elements);}
}
全局异常捕获
import cn.com.xxx.BaseResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {@ExceptionHandler({BusinessException.class})public BaseResponse<String> businessExceptionHandler(BusinessException businessException){log.error(businessException.getMessage(), businessException);return BaseResponse.error(businessException.getStatusCode(), businessException.getCode(),businessException.getMessage(), null);}@ExceptionHandler({Exception.class})public void globalExceptionHandler(Exception ex, HttpServletResponse response){log.error(ex.getMessage(), ex);response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);}
}
测试
测试类
@RestController
@RequestMapping("/system/org/{orgId}/project/{projectId}/test")
@Slf4j
public class TestController {@GetMapping("/i18n-exception")public BaseResponse<String> testI18nThrowException(){throw new BusinessException(BusinessErrorConstant.PERM_NO_PERMISSION);}
}
测试用例
Accept-Language=en-US
curl --location --request GET 'http://localhost:24433/system/org/1/project/-1/test/i18n-exception' \
--header 'Accept-Language: en-US' \
--header 'Content-Type: application/json' \
--data-raw ''
响应结果
{"status": 403,"errCode": "PERM-ERR-001","msg": "No permission to operate","data": null
}
Accept-Language=zh-CN
curl --location --request GET 'http://localhost:24433/system/org/1/project/-1/test/i18n-exception' \
--header 'Accept-Language: zh-CN' \
--header 'Content-Type: application/json' \
--data-raw ''
响应结果
{"status": 403,"errCode": "PERM-ERR-001","msg": "没有权限执行该操作","data": null
}