SpringBoot接口國(guó)際化異常信息的完整實(shí)現(xiàn)方案
一、整體方案設(shè)計(jì)
- 語(yǔ)言標(biāo)識(shí)約定:請(qǐng)求頭中自定義
lang字段(或復(fù)用Accept-Language),值如zh-CN(中文)、en-US(英文),默認(rèn)值zh-CN。 - 國(guó)際化資源文件:存放不同語(yǔ)言的錯(cuò)誤信息模板。
- 自定義異常類:攜帶錯(cuò)誤碼和參數(shù),便于匹配國(guó)際化信息。
- 語(yǔ)言解析工具:從請(qǐng)求頭提取語(yǔ)言標(biāo)識(shí),轉(zhuǎn)換為
Locale對(duì)象。 - 全局異常處理器:捕獲異常后,根據(jù)語(yǔ)言解析結(jié)果加載對(duì)應(yīng)語(yǔ)言的錯(cuò)誤信息并返回。
- MessageSource配置:加載國(guó)際化資源文件,支持參數(shù)替換。
二、具體實(shí)現(xiàn)步驟
1. 配置國(guó)際化資源文件
在src/main/resources下創(chuàng)建i18n目錄,存放多語(yǔ)言配置文件:
messages_zh_CN.properties(中文)
# 業(yè)務(wù)異常
error.user.not.found=用戶不存在,用戶ID:{0}
error.param.invalid=參數(shù)無(wú)效,參數(shù)名:{0}
# 系統(tǒng)異常
error.system.error=系統(tǒng)內(nèi)部錯(cuò)誤,請(qǐng)稍后重試
messages_en_US.properties(英文)
# 業(yè)務(wù)異常
error.user.not.found=User not found, User ID: {0}
error.param.invalid=Invalid parameter, Parameter name: {0}
# 系統(tǒng)異常
error.system.error=System internal error, please try again later
2. 自定義業(yè)務(wù)異常類
創(chuàng)建BusinessException,用于拋出業(yè)務(wù)相關(guān)異常,攜帶錯(cuò)誤碼和參數(shù):
package com.example.demo.exception;
import lombok.Getter;
/**
* 自定義業(yè)務(wù)異常
*/
@Getter
public class BusinessException extends RuntimeException {
// 錯(cuò)誤碼(對(duì)應(yīng)國(guó)際化配置文件的key)
private final String errorCode;
// 錯(cuò)誤信息參數(shù)(用于替換國(guó)際化模板中的占位符)
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. 語(yǔ)言解析工具類
創(chuàng)建LocaleUtils,從Http請(qǐng)求頭解析語(yǔ)言標(biāo)識(shí),轉(zhuǎn)換為Locale:
package com.example.demo.utils;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Locale;
/**
* 語(yǔ)言解析工具類
*/
public class LocaleUtils {
// 請(qǐng)求頭中語(yǔ)言字段名(自定義,也可復(fù)用Accept-Language)
private static final String LANG_HEADER = "lang";
// 默認(rèn)語(yǔ)言
private static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
/**
* 從請(qǐng)求頭解析Locale
*/
public static Locale getLocaleFromRequest(HttpServletRequest request) {
if (request == null) {
return DEFAULT_LOCALE;
}
// 獲取請(qǐng)求頭中的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(加載國(guó)際化資源)
在Spring Boot配置類中注冊(cè)MessageSource Bean,加載國(guó)際化資源文件:
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;
/**
* 國(guó)際化配置
*/
@Configuration
public class I18nConfig {
/**
* 配置MessageSource,加載國(guó)際化資源文件
*/
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
// 指定資源文件基礎(chǔ)名(i18n目錄下的messages)
messageSource.setBasename("i18n/messages");
// 設(shè)置編碼,避免中文亂碼
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
// 默認(rèn)語(yǔ)言
messageSource.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
// 緩存時(shí)間(秒),開發(fā)時(shí)設(shè)為0,生產(chǎn)可設(shè)為3600
messageSource.setCacheSeconds(0);
return messageSource;
}
/**
* 配置LocaleResolver(可選,復(fù)用Accept-Language時(shí)生效)
*/
@Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return resolver;
}
}
5. 全局異常處理器
創(chuàng)建GlobalExceptionHandler,捕獲異常并返回對(duì)應(yīng)語(yǔ)言的錯(cuò)誤信息:
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 {
// 注入國(guó)際化消息源
private final MessageSource messageSource;
/**
* 處理業(yè)務(wù)異常
*/
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<?> handleBusinessException(BusinessException e, HttpServletRequest request) {
// 解析請(qǐng)求頭的語(yǔ)言
Locale locale = LocaleUtils.getLocaleFromRequest(request);
// 從國(guó)際化配置中獲取對(duì)應(yīng)語(yǔ)言的錯(cuò)誤信息
String errorMessage = messageSource.getMessage(
e.getErrorCode(), // 錯(cuò)誤碼(對(duì)應(yīng)配置文件的key)
e.getArgs(), // 占位符參數(shù)
e.getErrorCode(), // 默認(rèn)值(配置文件無(wú)該key時(shí)使用)
locale // 語(yǔ)言
);
return Result.fail(HttpStatus.BAD_REQUEST.value(), errorMessage);
}
/**
* 處理系統(tǒng)異常
*/
@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
);
// 打印系統(tǒng)異常棧(生產(chǎn)環(huán)境可接入日志框架)
e.printStackTrace();
return Result.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), errorMessage);
}
/**
* 統(tǒng)一返回結(jié)果封裝
*/
@Data
@AllArgsConstructor
public static class Result<T> {
private int code; // 狀態(tài)碼
private String message; // 錯(cuò)誤信息
private T data; // 數(shù)據(jù)(異常時(shí)為null)
public static <T> Result<T> fail(int code, String message) {
return new Result<>(code, message, null);
}
}
}
6. 接口示例(測(cè)試異常返回)
創(chuàng)建UserController,模擬查詢用戶接口,不存在時(shí)拋業(yè)務(wù)異常:
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;
/**
* 測(cè)試接口
*/
@RestController
@RequestMapping("/users")
public class UserController {
/**
* 根據(jù)用戶ID查詢用戶
*/
@GetMapping("/{userId}")
public GlobalExceptionHandler.Result<?> getUser(@PathVariable Long userId) {
// 模擬用戶不存在的場(chǎng)景
if (userId <= 0) {
// 拋業(yè)務(wù)異常,攜帶錯(cuò)誤碼和參數(shù)(用戶ID)
throw new BusinessException("error.user.not.found", userId);
}
return new GlobalExceptionHandler.Result<>(200, "success", "用戶信息:" + userId);
}
}
三、測(cè)試驗(yàn)證
使用Postman/Curl調(diào)用接口,通過(guò)請(qǐng)求頭lang指定語(yǔ)言:
1. 測(cè)試中文返回(lang=zh-CN)
請(qǐng)求:
GET http://localhost:8080/users/-1 Header: lang=zh-CN
響應(yīng):
{
"code": 400,
"message": "用戶不存在,用戶ID:-1",
"data": null
}
2. 測(cè)試英文返回(lang=en-US)
請(qǐng)求:
GET http://localhost:8080/users/-1 Header: lang=en-US
響應(yīng):
{
"code": 400,
"message": "User not found, User ID: -1",
"data": null
}
3. 測(cè)試默認(rèn)語(yǔ)言(不傳遞lang)
請(qǐng)求:
GET http://localhost:8080/users/-1
響應(yīng):
{
"code": 400,
"message": "用戶不存在,用戶ID:-1",
"data": null
}
四、擴(kuò)展說(shuō)明
復(fù)用Accept-Language:若想復(fù)用HTTP標(biāo)準(zhǔn)頭Accept-Language,只需修改LocaleUtils中的LANG_HEADER為Accept-Language,并適配解析邏輯(Accept-Language格式如zh-CN,zh;q=0.9,en;q=0.8)。
更多語(yǔ)言支持:新增messages_ja_JP.properties(日語(yǔ))等配置文件,即可支持更多語(yǔ)言,無(wú)需修改代碼。
錯(cuò)誤碼規(guī)范:建議將錯(cuò)誤碼枚舉化(如ErrorCode.USER_NOT_FOUND),避免硬編碼。
生產(chǎn)環(huán)境優(yōu)化:
- 異常棧信息不要返回給前端,僅打印到日志;
MessageSource的cacheSeconds設(shè)為3600,提升性能;- 接入日志框架(如Logback/Log4j2)記錄異常詳情。
五、核心依賴(pom.xml)
確保Spring Boot基礎(chǔ)依賴已引入:
<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>
該方案實(shí)現(xiàn)了異常信息的國(guó)際化,符合RESTful接口設(shè)計(jì)規(guī)范,且易于擴(kuò)展和維護(hù)。
以上就是SpringBoot接口國(guó)際化異常信息的完整實(shí)現(xiàn)方案的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot接口國(guó)際化異常信息的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
純Java實(shí)現(xiàn)高效MP3音頻合并的詳細(xì)方案
在 Java 音頻處理中,MP3 格式的合并一直是一項(xiàng)技術(shù)難點(diǎn),大多數(shù)開發(fā)者默認(rèn)使用 FFmpeg 命令行來(lái)完成任務(wù),但會(huì)帶來(lái)許多問(wèn)題,所以本文將介紹一種 純 Java 實(shí)現(xiàn)的 MP3 合并方法,需要的朋友可以參考下2025-11-11
Java中為什么start方法不能重復(fù)調(diào)用而run方法可以?
這篇文章主要介紹了Java中為什么start方法不能重復(fù)調(diào)用而run方法可以?帶著疑問(wèn)一起學(xué)習(xí)下面文章的詳細(xì)內(nèi)容吧2022-05-05
MyBatis一對(duì)多關(guān)系使用@Many注解的實(shí)現(xiàn)
本文介紹了在MyBatis中實(shí)現(xiàn)一對(duì)多查詢的方法,包括數(shù)據(jù)表和數(shù)據(jù)類的設(shè)計(jì),以及使用@Many注解進(jìn)行查詢,具有一定的參考價(jià)值,感興趣的可以了解一下2025-11-11
使用IDEA如何打包發(fā)布SpringBoot并部署到云服務(wù)器
這篇文章主要介紹了使用IDEA如何打包發(fā)布SpringBoot并部署到云服務(wù)器問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
Java獲取當(dāng)前時(shí)間并轉(zhuǎn)化為yyyy-MM-dd?HH:mm:ss格式的多種方式
這篇文章主要介紹了Java獲取當(dāng)前時(shí)間并轉(zhuǎn)化為yyyy-MM-dd?HH:mm:ss格式的多種方式,每種方式結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-03-03
Java中類與對(duì)象全面解析(附實(shí)例代碼)
這篇文章主要介紹了Java中類與對(duì)象的相關(guān)資料,重點(diǎn)講解了封裝的概念,通過(guò)private關(guān)鍵字和getter/setter方法來(lái)保護(hù)和操作對(duì)象的成員變量,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-05-05

