Zademy

Spring Boot 中的 Result 模式:优雅的错误处理

spring-boot; result-pattern
words 字

我写了好几年 Java,异常处理一直让我不太舒服。受检异常污染方法签名,unchecked 异常又让你随时踩坑。后来接触了 Rust 的 Result 类型,觉得这个思路完全可以搬到 Java 里——把错误当成返回值的一部分,而不是副作用。

为什么使用 Result 模式?

传统异常的问题

// ❌ 传统异常处理
public User findUserById(Long id) throws UserNotFoundException {
    User user = userRepository.findById(id)
        .orElseThrow(() -> new UserNotFoundException("用户未找到: " + id));
    
    if (!user.isActive()) {
        throw new UserInactiveException("用户未激活: " + id);
    }
    return user;
}

这段代码的问题不少。throws UserNotFoundException 挂在签名上,调用方被迫 try-catch 或继续往上抛。错误处理逻辑跟正常业务逻辑搅在一起,读代码得到处找 catch 块。想把多个可能失败的操作串起来组合?很费劲。

Result 模式的优势

// ✅ 使用 Result 模式
public Result<User> findUserById(Long id) {
    return userRepository.findById(id)
        .map(Result::success)
        .orElse(Result.failure(new UserNotFoundError("用户未找到: " + id)))
        .flatMap(user -> user.isActive()
            ? Result.success(user)
            : Result.failure(new UserInactiveError("用户未激活: " + id)));
}

同样的逻辑,但失败的可能性编码进了返回类型。调用方一看 Result<User> 就知道这操作可能出问题,必须处理。map 和 flatMap 让你像拼管道一样组合多个操作,任何一步失败都会短路传播下去。

基础实现

主 Result 接口

@FunctionalInterface
public interface Result<T> {
    boolean isSuccess();
    boolean isFailure();
    
    T get() throws NoSuchElementException;
    Error getError() throws NoSuchElementException;
    
    // 函数式转换方法
    <U> Result<U> map(Function<T, U> mapper);
    <U> Result<U> flatMap(Function<T, Result<U>> mapper);
    
    // 错误处理
    Result<T> peek(Consumer<T> successConsumer);
    Result<T> peekError(Consumer<Error> errorConsumer);
    Result<T> recover(Function<Error, T> recoveryFunction);
    
    // 转换为 Java 类型
    Optional<T> toOptional();
    Stream<T> toStream();
    
    // 工厂方法
    static <T> Result<T> success(T value) {
        return new Success<>(value);
    }
    
    static <T> Result<T> failure(Error error) {
        return new Failure<>(error);
    }
    
    static <T> Result<T> ofCallable(Callable<T> callable) {
        try {
            return success(callable.call());
        } catch (Exception e) {
            return failure(new SystemError(e));
        }
    }
}

具体实现

public final class Success<T> implements Result<T> {
    private final T value;
    
    public Success(T value) {
        this.value = Objects.requireNonNull(value);
    }
    
    @Override
    public boolean isSuccess() { return true; }
    @Override
    public boolean isFailure() { return false; }
    @Override
    public T get() { return value; }
    @Override
    public Error getError() {
        throw new NoSuchElementException("Success 不包含错误");
    }
    
    @Override
    public <U> Result<U> map(Function<T, U> mapper) {
        try {
            return success(mapper.apply(value));
        } catch (Exception e) {
            return failure(new SystemError(e));
        }
    }
    
    @Override
    public <U> Result<U> flatMap(Function<T, Result<U>> mapper) {
        try {
            return mapper.apply(value);
        } catch (Exception e) {
            return failure(new SystemError(e));
        }
    }
    
    @Override
    public Result<T> peek(Consumer<T> successConsumer) {
        successConsumer.accept(value);
        return this;
    }
    
    @Override
    public Result<T> peekError(Consumer<Error> errorConsumer) {
        return this; // Success 中不执行任何操作
    }
    
    @Override
    public Optional<T> toOptional() {
        return Optional.of(value);
    }
    
    @Override
    public Stream<T> toStream() {
        return Stream.of(value);
    }
}

public final class Failure<T> implements Result<T> {
    private final Error error;
    
    public Failure(Error error) {
        this.error = Objects.requireNonNull(error);
    }
    
    @Override
    public boolean isSuccess() { return false; }
    @Override
    public boolean isFailure() { return true; }
    @Override
    public T get() {
        throw new NoSuchElementException("Failure 不包含值");
    }
    @Override
    public Error getError() { return error; }
    
    @Override
    public <U> Result<U> map(Function<T, U> mapper) {
        return failure(error); // 传播错误
    }
    
    @Override
    public <U> Result<U> flatMap(Function<T, Result<U>> mapper) {
        return failure(error); // 传播错误
    }
    
    @Override
    public Result<T> peek(Consumer<T> successConsumer) {
        return this; // Failure 中不执行任何操作
    }
    
    @Override
    public Result<T> peekError(Consumer<Error> errorConsumer) {
        errorConsumer.accept(error);
        return this;
    }
    
    @Override
    public Optional<T> toOptional() {
        return Optional.empty();
    }
    
    @Override
    public Stream<T> toStream() {
        return Stream.empty();
    }
}

错误系统

基础错误类

public abstract class Error {
    private final String code;
    private final String message;
    private final Throwable cause;
    private final LocalDateTime timestamp;
    
    protected Error(String code, String message, Throwable cause) {
        this.code = code;
        this.message = message;
        this.cause = cause;
        this.timestamp = LocalDateTime.now();
    }
    
    public String getCode() { return code; }
    public String getMessage() { return message; }
    public Throwable getCause() { return cause; }
    public LocalDateTime getTimestamp() { return timestamp; }
    
    @Override
    public String toString() {
        return String.format("[%s] %s", code, message);
    }
}

域错误

public class ValidationError extends Error {
    private final String field;
    
    public ValidationError(String field, String message) {
        super("VALIDATION_ERROR", message, null);
        this.field = field;
    }
    
    public String getField() { return field; }
}

public class BusinessRuleError extends Error {
    public BusinessRuleError(String rule, String message) {
        super("BUSINESS_RULE_VIOLATION",
              String.format("规则 '%s': %s", rule, message), null);
    }
}

public class SystemError extends Error {
    public SystemError(Throwable cause) {
        super("SYSTEM_ERROR", "内部系统错误", cause);
    }
}

public class UserNotFoundError extends Error {
    public UserNotFoundError(String message) {
        super("USER_NOT_FOUND", message, null);
    }
}

public class UserInactiveError extends Error {
    public UserInactiveError(String message) {
        super("USER_INACTIVE", message, null);
    }
}

Spring Boot 集成

使用 Result 的服务层

@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;
    private final EmailValidator emailValidator;
    private final PasswordEncoder passwordEncoder;
    
    public Result<User> createUser(CreateUserRequest request) {
        return validateCreateRequest(request)
            .flatMap(this::checkEmailExists)
            .flatMap(this::encodePassword)
            .flatMap(this::saveUser);
    }
    
    private Result<CreateUserRequest> validateCreateRequest(CreateUserRequest request) {
        List<ValidationError> errors = new ArrayList<>();
        
        if (StringUtils.isBlank(request.getEmail())) {
            errors.add(new ValidationError("email", "邮箱是必需的"));
        } else if (!emailValidator.isValid(request.getEmail())) {
            errors.add(new ValidationError("email", "邮箱无效"));
        }
        
        if (StringUtils.isBlank(request.getPassword())) {
            errors.add(new ValidationError("password", "密码是必需的"));
        } else if (request.getPassword().length() < 8) {
            errors.add(new ValidationError("password", "密码必须至少8个字符"));
        }
        
        return errors.isEmpty()
            ? Result.success(request)
            : Result.failure(new ValidationError(errors.get(0).getField(),
                errors.stream().map(Error::getMessage).collect(Collectors.joining(", "))));
    }
    
    private Result<CreateUserRequest> checkEmailExists(CreateUserRequest request) {
        return userRepository.existsByEmail(request.getEmail())
            ? Result.failure(new BusinessRuleError("EMAIL_UNIQUE",
                "邮箱已被注册"))
            : Result.success(request);
    }
    
    private Result<UserData> encodePassword(CreateUserRequest request) {
        return Result.ofCallable(() -> {
            String encodedPassword = passwordEncoder.encode(request.getPassword());
            return UserData.builder()
                .email(request.getEmail())
                .password(encodedPassword)
                .name(request.getName())
                .active(true)
                .build();
        });
    }
    
    private Result<User> saveUser(UserData userData) {
        return Result.ofCallable(() -> {
            User saved = userRepository.save(userData);
            return saved;
        });
    }
}

看 createUser 方法。验证邮箱、检查邮箱是否已存在、加密密码、保存用户,四个步骤全用 flatMap 串起来。任何一步返回 Failure,后续步骤自动跳过。不用一层层嵌套 if-else,也不用 try-catch。

使用 Result 的控制器

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;
    
    @PostMapping
    public ResponseEntity<?> createUser(@RequestBody @Valid CreateUserRequest request) {
        return userService.createUser(request)
            .map(user -> ResponseEntity.status(HttpStatus.CREATED)
                .body(UserResponse.from(user)))
            .recover(this::handleBusinessError)
            .recover(this::handleValidationError)
            .recover(this::handleSystemError)
            .get();
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<?> getUser(@PathVariable Long id) {
        return userService.findUserById(id)
            .map(user -> ResponseEntity.ok(UserResponse.from(user)))
            .recover(this::handleNotFoundError)
            .recover(this::handleSystemError)
            .get();
    }
    
    private ResponseEntity<ErrorResponse> handleBusinessError(Error error) {
        if (error instanceof BusinessRuleError) {
            return ResponseEntity.badRequest()
                .body(ErrorResponse.from(error));
        }
        throw new IllegalStateException("未处理的错误: " + error);
    }
    
    private ResponseEntity<ErrorResponse> handleValidationError(Error error) {
        if (error instanceof ValidationError) {
            return ResponseEntity.badRequest()
                .body(ErrorResponse.from(error));
        }
        throw new IllegalStateException("未处理的错误: " + error);
    }
    
    private ResponseEntity<ErrorResponse> handleNotFoundError(Error error) {
        if (error instanceof UserNotFoundError) {
            return ResponseEntity.notFound().build();
        }
        throw new IllegalStateException("未处理的错误: " + error);
    }
    
    private ResponseEntity<ErrorResponse> handleSystemError(Error error) {
        if (error instanceof SystemError) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ErrorResponse.from(error));
        }
        throw new IllegalStateException("未处理的错误: " + error);
    }
}

控制器层用 recover 做错误分发。每种错误类型对应一个 HTTP 状态码,逻辑一目了然。

支持类

API 错误响应

@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ErrorResponse {
    private String code;
    private String message;
    private LocalDateTime timestamp;
    private String path;
    
    public static ErrorResponse from(Error error) {
        return ErrorResponse.builder()
            .code(error.getCode())
            .message(error.getMessage())
            .timestamp(error.getTimestamp())
            .build();
    }
}

域类

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserRequest {
    private String email;
    private String password;
    private String name;
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserResponse {
    private Long id;
    private String email;
    private String name;
    private boolean active;
    
    public static UserResponse from(User user) {
        return UserResponse.builder()
            .id(user.getId())
            .email(user.getEmail())
            .name(user.getName())
            .active(user.isActive())
            .build();
    }
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserData {
    private String email;
    private String password;
    private String name;
    private boolean active;
}

模式优势

我用下来最直接的感受:错误流成了类型系统的一部分,编译器帮你盯着。Result 对象不可变,map 和 flatMap 让操作组合变得自然。写单元测试也舒服多了,不用 @Test(expected = ...) 去捕获异常,直接断言返回值是 Success 还是 Failure。

结论

不是所有场景都适合 Result 模式。简单的 CRUD,Spring 的 @ControllerAdvice 加全局异常处理器已经够用。但一旦业务逻辑开始复杂,需要组合多个可能失败的操作,Result 模式能让代码结构清晰很多。我从 Rust 那边学来这个思路,在 Java 里用着确实顺手。