Spring 5 反应式异常处理

Posted

技术标签:

【中文标题】Spring 5 反应式异常处理【英文标题】:Spring 5 reactive exception handling 【发布时间】:2019-07-17 12:55:45 【问题描述】:

我正在编写一个复杂的应用程序作为 Spring 5 Webflux 的实验。我打算在这个应用程序中使用很多技术。我熟悉“旧式”@RestController,但现在,我正在编写函数式端点。例如。公司注册服务的后端。我在“旧世界”中追求类似@ControllerAdvice 的东西。但我真的找不到任何类似的反应式等价物。 (或者任何对我有用的东西。)

我有一个非常基本的设置。一个路由功能、一个反应式 Cassandra 存储库、一个处理程序和一个测试类。存储库操作可能会抛出 IllegalArgumentException,我想通过向客户端返回 HTTP 状态 BadRequest 来处理它。只是作为我有能力做到的一个例子。 :-) 异常由处理程序类处理。这是我的代码。

路由器配置

@Slf4j
@Configuration
@EnableWebFlux
public class RouterConfig 

@Bean
public RouterFunction<ServerResponse> route(@Autowired CompanyHandler handler) 
    return RouterFunctions.route()
            .nest(path("/company"), bc -> bc
                    .GET("/id", handler::handleGetCompanyDataRequest)                     
                    .before(request -> 
                        log.info("Request= has been received.", request.toString());
                        return request;
                    )
            .after((request, response) -> 
                log.info("Response= has been sent.", response.toString());
                return response;
            ))
            .build();       
    

CompanyHandler

@Slf4j
@Component
public class CompanyHandler 

    @Autowired
    private ReactiveCompanyRepository repository;

    // Handle get single company data request
    public Mono<ServerResponse> handleGetCompanyDataRequest(ServerRequest request) 
        //Some validation ges here

        return repository.findById(Mono.just(uuid))
            .flatMap(this::ok)
            .onErrorResume(IllegalArgumentException.class, e -> ServerResponse.badRequest().build())
            .switchIfEmpty(ServerResponse.notFound().build());
    

    private Mono<ServerResponse> ok (Company c) 
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
            .body(BodyInserters.fromPublisher(Mono.just(c), Company.class));
    

ReactiveCompanyRepository

@Component
public interface ReactiveCompanyRepository extends ReactiveCassandraRepository<Company, UUID>

    Mono<Company> findByName(String name);

    Mono<Company> findByEmail(String email);

我的问题是 .onErrorResume(IllegalArgumentException.class, e -> ServerResponse.badRequest().build()) 从未被调用,并且在测试用例中:

@SuppressWarnings("unchecked")
@Test
public void testGetCompanyExceptionDuringFind() 

    Mockito.when(repository.findById(Mockito.any(Mono.class))).thenThrow(new IllegalArgumentException("Hahaha"));       

    WebTestClient.bindToRouterFunction(routerConfig.route(companyHandler))
        .build()
        .get().uri("/company/2b851f10-356e-11e9-a847-0f89e1aa5554")
        .accept(MediaType.APPLICATION_JSON_UTF8)
        .exchange()
        .expectStatus().isBadRequest()
        .returnResult(Company.class)
        .getResponseBody();

我总是得到 HttpStatus 500 而不是 400。所以它失败了。任何帮助将不胜感激!

【问题讨论】:

【参考方案1】:

您的测试并不代表反应式 API 的行为方式。如果这样的存储库直接抛出异常,我会认为这是一个向维护者报告的错误。

当响应式 API 返回类似 MonoFlux 的响应式类型时,预计所有错误都不会直接抛出,而是在响应式管道中实际作为消息发送。

在这种情况下,您的测试用例可能更像:

Mockito.when(repository.findById(Mockito.any(Mono.class)))
   .thenReturn(Mono.error(new IllegalArgumentException("Hahaha")));

这样, Reactor 中的onError... 运算符将处理这些错误消息。

【讨论】:

感谢您的回复。不,IllegalArgumentException 是存储库服务抛出的:repository.findById(Mono.just(uuid)) 处理验证抛出的异常很好,我可以做到。 您能否在您的代码 sn-ps 中提供更多详细信息?我似乎无法理解你是否给它一个 UUID 或为什么抛出异常。你也有堆栈跟踪要分享吗? 刚刚更新了测试用例。对不起,我第一次粘贴错误的测试用例。如果您需要更多解释,请告诉我。我认为这更容易理解。 谢谢,我回家后会测试一下... :-) 再次感谢。现在好了。我对这个反应性世界有点陌生......接受了你的回答。

以上是关于Spring 5 反应式异常处理的主要内容,如果未能解决你的问题,请参考以下文章

android服务如何对未处理的异常做出反应?

spring boot 1.5.4 统一异常处理

Spring 5 Reactive 中的 HTTP 响应异常处理

Spring全局异常处理

Spring异常处理@ExceptionHandler

Spring Boot制作个人博客-框架搭建(异常处理)