要实现Java后端接口根据请求头的语言字段返回对应语言的异常信息,核心思路是国际化配置 + 全局异常处理 + 请求头语言解析。以下是基于Spring Boot的完整实现方案:
一、整体方案设计
- 语言标识约定:请求头中自定义
lang字段(或复用Accept-Language),值如zh-CN(中文)、en-US(英文),默认值zh-CN。 - 国际化资源文件:存放不同语言的错误信息模板。
- 自定义异常类:携带错误码和参数,便于匹配国际化信息。
- 语言解析工具:从请求头提取语言标识,转换为
Locale对象。 - 全局异常处理器:捕获异常后,根据语言解析结果加载对应语言的错误信息并返回。
- MessageSource配置:加载国际化资源文件,支持参数替换。
二、具体实现步骤
1. 配置国际化资源文件
在src/main/resources下创建i18n目录,存放多语言配置文件:
messages_zh_CN.properties(中文)
# 业务异常 error.user.not.found=用户不存在,用户ID:{0} error.param.invalid=参数无效,参数名:{0} # 系统异常 error.system.error=系统内部错误,请稍后重试
messages_en_US.properties(英文)
# 业务异常 error.user.not.found=User not found, User ID: {0} error.param.invalid=Invalid parameter, Parameter name: {0} # 系统异常 error.system.error=System internal error, please try again later
2. 自定义业务异常类
创建BusinessException,用于抛出业务相关异常,携带错误码和参数:
package com.example.demo.exception; import lombok.Getter; /** * 自定义业务异常 */ @Getter public class BusinessException extends RuntimeException { // 错误码(对应国际化配置文件的key) private final String errorCode; // 错误信息参数(用于替换国际化模板中的占位符) private final Object[] args; public BusinessException(String errorCode) { this(errorCode, null); } public BusinessException(String errorCode, Object... args) { super(errorCode); this.errorCode = errorCode; this.args = args; } }
3. 语言解析工具类
创建LocaleUtils,从Http请求头解析语言标识,转换为Locale:
package com.example.demo.utils; import jakarta.servlet.http.HttpServletRequest; import java.util.Locale; /** * 语言解析工具类 */ public class LocaleUtils { // 请求头中语言字段名(自定义,也可复用Accept-Language) private static final String LANG_HEADER = "lang"; // 默认语言 private static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; /** * 从请求头解析Locale */ public static Locale getLocaleFromRequest(HttpServletRequest request) { if (request == null) { return DEFAULT_LOCALE; } // 获取请求头中的lang值 String lang = request.getHeader(LANG_HEADER); if (lang == null || lang.trim().isEmpty()) { return DEFAULT_LOCALE; } // 解析lang值(支持zh-CN、en-US、zh、en等格式) String[] langParts = lang.split("-"); return switch (langParts.length) { case 1 -> new Locale(langParts[0]); // 如zh -> Locale("zh") case 2 -> new Locale(langParts[0], langParts[1]); // 如zh-CN -> Locale("zh", "CN") default -> DEFAULT_LOCALE; }; } }
4. 配置MessageSource(加载国际化资源)
在Spring Boot配置类中注册MessageSourceBean,加载国际化资源文件:
package com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import java.nio.charset.StandardCharsets; import java.util.Locale; /** * 国际化配置 */ @Configuration public class I18nConfig { /** * 配置MessageSource,加载国际化资源文件 */ @Bean public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); // 指定资源文件基础名(i18n目录下的messages) messageSource.setBasename("i18n/messages"); // 设置编码,避免中文乱码 messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name()); // 默认语言 messageSource.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); // 缓存时间(秒),开发时设为0,生产可设为3600 messageSource.setCacheSeconds(0); return messageSource; } /** * 配置LocaleResolver(可选,复用Accept-Language时生效) */ @Bean public LocaleResolver localeResolver() { AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver(); resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); return resolver; } }
5. 全局异常处理器
创建GlobalExceptionHandler,捕获异常并返回对应语言的错误信息:
package com.example.demo.exception; import com.example.demo.utils.LocaleUtils; import jakarta.servlet.http.HttpServletRequest; import lombok.AllArgsConstructor; import lombok.Data; import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.Locale; /** * 全局异常处理器 */ @RestControllerAdvice @AllArgsConstructor public class GlobalExceptionHandler { // 注入国际化消息源 private final MessageSource messageSource; /** * 处理业务异常 */ @ExceptionHandler(BusinessException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Result<?> handleBusinessException(BusinessException e, HttpServletRequest request) { // 解析请求头的语言 Locale locale = LocaleUtils.getLocaleFromRequest(request); // 从国际化配置中获取对应语言的错误信息 String errorMessage = messageSource.getMessage( e.getErrorCode(), // 错误码(对应配置文件的key) e.getArgs(), // 占位符参数 e.getErrorCode(), // 默认值(配置文件无该key时使用) locale // 语言 ); return Result.fail(HttpStatus.BAD_REQUEST.value(), errorMessage); } /** * 处理系统异常 */ @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Result<?> handleSystemException(Exception e, HttpServletRequest request) { Locale locale = LocaleUtils.getLocaleFromRequest(request); String errorMessage = messageSource.getMessage( "error.system.error", null, "System internal error", locale ); // 打印系统异常栈(生产环境可接入日志框架) e.printStackTrace(); return Result.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), errorMessage); } /** * 统一返回结果封装 */ @Data @AllArgsConstructor public static class Result<T> { private int code; // 状态码 private String message; // 错误信息 private T data; // 数据(异常时为null) public static <T> Result<T> fail(int code, String message) { return new Result<>(code, message, null); } } }
6. 接口示例(测试异常返回)
创建UserController,模拟查询用户接口,不存在时抛业务异常:
package com.example.demo.controller; import com.example.demo.exception.BusinessException; import com.example.demo.exception.GlobalExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 测试接口 */ @RestController @RequestMapping("/users") public class UserController { /** * 根据用户ID查询用户 */ @GetMapping("/{userId}") public GlobalExceptionHandler.Result<?> getUser(@PathVariable Long userId) { // 模拟用户不存在的场景 if (userId <= 0) { // 抛业务异常,携带错误码和参数(用户ID) throw new BusinessException("error.user.not.found", userId); } return new GlobalExceptionHandler.Result<>(200, "success", "用户信息:" + userId); } }
三、测试验证
使用Postman/Curl调用接口,通过请求头lang指定语言:
1. 测试中文返回(lang=zh-CN)
请求:
GET http://localhost:8080/users/-1 Header: lang=zh-CN
响应:
{ "code": 400, "message": "用户不存在,用户ID:-1", "data": null }
2. 测试英文返回(lang=en-US)
请求:
GET http://localhost:8080/users/-1 Header: lang=en-US
响应:
{ "code": 400, "message": "User not found, User ID: -1", "data": null }
3. 测试默认语言(不传递lang)
请求:
GET http://localhost:8080/users/-1
响应:
{ "code": 400, "message": "用户不存在,用户ID:-1", "data": null }
四、扩展说明
复用Accept-Language:若想复用HTTP标准头
Accept-Language,只需修改LocaleUtils中的LANG_HEADER为Accept-Language,并适配解析逻辑(Accept-Language格式如zh-CN,zh;q=0.9,en;q=0.8)。更多语言支持:新增
messages_ja_JP.properties(日语)等配置文件,即可支持更多语言,无需修改代码。错误码规范:建议将错误码枚举化(如
ErrorCode.USER_NOT_FOUND),避免硬编码。生产环境优化:
- 异常栈信息不要返回给前端,仅打印到日志;
MessageSource的cacheSeconds设为3600,提升性能;- 接入日志框架(如Logback/Log4j2)记录异常详情。
五、核心依赖(pom.xml)
确保Spring Boot基础依赖已引入:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
该方案实现了异常信息的国际化,符合RESTful接口设计规范,且易于扩展和维护。