Spring Boot 实现国际化消息提示

tech2026-06-12  1

前言

本文介绍使用Spring Boot实现国际化信息(i18n)提示。

Spring Boot 官方文档中关于国际化的文档资料:boot-features-internationalization

默认国际化配置

SpringBoot提供了自动配置类org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration。

可以看到自动配置类中提供的可配置参数为spring.messages,其中典型配置为:

#默认配置国际化文件路径 spring.messages.basename=messages

默认将读取resources名为messages的Resource Bundle文件。

如果要使用该自动化配置的国际化资源,注入org.springframework.context.MessageSource实例即可。

自定义国际化配置

如果需要更灵活的国际化配置,可以自行指定对应的国际化资源配置,自行读取。

这里以一个动态响应错误信息的国际化示例为例。

创建ResultCodeMessages国际化文件

在resources目录下创建i18n文件夹,用于存放国际化文件。

在i18n目录下创建名为ResultCodeMessages的Resource Bundle文件。

ResultCodeMessages.properties

00=success 97=request param error 98=not found 99=fail. arg : {0} , {1}

ResultCodeMessages_en.properties

#同ResultCodeMessages.properties,这里默认Locale为ENGLISH 00=success 97=request param error 98=not found 99=fail. arg : {0} , {1}

ResultCodeMessages_zh_CN.properties

00=成功 97=请求入参校验出错 98=未找到 99=失败。错了,说点啥吧:{0},{1}

创建标准响应结果类和响应码枚举类

响应码枚举类

public enum ResultCodeEnum { //成功 SUCCESS("00"), //失败 FAIL("99"), //未找到 NOT_FOUND("98"), //请求入参校验出错 REQUEST_PARAM_ERROR("97"); private final String code; public String getCode() { return code; } ResultCodeEnum(String code) { this.code = code; } public static ResultCodeEnum getEnum(String code) { for (ResultCodeEnum resultCodeEnum : ResultCodeEnum.values()) { if (resultCodeEnum.getCode().equals(code)) { return resultCodeEnum; } } return null; } }

标准响应结果类

@Data @JsonInclude(JsonInclude.Include.NON_NULL) public class ApiResult<T> implements Serializable { private String code; private String message; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime timestamp; private T data; private ApiResult(String code, String message, T data) { this.code = code; this.message = message; this.timestamp = LocalDateTime.now(); this.data = data; } public static <T> ApiResult<T> success(T data) { return new ApiResult<>(ResultCodeEnum.SUCCESS.getCode(), null, data); } public static ApiResult<Void> fail(String code, String message) { return new ApiResult<>(code, message, null); } }

自定义国际化拦截器

Spring Boot中国际化拦截器默认为org.springframework.web.servlet.i18n.LocaleChangeInterceptor,默认国际化参数为URL参数locale。

这里自定义修改国际化拦截器,默认国际化参数为URL参数(可以修改为从请求头获取),这里不做实质上的修改

public class CustomizeLocaleChangeInterceptor extends LocaleChangeInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException { //从URL参数中获取 String newLocale = request.getParameter(getParamName()); //可修改成从请求头获取 //String newLocale = request.getHeader(getParamName()); if (newLocale != null) { if (checkHttpMethod(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(newLocale)); } catch (IllegalArgumentException ex) { if (isIgnoreInvalidLocale()) { if (logger.isDebugEnabled()) { logger.debug("Ignoring invalid locale value [" + newLocale + "]: " + ex.getMessage()); } } else { throw ex; } } } } // Proceed in any case. return true; } private boolean checkHttpMethod(String currentMethod) { String[] configuredMethods = getHttpMethods(); if (ObjectUtils.isEmpty(configuredMethods)) { return true; } for (String configuredMethod : configuredMethods) { if (configuredMethod.equalsIgnoreCase(currentMethod)) { return true; } } return false; } }

注册自定义国际化拦截器

@Configuration public class WebConfiguration implements WebMvcConfigurer { @Resource(name = "customizeLocaleChangeInterceptor") LocaleChangeInterceptor localeChangeInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(localeChangeInterceptor); } }

国际化配置

i18n配置

@Configuration public class LocaleConfig { /** * 默认本地化解析器 */ @Bean public LocaleResolver localeResolver() { SessionLocaleResolver localeResolver = new SessionLocaleResolver(); //指定默认语言 localeResolver.setDefaultLocale(Locale.ENGLISH); return localeResolver; } /** * 默认本地化拦截器 */ @Bean("customizeLocaleChangeInterceptor") public LocaleChangeInterceptor localeChangeInterceptor() { CustomizeLocaleChangeInterceptor localeChangeInterceptor = new CustomizeLocaleChangeInterceptor(); //自定义国际化参数 localeChangeInterceptor.setParamName("language"); return localeChangeInterceptor; } // result code : 响应码信息国际化配置 @Bean("resultCodeMessageResource") public MessageSource resultCodeMessageResource() { ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); //指定国际化的Resource Bundle地址 resourceBundleMessageSource.setBasename("i18n/ResultCodeMessages"); //指定国际化的默认编码 resourceBundleMessageSource.setDefaultEncoding("UTF-8"); return resourceBundleMessageSource; } @Bean("resultCodeLocaleMessage") public LocaleMessage resultCodeLocaleMessage() { return new LocaleMessage(resultCodeMessageResource()); } }

国际化资源工具类

public class LocaleMessage { private final MessageSource messageSource; public LocaleMessage(MessageSource messageSource) { this.messageSource = messageSource; } public String getMessage(String code) { return getMessage(code, new Object[]{}); } public String getMessage(String code, String defaultMessage) { return getMessage(code, new Object[]{}, defaultMessage); } public String getMessage(String code, String defaultMessage, Locale locale) { return getMessage(code, new Object[]{}, defaultMessage, locale); } public String getMessage(String code, Locale locale) { return getMessage(code, new Object[]{}, "", locale); } public String getMessage(String code, Object[] args) { return getMessage(code, args, ""); } public String getMessage(String code, Object[] args, Locale locale) { return getMessage(code, args, "", locale); } public String getMessage(String code, Object[] args, String defaultMessage) { //根据应用部署的服务器系统来决定国际化 return getMessage(code, args, defaultMessage, LocaleContextHolder.getLocale()); } public String getMessage(String code, Object[] args, String defaultMessage, Locale locale) { return messageSource.getMessage(code, args == null ? new Object[]{} : args, defaultMessage, locale); } }

全局统一异常处理

业务异常类

public class BusinessException extends RuntimeException { private final String resultCode; private final Object[] args; public String getResultCode() { return resultCode; } public Object[] getArgs() { return args; } public BusinessException(ResultCodeEnum resultCodeEnum, Object[] args) { this.resultCode = resultCodeEnum.getCode(); this.args = args; } public BusinessException(String code, Object[] args) { this.resultCode = code; this.args = args; } public static void throwMessage(String errorCode, Object[] args) { throw new BusinessException(errorCode, args); } public static void throwMessage(ResultCodeEnum resultCodeEnum, Object[] args) { throw new BusinessException(resultCodeEnum.getCode(), args); } }

统一异常处理

@Slf4j @RestControllerAdvice class GlobalExceptionHandler { @Resource(name = "resultCodeLocaleMessage") LocaleMessage resultCodeLocaleMessage; @ExceptionHandler(BindException.class) public ApiResult<Void> handlerBindExceptionHandler(HttpServletRequest req, final BindException e) { log.error(req.getServletPath() + " Bind Exception", e); String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";")); return ApiResult.fail(ResultCodeEnum.REQUEST_PARAM_ERROR.getCode(), message); } @ExceptionHandler(MethodArgumentNotValidException.class) public ApiResult<Void> handlerMethodArgumentNotValidException(HttpServletRequest req, final MethodArgumentNotValidException e) { log.error(req.getServletPath() + " MethodArgumentNotValid Exception", e); String message = e.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(";")); return ApiResult.fail(ResultCodeEnum.REQUEST_PARAM_ERROR.getCode(), message); } @ExceptionHandler(BusinessException.class) public ApiResult<Void> handlerBusinessException(HttpServletRequest req, final BusinessException e) { log.error(req.getServletPath() + " Business Exception", e); return ApiResult.fail(e.getResultCode(), resultCodeLocaleMessage.getMessage(e.getResultCode(), e.getArgs(), RequestContextUtils.getLocale(req))); } }

请求示例

@GetMapping("/success") public ApiResult<String> success() { return ApiResult.success("success"); } @GetMapping("/fail") public void fail(@RequestParam(value = "code", required = false, defaultValue = "99") String code, @RequestParam(value = "content", required = false, defaultValue = "错了吧,说点啥吧") List<String> contentList) { BusinessException.throwMessage(code, contentList.toArray()); }

http测试示例

### 测试成功响应 GET http://localhost:8080/success HTTP/1.1 ### 测试中文,响应码99 GET http://localhost:8080/fail?language=zh_CN&code=99&content=我没错&content=是系统的错 HTTP/1.1 ### 测试英文,响应码99 GET http://localhost:8080/fail?language=en&code=99&content=I'm not wrong&content=It's the system's fault HTTP/1.1

示例

项目Demo:internationalization

效果图:

参考

SpringBoot项目国际化
最新回复(0)