全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-18 6 分钟 ✍️ juanwangdev

Spring MVC 异常处理与国际化

国际化异常消息能够为不同语言用户返回友好的错误提示,提升用户体验。

错误码枚举设计

Java
public enum ErrorCode {
    // 系统错误 1xxx
    SYSTEM_ERROR(1000, "system.error"),
    PARAM_INVALID(1001, "param.invalid"),
    PARAM_MISSING(1002, "param.missing"),

    // 用户错误 2xxx
    USER_NOT_FOUND(2001, "user.not.found"),
    USER_EXISTS(2002, "user.exists"),
    LOGIN_FAILED(2003, "login.failed"),
    PASSWORD_ERROR(2004, "password.error"),

    // 文件错误 3xxx
    FILE_TOO_LARGE(3001, "file.too.large"),
    FILE_TYPE_INVALID(3002, "file.type.invalid"),
    FILE_UPLOAD_FAILED(3003, "file.upload.failed");

    private final int code;
    private final String messageKey;

    ErrorCode(int code, String messageKey) {
        this.code = code;
        this.messageKey = messageKey;
    }

    public int getCode() { return code; }
    public String getMessageKey() { return messageKey; }
}

国际化消息资源文件

properties
# messages.properties(默认)
system.error=系统错误
param.invalid=参数无效
param.missing=参数缺失
user.not.found=用户不存在
user.exists=用户已存在
login.failed=登录失败
password.error=密码错误
file.too.large=文件大小超过限制,最大{0}MB
file.type.invalid=不允许的文件类型,允许的类型:{0}
file.upload.failed=文件上传失败

# messages_en.properties(英文)
system.error=System error
param.invalid=Invalid parameter
param.missing=Missing parameter
user.not.found=User not found
user.exists=User already exists
login.failed=Login failed
password.error=Password error
file.too.large=File size exceeds limit, max {0}MB
file.type.invalid=Invalid file type, allowed types: {0}
file.upload.failed=File upload failed

# messages_zh_CN.properties(简体中文)
system.error=系统错误,请稍后重试
param.invalid=参数格式不正确
param.missing=缺少必填参数
user.not.found=用户不存在,请检查用户ID
user.exists=用户已存在,请使用其他用户名
login.failed=登录失败,请检查账号密码
password.error=密码错误
file.too.large=文件大小超过限制,最大允许{0}MB
file.type.invalid=不允许的文件类型,允许的文件类型:{0}
file.upload.failed=文件上传失败,请重试

业务异常类

Java
public class BusinessException extends RuntimeException {

    private final ErrorCode errorCode;
    private final Object[] args;

    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessageKey());
        this.errorCode = errorCode;
        this.args = null;
    }

    public BusinessException(ErrorCode errorCode, Object... args) {
        super(errorCode.getMessageKey());
        this.errorCode = errorCode;
        this.args = args;
    }

    public ErrorCode getErrorCode() { return errorCode; }
    public Object[] getArgs() { return args; }
}

国际化异常处理器

Java
@RestControllerAdvice
public class GlobalExceptionHandler {

    @Autowired
    private MessageSource messageSource;

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ApiResult<Void>> handleBusinessException(
            BusinessException e,
            HttpServletRequest request) {

        Locale locale = LocaleContextHolder.getLocale();

        // 获取国际化消息
        String message = getMessage(e.getErrorCode(), e.getArgs(), locale);

        ApiResult<Void> result = ApiResult.error(e.getErrorCode().getCode(), message);

        return ResponseEntity.status(e.getErrorCode().getCode() < 500 ? 400 : 500)
            .body(result);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResult<Void>> handleException(Exception e) {
        Locale locale = LocaleContextHolder.getLocale();

        String message = messageSource.getMessage(
            ErrorCode.SYSTEM_ERROR.getMessageKey(),
            null,
            locale
        );

        ApiResult<Void> result = ApiResult.error(
            ErrorCode.SYSTEM_ERROR.getCode(),
            message
        );

        return ResponseEntity.internalServerError().body(result);
    }

    private String getMessage(ErrorCode errorCode, Object[] args, Locale locale) {
        return messageSource.getMessage(
            errorCode.getMessageKey(),
            args,
            errorCode.getMessageKey(),
            locale
        );
    }
}

统一响应封装

Java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResult<T> {
    private int code;
    private String message;
    private T data;
    private Long timestamp;

    public static <T> ApiResult<T> success(T data) {
        return new ApiResult<>(200, "success", data, System.currentTimeMillis());
    }

    public static <T> ApiResult<T> success() {
        return success(null);
    }

    public static <T> ApiResult<T> error(int code, String message) {
        return new ApiResult<>(code, message, null, System.currentTimeMillis());
    }

    public static <T> ApiResult<T> error(ErrorCode errorCode, Locale locale, MessageSource messageSource) {
        String message = messageSource.getMessage(
            errorCode.getMessageKey(),
            null,
            locale
        );
        return error(errorCode.getCode(), message);
    }

    public static <T> ApiResult<T> error(ErrorCode errorCode, Object[] args, Locale locale, MessageSource messageSource) {
        String message = messageSource.getMessage(
            errorCode.getMessageKey(),
            args,
            locale
        );
        return error(errorCode.getCode(), message);
    }
}

业务中使用

Java
@Service
public class UserService {

    public User findById(Long id) {
        User user = userRepository.findById(id);
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        return user;
    }

    public void register(UserDTO dto) {
        if (userRepository.existsByUsername(dto.getUsername())) {
            throw new BusinessException(ErrorCode.USER_EXISTS);
        }
        // 创建用户...
    }

    public void login(String username, String password) {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new BusinessException(ErrorCode.LOGIN_FAILED);
        }
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new BusinessException(ErrorCode.PASSWORD_ERROR);
        }
    }
}

@RestController
@RequestMapping("/api/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public ApiResult<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        return ApiResult.success(user);
    }

    @PostMapping
    public ApiResult<User> register(@RequestBody UserDTO dto) {
        userService.register(dto);
        return ApiResult.success();
    }
}

文件上传国际化异常

Java
@RestControllerAdvice
public class FileUploadExceptionHandler {

    @Autowired
    private MessageSource messageSource;

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<ApiResult<Void>> handleMaxSizeException(
            MaxUploadSizeExceededException e) {

        Locale locale = LocaleContextHolder.getLocale();

        long maxSizeMB = e.getMaxFileSize() / 1024 / 1024;

        String message = messageSource.getMessage(
            ErrorCode.FILE_TOO_LARGE.getMessageKey(),
            new Object[]{maxSizeMB},
            locale
        );

        return ResponseEntity.badRequest()
            .body(ApiResult.error(ErrorCode.FILE_TOO_LARGE.getCode(), message));
    }
}

参数校验国际化

Java
@RestControllerAdvice
public class ValidationExceptionHandler {

    @Autowired
    private MessageSource messageSource;

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResult<List<FieldError>>> handleValidation(
            MethodArgumentNotValidException e) {

        Locale locale = LocaleContextHolder.getLocale();

        List<FieldError> errors = e.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> new FieldError(
                error.getField(),
                messageSource.getMessage(error.getDefaultMessage(), null, locale)
            ))
            .collect(Collectors.toList());

        String message = messageSource.getMessage(
            ErrorCode.PARAM_INVALID.getMessageKey(),
            null,
            locale
        );

        return ResponseEntity.badRequest()
            .body(ApiResult.error(ErrorCode.PARAM_INVALID.getCode(), message, errors));
    }
}

消息服务封装

Java
@Service
public class MessageService {

    @Autowired
    private MessageSource messageSource;

    public String getMessage(String key) {
        return getMessage(key, null);
    }

    public String getMessage(String key, Object[] args) {
        Locale locale = LocaleContextHolder.getLocale();
        return messageSource.getMessage(key, args, locale);
    }

    public String getMessage(ErrorCode errorCode) {
        return getMessage(errorCode.getMessageKey());
    }

    public String getMessage(ErrorCode errorCode, Object[] args) {
        Locale locale = LocaleContextHolder.getLocale();
        return messageSource.getMessage(
            errorCode.getMessageKey(),
            args,
            locale
        );
    }
}

LocaleContextHolder获取Locale

Java
// Spring自动从请求头Accept-Language解析Locale
@GetMapping("/test")
public String testLocale() {
    Locale locale = LocaleContextHolder.getLocale();
    String language = locale.getLanguage();
    String country = locale.getCountry();
    return "Language: " + language + ", Country: " + country;
}

响应示例

中文用户响应:

JSON
{
    "code": 2001,
    "message": "用户不存在,请检查用户ID",
    "data": null,
    "timestamp": 1716000000000
}

英文用户响应:

JSON
{
    "code": 2001,
    "message": "User not found",
    "data": null,
    "timestamp": 1716000000000
}

要点总结

  • 错误码枚举包含code和messageKey
  • messageKey对应国际化资源文件中的key
  • BusinessException携带错误码和参数
  • GlobalExceptionHandler通过MessageSource获取国际化消息
  • LocaleContextHolder自动获取请求Locale
  • 参数校验消息同样支持国际化

📝 发现内容有误?点击此处直接编辑

← 上一篇 Spring MVC 多文件上传实现
下一篇 → Spring MVC 文件上传原理与配置
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库