Java Boot异常处理案例如何封装

wen java案例 30

本文目录导读:

Java Boot异常处理案例如何封装

  1. 统一响应结果封装
  2. 自定义异常类
  3. 全局异常处理器
  4. 使用示例
  5. DTO验证示例
  6. 配置全局异常处理
  7. 日志记录增强
  8. 配置文件

我来介绍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实现参数校验
  • 错误码管理:统一的错误码体系

这样的设计可以让后端错误处理更加规范和易于维护。

抱歉,评论功能暂时关闭!