要实现Java后端接口根据请求头的语言字段返回对应语言的异常信息,核心思路是国际化配置 + 全局异常处理 + 请求头语言解析。以下是基于Spring Boot的完整实现方案:
一、整体方案设计
- 语言标识约定 :请求头中自定义
lang字段(或复用Accept-Language),值如zh-CN(中文)、en-US(英文),默认值zh-CN。 - 国际化资源文件:存放不同语言的错误信息模板。
- 自定义异常类:携带错误码和参数,便于匹配国际化信息。
- 语言解析工具 :从请求头提取语言标识,转换为
Locale对象。 - 全局异常处理器:捕获异常后,根据语言解析结果加载对应语言的错误信息并返回。
- MessageSource配置:加载国际化资源文件,支持参数替换。
二、具体实现步骤
1. 配置国际化资源文件
在src/main/resources下创建i18n目录,存放多语言配置文件:
-
messages_zh_CN.properties(中文)
ini# 业务异常 error.user.not.found=用户不存在,用户ID:{0} error.param.invalid=参数无效,参数名:{0} # 系统异常 error.system.error=系统内部错误,请稍后重试 -
messages_en_US.properties(英文)
ini# 业务异常 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,用于抛出业务相关异常,携带错误码和参数:
scala
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:
java
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配置类中注册MessageSource Bean,加载国际化资源文件:
java
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,捕获异常并返回对应语言的错误信息:
java
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,模拟查询用户接口,不存在时抛业务异常:
kotlin
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)
-
请求:
bashGET http://localhost:8080/users/-1 Header: lang=zh-CN -
响应:
json{ "code": 400, "message": "用户不存在,用户ID:-1", "data": null }
2. 测试英文返回(lang=en-US)
-
请求:
bashGET http://localhost:8080/users/-1 Header: lang=en-US -
响应:
json{ "code": 400, "message": "User not found, User ID: -1", "data": null }
3. 测试默认语言(不传递lang)
-
请求:
bashGET http://localhost:8080/users/-1 -
响应:
json{ "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基础依赖已引入:
xml
<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接口设计规范,且易于扩展和维护。