Spring Boot接口国际化异常信息方案

要实现Java后端接口根据请求头的语言字段返回对应语言的异常信息,核心思路是国际化配置 + 全局异常处理 + 请求头语言解析。以下是基于Spring Boot的完整实现方案:

一、整体方案设计

  1. 语言标识约定 :请求头中自定义lang字段(或复用Accept-Language),值如zh-CN(中文)、en-US(英文),默认值zh-CN
  2. 国际化资源文件:存放不同语言的错误信息模板。
  3. 自定义异常类:携带错误码和参数,便于匹配国际化信息。
  4. 语言解析工具 :从请求头提取语言标识,转换为Locale对象。
  5. 全局异常处理器:捕获异常后,根据语言解析结果加载对应语言的错误信息并返回。
  6. 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)

  • 请求:

    bash 复制代码
    GET http://localhost:8080/users/-1
    Header: lang=zh-CN
  • 响应:

    json 复制代码
    {
      "code": 400,
      "message": "用户不存在,用户ID:-1",
      "data": null
    }

2. 测试英文返回(lang=en-US)

  • 请求:

    bash 复制代码
    GET http://localhost:8080/users/-1
    Header: lang=en-US
  • 响应:

    json 复制代码
    {
      "code": 400,
      "message": "User not found, User ID: -1",
      "data": null
    }

3. 测试默认语言(不传递lang)

  • 请求:

    bash 复制代码
    GET http://localhost:8080/users/-1
  • 响应:

    json 复制代码
    {
      "code": 400,
      "message": "用户不存在,用户ID:-1",
      "data": null
    }

四、扩展说明

  1. 复用Accept-Language :若想复用HTTP标准头Accept-Language,只需修改LocaleUtils中的LANG_HEADERAccept-Language,并适配解析逻辑(Accept-Language格式如zh-CN,zh;q=0.9,en;q=0.8)。

  2. 更多语言支持 :新增messages_ja_JP.properties(日语)等配置文件,即可支持更多语言,无需修改代码。

  3. 错误码规范 :建议将错误码枚举化(如ErrorCode.USER_NOT_FOUND),避免硬编码。

  4. 生产环境优化

    1. 异常栈信息不要返回给前端,仅打印到日志;
    2. MessageSourcecacheSeconds设为3600,提升性能;
    3. 接入日志框架(如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接口设计规范,且易于扩展和维护。

相关推荐
qq_162987692 小时前
SpringBoot框架选型
java·spring boot·后端
爱学习的小可爱卢2 小时前
JavaEE进阶-SpringBoot三层架构:餐厅模式解析
java·java-ee
武藤一雄2 小时前
C# 万字拆解线程间通讯?
后端·微软·c#·.net·.netcore·多线程
掉鱼的猫2 小时前
Java 低代码平台的“动态引擎”:Liquor
java·低代码·groovy
TT哇3 小时前
【Database Navigator 插件】idea 社区版连接 mysql 数据库
java·数据库·mysql·intellij-idea·database
Tony__Ferguson3 小时前
抽奖系统测试报告
java·功能测试·模块测试
做人不要太理性3 小时前
【Linux系统】ELF 文件格式的硬核揭秘
java·linux·服务器
zhglhy3 小时前
Jaccard相似度算法原理及Java实现
java·开发语言·算法
啥都不懂的小小白3 小时前
Java日志篇3:Logback 配置全解析与生产环境最佳实践
java·开发语言·logback