Spring Boot 中的 Result 模式:优雅的错误处理
Result 模式是 Spring Boot 中传统异常处理的优雅替代方案。受 Rust 等函数式语言启发,它允许显式表示可能失败的操作,提高代码的可读性和健壮性。
为什么使用 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;
}问题:
- 受检异常污染方法签名
- 错误流不明确
- 难以组合操作
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 接口
@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;
});
}
}使用 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);
}
}支持类
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 对象是不可变的
- 函数式:与函数式编程良好集成
- 可测试性:使单元测试更容易
结论
Result 模式为 Spring Boot 应用中的错误处理提供了更优雅和健壮的方式,消除了对受检异常的需求,使代码更易读和维护。