本文目录导读:

我来介绍Java Boot中异常处理的封装最佳实践,包含完整的案例实现。
统一响应结果封装
全局响应对象
// 统一响应结果类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResult<T> {
private Integer code;
private String message;
private T data;
private Long timestamp;
// 成功响应
public static <T> ApiResult<T> success() {
return ApiResult.<T>builder()
.code(200)
.message("操作成功")
.timestamp(System.currentTimeMillis())
.build();
}
public static <T> ApiResult<T> success(T data) {
return ApiResult.<T>builder()
.code(200)
.message("操作成功")
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}
public static <T> ApiResult<T> success(String message, T data) {
return ApiResult.<T>builder()
.code(200)
.message(message)
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}
// 失败响应
public static <T> ApiResult<T> error(Integer code, String message) {
return ApiResult.<T>builder()
.code(code)
.message(message)
.timestamp(System.currentTimeMillis())
.build();
}
public static <T> ApiResult<T> error(ErrorCode errorCode) {
return ApiResult.<T>builder()
.code(errorCode.getCode())
.message(errorCode.getMessage())
.timestamp(System.currentTimeMillis())
.build();
}
public static <T> ApiResult<T> error(ErrorCode errorCode, Object... args) {
return ApiResult.<T>builder()
.code(errorCode.getCode())
.message(String.format(errorCode.getMessage(), args))
.timestamp(System.currentTimeMillis())
.build();
}
}
错误码枚举
// 错误码枚举
public enum ErrorCode {
// 通用错误码
SUCCESS(200, "操作成功"),
BAD_REQUEST(400, "请求参数错误"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "权限不足"),
NOT_FOUND(404, "资源不存在"),
METHOD_NOT_ALLOWED(405, "请求方法不支持"),
INTERNAL_ERROR(500, "服务器内部错误"),
SERVICE_UNAVAILABLE(503, "服务不可用"),
// 业务错误码
PARAM_VALIDATION_ERROR(1001, "参数校验失败"),
USER_NOT_EXIST(1002, "用户不存在"),
USER_ALREADY_EXIST(1003, "用户已存在"),
PASSWORD_ERROR(1004, "密码错误"),
ACCOUNT_LOCKED(1005, "账号已被锁定"),
TOKEN_EXPIRED(1006, "Token已过期"),
TOKEN_INVALID(1007, "Token无效"),
// 数据错误码
DATA_NOT_FOUND(2001, "数据不存在"),
DATA_ALREADY_EXIST(2002, "数据已存在"),
DATA_CREATE_FAILED(2003, "数据创建失败"),
DATA_UPDATE_FAILED(2004, "数据更新失败"),
DATA_DELETE_FAILED(2005, "数据删除失败"),
// 系统错误码
SYSTEM_ERROR(5000, "系统繁忙,请稍后重试"),
DATABASE_ERROR(5001, "数据库操作异常"),
CACHE_ERROR(5002, "缓存操作异常"),
NETWORK_ERROR(5003, "网络通信异常");
private final Integer code;
private final String message;
ErrorCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
自定义异常类
业务异常
// 业务异常类
@Data
@EqualsAndHashCode(callSuper = true)
public class BusinessException extends RuntimeException {
private Integer code;
private String message;
private Object[] args;
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
public BusinessException(ErrorCode errorCode, Object... args) {
super(String.format(errorCode.getMessage(), args));
this.code = errorCode.getCode();
this.message = String.format(errorCode.getMessage(), args);
this.args = args;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
}
参数校验异常
// 参数校验异常
@Data
@EqualsAndHashCode(callSuper = true)
public class ParamValidationException extends RuntimeException {
private final String field;
private final String message;
public ParamValidationException(String field, String message) {
super(message);
this.field = field;
this.message = message;
}
}
全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ApiResult<Void> handleBusinessException(BusinessException e) {
log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
return ApiResult.error(e.getCode(), e.getMessage());
}
// 处理参数校验异常
@ExceptionHandler(ParamValidationException.class)
public ApiResult<Void> handleParamValidationException(ParamValidationException e) {
log.warn("参数校验异常: field={}, message={}", e.getField(), e.getMessage());
return ApiResult.error(ErrorCode.PARAM_VALIDATION_ERROR.getCode(), e.getMessage());
}
// 处理方法参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResult<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
String message = fieldErrors.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
log.warn("参数校验失败: {}", message);
return ApiResult.error(ErrorCode.PARAM_VALIDATION_ERROR.getCode(), message);
}
// 处理约束违反异常
@ExceptionHandler(ConstraintViolationException.class)
public ApiResult<Void> handleConstraintViolationException(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream()
.map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
.collect(Collectors.joining(", "));
log.warn("约束违反: {}", message);
return ApiResult.error(ErrorCode.PARAM_VALIDATION_ERROR.getCode(), message);
}
// 处理HTTP消息不可读异常
@ExceptionHandler(HttpMessageNotReadableException.class)
public ApiResult<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.warn("请求体格式错误: {}", e.getMessage());
return ApiResult.error(ErrorCode.BAD_REQUEST.getCode(), "请求体格式错误");
}
// 处理类型不匹配异常
@ExceptionHandler(TypeMismatchException.class)
public ApiResult<Void> handleTypeMismatchException(TypeMismatchException e) {
log.warn("参数类型不匹配: {}", e.getMessage());
return ApiResult.error(ErrorCode.BAD_REQUEST.getCode(), "参数类型不匹配");
}
// 处理缺失请求参数异常
@ExceptionHandler(MissingServletRequestParameterException.class)
public ApiResult<Void> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
log.warn("缺少参数: {}", e.getParameterName());
return ApiResult.error(ErrorCode.BAD_REQUEST.getCode(), "缺少参数: " + e.getParameterName());
}
// 处理认证异常
@ExceptionHandler(AuthenticationException.class)
public ApiResult<Void> handleAuthenticationException(AuthenticationException e) {
log.warn("认证失败: {}", e.getMessage());
return ApiResult.error(ErrorCode.UNAUTHORIZED.getCode(), "认证失败: " + e.getMessage());
}
// 处理权限异常
@ExceptionHandler(AccessDeniedException.class)
public ApiResult<Void> handleAccessDeniedException(AccessDeniedException e) {
log.warn("权限不足: {}", e.getMessage());
return ApiResult.error(ErrorCode.FORBIDDEN.getCode(), "权限不足,无法访问");
}
// 处理HTTP请求方法不支持异常
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ApiResult<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.warn("请求方法不支持: {}", e.getMessage());
return ApiResult.error(ErrorCode.METHOD_NOT_ALLOWED.getCode(), "请求方法不支持");
}
// 处理空指针异常
@ExceptionHandler(NullPointerException.class)
public ApiResult<Void> handleNullPointerException(NullPointerException e) {
log.error("空指针异常", e);
return ApiResult.error(ErrorCode.INTERNAL_ERROR.getCode(), "系统异常,请联系管理员");
}
// 处理数据访问异常
@ExceptionHandler(DataAccessException.class)
public ApiResult<Void> handleDataAccessException(DataAccessException e) {
log.error("数据库访问异常", e);
return ApiResult.error(ErrorCode.DATABASE_ERROR.getCode(), "数据库操作异常");
}
// 处理通用异常
@ExceptionHandler(Exception.class)
public ApiResult<Void> handleException(Exception e) {
log.error("系统异常", e);
return ApiResult.error(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
}
}
使用示例
Service层使用
@Service
@Slf4j
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUserById(Long id) {
// 参数校验
if (id == null || id <= 0) {
throw new ParamValidationException("id", "用户ID不能为空且必须大于0");
}
// 业务逻辑
User user = userRepository.findById(id)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_EXIST));
// 检查用户状态
if (user.getStatus() == Status.LOCKED) {
throw new BusinessException(ErrorCode.ACCOUNT_LOCKED);
}
return user;
}
@Transactional
public User createUser(UserCreateDTO createDTO) {
// 检查用户是否已存在
if (userRepository.existsByUsername(createDTO.getUsername())) {
throw new BusinessException(ErrorCode.USER_ALREADY_EXIST);
}
try {
User user = new User();
BeanUtils.copyProperties(createDTO, user);
user.setStatus(Status.ACTIVE);
user.setCreateTime(LocalDateTime.now());
return userRepository.save(user);
} catch (DataIntegrityViolationException e) {
log.error("创建用户失败: {}", e.getMessage());
throw new BusinessException(ErrorCode.DATA_CREATE_FAILED);
}
}
public Page<User> listUsers(UserQueryDTO queryDTO) {
try {
Pageable pageable = PageRequest.of(
queryDTO.getPage(),
queryDTO.getSize(),
Sort.by(Sort.Direction.DESC, "createTime")
);
Specification<User> specification = Specification.where(null);
if (StringUtils.hasText(queryDTO.getUsername())) {
specification = specification.and(
(root, query, cb) -> cb.like(root.get("username"),
"%" + queryDTO.getUsername() + "%")
);
}
return userRepository.findAll(specification, pageable);
} catch (Exception e) {
log.error("查询用户列表失败", e);
throw new BusinessException(ErrorCode.DATA_NOT_FOUND);
}
}
}
Controller层使用
@RestController
@RequestMapping("/api/users")
@Validated
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ApiResult<User> getUser(@PathVariable Long id) {
return ApiResult.success(userService.getUserById(id));
}
@PostMapping
public ApiResult<User> createUser(@Valid @RequestBody UserCreateDTO createDTO) {
User user = userService.createUser(createDTO);
return ApiResult.success("用户创建成功", user);
}
@GetMapping
public ApiResult<Page<User>> listUsers(@Valid UserQueryDTO queryDTO) {
return ApiResult.success(userService.listUsers(queryDTO));
}
@PutMapping("/{id}")
public ApiResult<Void> updateUser(@PathVariable Long id,
@Valid @RequestBody UserUpdateDTO updateDTO) {
userService.updateUser(id, updateDTO);
return ApiResult.success("用户更新成功");
}
@DeleteMapping("/{id}")
public ApiResult<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ApiResult.success("用户删除成功");
}
}
DTO验证示例
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserCreateDTO {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度在3-50个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度在6-20个字符之间")
private String password;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@NotNull(message = "年龄不能为空")
@Min(value = 0, message = "年龄不能小于0")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
}
配置全局异常处理
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
// 配置404异常处理
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
return dispatcherServlet;
}
// 配置资源处理器
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
}
日志记录增强
@Aspect
@Component
@Slf4j
public class ExceptionLogAspect {
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex")
public void logException(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
Object[] args = joinPoint.getArgs();
// 记录详细异常信息
log.error("异常发生 - 类: {}, 方法: {}, 参数: {}",
className, methodName, args, ex);
// 发送告警通知(可选)
if (ex instanceof BusinessException) {
BusinessException be = (BusinessException) ex;
if (be.getCode() >= 5000) {
sendAlert(className, methodName, be);
}
}
}
private void sendAlert(String className, String methodName, BusinessException ex) {
// 实现告警通知逻辑,如邮件、短信、钉钉等
log.warn("系统告警: {} - {} - {}", className, methodName, ex.getMessage());
}
}
配置文件
# application.yml
server:
error:
include-stacktrace: never
include-binding-errors: always
include-message: always
include-exception: false
spring:
jackson:
default-property-inclusion: non_null
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
这个完整的异常处理封装方案提供了统一的异常处理机制,包括:
- 统一响应格式:标准的API响应结构
- 自定义异常:业务异常和参数校验异常
- 异常分类处理:针对不同类型的异常提供不同的处理逻辑
- 日志记录:完整的异常日志记录
- 参数校验:结合Spring Validation实现参数校验
- 错误码管理:统一的错误码体系
这样的设计可以让后端错误处理更加规范和易于维护。