如何在反应性 Spring WebClient 调用的错误部分抛出异常?
How to throw an exception in on error part of reactive Spring WebClient call?
我想要以下方法在发生错误时抛出自定义异常:
@Service
public class MyClass {
private final WebClient webClient;
public MatcherClient(@Value("${my.url}") final String myUrl) {
this.webClient = WebClient.create(myUrl);
}
public void sendAsync(String request) {
Mono<MyCustomResponse> result = webClient.post()
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(BodyInserters.fromObject(request))
.retrieve()
.doOnError(throwable -> throw new CustomException(throwable.getMessage()))
.subscribe(response -> log.info(response));
}
}
我还设置了一个单元测试,期望抛出 CustomException。不幸的是,测试失败并且异常被包装到 Mono 对象中。这里还有测试代码供参考:
@Test(expected = CustomException.class)
public void testSendAsyncRethrowingException() {
MockResponse mockResponse = new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.setResponseCode(500).setBody("Server error");
mockWebServer.enqueue(mockResponse);
matcherService.matchAsync(track);
}
我正在使用 MockWebServer 模拟测试中的错误。
那么,如果要使我的方法真正抛出异常,我应该如何实现 doOnError 或 onError 部分?
我没有使用 doOnError
,而是改用订阅方法,同时接受错误消费者:
Mono<MyCustomResponse> result = webClient.post()
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(BodyInserters.fromObject(request))
.retrieve()
.subscribe(response -> log.info(response),
throwable -> throw new CustomException(throwable.getMessage()));
这份文档很有帮助:https://projectreactor.io/docs/core/release/reference/index.html#_error_handling_operators
我建议从网络客户端公开 API returns Mono<Void>
的反应式 API,特别是如果您将方法命名为 "sendAsync"。如果您必须阻止对 return/fail 的调用,则它不是异步的。如果你想提供一个 sendSync()
替代方案,你总是可以让它调用 sendAsync().block()
.
对于异常的转换,可以使用专用的onErrorMap
运算符。
对于测试,事实是,您不能 100% 测试具有纯命令式和同步结构(如 JUnit 的 Test(expected=?)
注释)的异步代码。 (虽然 一些 反应运算符不会引发并行性,所以这种测试 有时 可以工作)。
您也可以在此处使用 .block()
(测试是不太可能出现问题的罕见情况之一)。
但如果我是你,我会养成使用 reactor-test
中的 StepVerifier
的习惯。举一个总结我的建议的例子:
@Service
public class MyClass {
private final WebClient webClient;
public MatcherClient(@Value("${my.url}") final String myUrl) {
this.webClient = WebClient.create(myUrl);
}
public Mono<MyCustomResponse> sendAsync(String request) {
return webClient.post()
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(BodyInserters.fromObject(request))
.retrieve()
.onErrorMap(throwable -> new CustomException(throwable.getMessage()))
//if you really need to hardcode that logging
//(can also be done by users who decide to subscribe or further add operators)
.doOnNext(response -> log.info(response));
}
}
和测试:
@Test(expected = CustomException.class)
public void testSendAsyncRethrowingException() {
MockResponse mockResponse = new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.setResponseCode(500).setBody("Server error");
mockWebServer.enqueue(mockResponse);
//Monos are generally lazy, so the code below doesn't trigger any HTTP request yet
Mono<MyCustomResponse> underTest = matcherService.matchAsync(track);
StepVerifier.create(underTest)
.expectErrorSatisfies(t -> assertThat(t).isInstanceOf(CustomException.class)
.hasMessage(throwable.getMessage())
)
.verify(); //this triggers the Mono, compares the
//signals to the expectations/assertions and wait for mono's completion
}
The retrieve() method in WebClient throws a WebClientResponseException
whenever a response with status code 4xx or 5xx is received.
1.您可以使用 onStatus() 方法自定义异常
public Mono<JSONObject> listGithubRepositories() {
return webClient.get()
.uri(URL)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse ->
Mono.error(new MyCustomClientException())
)
.onStatus(HttpStatus::is5xxServerError, clientResponse ->
Mono.error(new MyCustomServerException())
)
.bodyToMono(JSONObject.class);
}
2。通过检查响应状态抛出自定义异常
Mono<Object> result = webClient.get().uri(URL).exchange().log().flatMap(entity -> {
HttpStatus statusCode = entity.statusCode();
if (statusCode.is4xxClientError() || statusCode.is5xxServerError())
{
return Mono.error(new Exception(statusCode.toString()));
}
return Mono.just(entity);
}).flatMap(clientResponse -> clientResponse.bodyToMono(JSONObject.class))
参考: https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/
我想要以下方法在发生错误时抛出自定义异常:
@Service
public class MyClass {
private final WebClient webClient;
public MatcherClient(@Value("${my.url}") final String myUrl) {
this.webClient = WebClient.create(myUrl);
}
public void sendAsync(String request) {
Mono<MyCustomResponse> result = webClient.post()
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(BodyInserters.fromObject(request))
.retrieve()
.doOnError(throwable -> throw new CustomException(throwable.getMessage()))
.subscribe(response -> log.info(response));
}
}
我还设置了一个单元测试,期望抛出 CustomException。不幸的是,测试失败并且异常被包装到 Mono 对象中。这里还有测试代码供参考:
@Test(expected = CustomException.class)
public void testSendAsyncRethrowingException() {
MockResponse mockResponse = new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.setResponseCode(500).setBody("Server error");
mockWebServer.enqueue(mockResponse);
matcherService.matchAsync(track);
}
我正在使用 MockWebServer 模拟测试中的错误。
那么,如果要使我的方法真正抛出异常,我应该如何实现 doOnError 或 onError 部分?
我没有使用 doOnError
,而是改用订阅方法,同时接受错误消费者:
Mono<MyCustomResponse> result = webClient.post()
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(BodyInserters.fromObject(request))
.retrieve()
.subscribe(response -> log.info(response),
throwable -> throw new CustomException(throwable.getMessage()));
这份文档很有帮助:https://projectreactor.io/docs/core/release/reference/index.html#_error_handling_operators
我建议从网络客户端公开 API returns Mono<Void>
的反应式 API,特别是如果您将方法命名为 "sendAsync"。如果您必须阻止对 return/fail 的调用,则它不是异步的。如果你想提供一个 sendSync()
替代方案,你总是可以让它调用 sendAsync().block()
.
对于异常的转换,可以使用专用的onErrorMap
运算符。
对于测试,事实是,您不能 100% 测试具有纯命令式和同步结构(如 JUnit 的 Test(expected=?)
注释)的异步代码。 (虽然 一些 反应运算符不会引发并行性,所以这种测试 有时 可以工作)。
您也可以在此处使用 .block()
(测试是不太可能出现问题的罕见情况之一)。
但如果我是你,我会养成使用 reactor-test
中的 StepVerifier
的习惯。举一个总结我的建议的例子:
@Service
public class MyClass {
private final WebClient webClient;
public MatcherClient(@Value("${my.url}") final String myUrl) {
this.webClient = WebClient.create(myUrl);
}
public Mono<MyCustomResponse> sendAsync(String request) {
return webClient.post()
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(BodyInserters.fromObject(request))
.retrieve()
.onErrorMap(throwable -> new CustomException(throwable.getMessage()))
//if you really need to hardcode that logging
//(can also be done by users who decide to subscribe or further add operators)
.doOnNext(response -> log.info(response));
}
}
和测试:
@Test(expected = CustomException.class)
public void testSendAsyncRethrowingException() {
MockResponse mockResponse = new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.setResponseCode(500).setBody("Server error");
mockWebServer.enqueue(mockResponse);
//Monos are generally lazy, so the code below doesn't trigger any HTTP request yet
Mono<MyCustomResponse> underTest = matcherService.matchAsync(track);
StepVerifier.create(underTest)
.expectErrorSatisfies(t -> assertThat(t).isInstanceOf(CustomException.class)
.hasMessage(throwable.getMessage())
)
.verify(); //this triggers the Mono, compares the
//signals to the expectations/assertions and wait for mono's completion
}
The retrieve() method in WebClient throws a WebClientResponseException whenever a response with status code 4xx or 5xx is received.
1.您可以使用 onStatus() 方法自定义异常
public Mono<JSONObject> listGithubRepositories() {
return webClient.get()
.uri(URL)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse ->
Mono.error(new MyCustomClientException())
)
.onStatus(HttpStatus::is5xxServerError, clientResponse ->
Mono.error(new MyCustomServerException())
)
.bodyToMono(JSONObject.class);
}
2。通过检查响应状态抛出自定义异常
Mono<Object> result = webClient.get().uri(URL).exchange().log().flatMap(entity -> {
HttpStatus statusCode = entity.statusCode();
if (statusCode.is4xxClientError() || statusCode.is5xxServerError())
{
return Mono.error(new Exception(statusCode.toString()));
}
return Mono.just(entity);
}).flatMap(clientResponse -> clientResponse.bodyToMono(JSONObject.class))
参考: https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/