【问题标题】:Error in Controller layer error handling in spring webfluxSpring webflux中Controller层错误处理错误
【发布时间】:2021-09-02 13:43:03
【问题描述】:

所以我想实现保存用户的功能 首先我检查用户是否存在 如果存在则抛出异常 否则保存用户 但是当我从服务层抛出异常时.flatMap(user -> Mono.error(new IllegalArgumentException("User Exists with email " + user.getEmail())))

@Service
@RequiredArgsConstructor
public class AppUserService {

    private final AppUserRepository appUserRepository;

    public Flux<AppUser> getAllUsers() {
        return appUserRepository.findAll();
    }

    public Mono<AppUser> saveUser(AppUser appUser) {
        return getUser(appUser.getEmail())
                .flatMap(user -> Mono.error(new IllegalArgumentException("User Exists with email " + user.getEmail())))
                .switchIfEmpty(Mono.defer(() -> appUserRepository.save(appUser))).cast(AppUser.class).log();
    }

    public Mono<AppUser> getUser(String email) {
        return appUserRepository.findFirstByEmail(email);
    }
}

如果我像 .onErrorResume(error -&gt; ServerResponse.badRequest().bodyValue(error)) 那样处理它,则在控制器层中

@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class AppUserController {

    private final AppUserService appUserService;
    private final PasswordEncoder encoder;

    @GetMapping
    public Flux<AppUser> getAllUsers(@RequestHeader("email") String email) {
        return appUserService.getAllUsers();
    }

    @PostMapping
    @CrossOrigin
    public Mono<ResponseEntity<Object>> saveUser(@RequestBody Mono<AppUser> appUserMono) {
        return appUserMono
        .doOnSuccess(appUser -> appUser.setPassword(encoder.encode(appUser.getPassword())))
        .subscribeOn(Schedulers.parallel())
        .flatMap(appUserService::saveUser)
        .flatMap(savedAppUser -> ResponseEntity.created(URI.create("/user/" + savedAppUser.getId())).build())
        .onErrorResume(error -> Response entity.badRequest().bodyValue(error))
        .log();
    }

}

它在控制台上抛出一个错误

Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.web.reactive.function.server.DefaultEntityResponseBuilder$DefaultEntityResponse and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

并向客户端返回 200

我做错了什么

阅读错误后,它似乎得到了一个空值 但是如果我在onErrorResume(error -&gt; ..) 处调试流程,则错误变量有错误 不明白为什么它仍然抛出杰克逊错误 是不是因为杰克逊无法订阅 ServerResponse 或类似的东西

【问题讨论】:

  • 你为什么打电话给cast?异常表示某些内容很可能是空的。你在某处返回空的东西。由于您还没有发布完整的代码,我们不可能知道哪个函数返回空值。你确定数据库调用不返回空吗?还是存档?
  • 发布了整个代码,Cast 在那里是因为函数返回 Mono 并且 switchIfEmpty() 在这种情况下返回 Mono&lt;Object&gt;,因为在它之前调用了 flatMap()。我检查了数据库它没有返回空它返回用户因此流进入服务层方法中的flatMap()并将Error返回给控制器,然后流进入方法onErrorResume()
  • 当某事返回 Mono&lt;Object&gt; 时,这是一个确定的信号,表明链中较早的部分正在做一些错误的事情。您应该始终避免返回 object,因为它是代码异味,类型系统可以帮助您,并且您会丢失类型信息。演员,直接告诉我你做的事情很糟糕。
  • 是的,我同意,即使我也有一种感觉,这一定不是正确的方法。如果我想实现以下目标,你认为管道应该如何设计:当saveUser()被调用时:getUser()将返回Mono&lt;AppUser&gt;所以我需要检查用户是否存在如果存在抛出异常如果它不返回任何数据或空保存用户?
  • 我做了一些实验,我认为您需要使用switchIfEmptyflatMap 切换位置,以便它首先对空白部分做出反应,然后您是否运行 flatMap。

标签: java spring-webflux reactor webflux


【解决方案1】:

您的代码中存在一些问题。

以下是让它运行的方法(没有存储库):

package test;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class AppUser {
  private String id;

  private String username;

  private String firstName;

  private String password;

  private String email;
}

@Service
@RequiredArgsConstructor
public class AppUserService {

  public Mono<AppUser> saveUser(AppUser appUser) {
    return getUser(appUser.getEmail())
        .flatMap(user -> Mono.<AppUser>error(
            new IllegalArgumentException("User Exists with email " + user.getEmail())))
        .switchIfEmpty(Mono.defer(() -> Mono.just(AppUser.builder().username("mustafa").build())));
  }

  public Mono<AppUser> getUser(String email) {
//    return Mono.defer(() -> Mono.just(AppUser.builder().email(email).build()));
    return Mono.defer(Mono::empty);
  }
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class AppUserController {

  private final AppUserService appUserService;

  @PostMapping
  @CrossOrigin
  public Mono<ResponseEntity<AppUser>> saveUser(@RequestBody Mono<AppUser> appUserMono,
                                                @RequestHeader("email") String email) {
    return appUserMono
        .subscribeOn(Schedulers.parallel())
        .flatMap(appUserService::saveUser)
        .flatMap(savedAppUser -> Mono.just(
            ResponseEntity.created(URI.create("/user/" + savedAppUser.getId())).body(savedAppUser)))
        .onErrorResume(error -> Mono.just(ResponseEntity.badRequest().build()))
        .log();
  }
}

您的控制器的返回类型是Mono&lt;ResponseEntity&lt;AppUser&gt;&gt;,但您返回的是ServerResponse。我修复了这个问题并从您的服务代码中删除了演员表。

您可以通过注释/取消注释 getUser 正文来尝试生成 400 或有效结果。

也许更清晰的方式来编写电子邮件检查逻辑是使用旧的 if/else 语句:

  public Mono<AppUser> saveUser(AppUser appUser) {
    if (emailExists(appUser.getEmail())) {
      return Mono.error(new IllegalArgumentException("User Exists with email "
          + appUser.getEmail()));
    } else {
      return Mono.just(AppUser.builder().username("mustafa").build());
    }
  }

【讨论】:

  • 这是工作,但我没有得到部分 Mono.error(new IllegalArgumentException("User Exists with email" + user.getEmail()))),它会创建包含 IllegalArgumentException 的包装器或其他东西?
  • 这里的类型参数只是意味着这个Mono的订阅者需要一个AppUser类型的对象。 Mono#error 返回一个包含错误的 Mono
猜你喜欢
  • 2021-08-06
  • 2021-12-29
  • 2018-07-05
  • 2019-12-04
  • 2021-08-04
  • 2021-07-16
  • 2019-11-08
  • 1970-01-01
  • 2017-09-20
相关资源
最近更新 更多