From c8ef6f2fd32baedd186f7cbeda0003d9276b645f Mon Sep 17 00:00:00 2001 From: michaelin007 Date: Tue, 23 Apr 2024 03:18:59 +0000 Subject: [PATCH 1/3] https://jira.baeldung.com/browse/BAEL-7789 --- .../spring-reactive-client-2/pom.xml | 14 ++ .../RetrieveAndExchangeApp.java | 12 ++ .../RetrieveAndExchangeController.java | 139 ++++++++++++++++++ .../webclientretrievevsexchange/User.java | 26 ++++ .../RetrieveAndExchangeBenchmark.java | 63 ++++++++ .../RetrieveAndExchangeIntegrationTest.java | 68 +++++++++ 6 files changed, 322 insertions(+) create mode 100644 spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeApp.java create mode 100644 spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeController.java create mode 100644 spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/User.java create mode 100644 spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeBenchmark.java create mode 100644 spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeIntegrationTest.java diff --git a/spring-reactive-modules/spring-reactive-client-2/pom.xml b/spring-reactive-modules/spring-reactive-client-2/pom.xml index 22b2943433..7953d5b4e1 100644 --- a/spring-reactive-modules/spring-reactive-client-2/pom.xml +++ b/spring-reactive-modules/spring-reactive-client-2/pom.xml @@ -58,6 +58,19 @@ spring-boot-devtools runtime + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + org.springframework spring-test @@ -140,6 +153,7 @@ 1.0.1.RELEASE 1.1.6 1.7.1 + 1.37 \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeApp.java b/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeApp.java new file mode 100644 index 0000000000..aac9533b5f --- /dev/null +++ b/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeApp.java @@ -0,0 +1,12 @@ +package com.baeldung.webclientretrievevsexchange; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class RetrieveAndExchangeApp { + + public static void main(String[] args) { + SpringApplication.run(RetrieveAndExchangeApp.class, args); + } +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeController.java b/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeController.java new file mode 100644 index 0000000000..183c8f4a66 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeController.java @@ -0,0 +1,139 @@ +package com.baeldung.webclientretrievevsexchange; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@RestController +public class RetrieveAndExchangeController { + + private static final Logger logger = LoggerFactory.getLogger(RetrieveAndExchangeController.class); + + WebClient client = WebClient.create("https://jsonplaceholder.typicode.com/users"); + + @GetMapping("/user/{id}") + Mono retrieveOneUser(@PathVariable int id) { + return client.get() + .uri("/{id}", id) + .retrieve() + .bodyToMono(User.class) + .onErrorResume(Mono::error); + } + + @GetMapping("/user-status/{id}") + Mono retrieveOneUserAndHandleErrorBaseOnStatus(@PathVariable int id) { + return client.get() + .uri("/{id}", id) + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, response -> Mono.error(new RuntimeException("Client Error: can't fetch user"))) + .onStatus(HttpStatusCode::is5xxServerError, response -> Mono.error(new RuntimeException("Server Error: can't fetch user"))) + .bodyToMono(User.class); + } + + @GetMapping("/user-id/{id}") + Mono> retrieveOneUserWithResponseEntity(@PathVariable int id) { + return client.get() + .uri("/{id}", id) + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .toEntity(User.class) + .onErrorResume(Mono::error); + } + + @GetMapping("/users") + Flux retrieveAllUsers() { + return client.get() + .retrieve() + .bodyToFlux(User.class) + .onErrorResume(Flux::error); + } + + @GetMapping("/user/exchange-alter/{id}") + Mono retrieveOneUserWithExchangeAndManipulate(@PathVariable int id) { + return client.get() + .uri("/{id}", id) + .exchangeToMono(res -> res.bodyToMono(User.class)) + .map(user -> { + user.setName(user.getName() + .toUpperCase()); + user.setId(user.getId() + 100); + return user; + }); + } + + @GetMapping("/user/exchange-mono/{id}") + Mono retrieveUsersWithExchangeAndError(@PathVariable int id) { + return client.get() + .uri("/{id}", id) + .exchangeToMono(res -> { + if (res.statusCode() + .is2xxSuccessful()) { + logger.info("Status code: " + res.headers() + .asHttpHeaders()); + logger.info("Content-type" + res.headers() + .contentType()); + return res.bodyToMono(User.class); + } else if (res.statusCode() + .is4xxClientError()) { + return Mono.error(new RuntimeException("Client Error: can't fetch user")); + } else if (res.statusCode() + .is5xxServerError()) { + return Mono.error(new RuntimeException("Server Error: can't fetch user")); + } else { + return res.createError(); + } + }); + } + + @GetMapping("/user/exchange-header/{id}") + Mono retrieveUsersWithExchangeAndHeader(@PathVariable int id) { + return client.get() + .uri("/{id}", id) + .exchangeToMono(res -> { + if (res.statusCode() + .is2xxSuccessful()) { + logger.info("Status code: " + res.headers() + .asHttpHeaders()); + logger.info("Content-type" + res.headers() + .contentType()); + return res.bodyToMono(User.class); + } else if (res.statusCode() + .is4xxClientError()) { + return Mono.error(new RuntimeException("Client Error: can't fetch user")); + } else if (res.statusCode() + .is5xxServerError()) { + return Mono.error(new RuntimeException("Server Error: can't fetch user")); + } else { + return res.createError(); + } + }); + } + + @GetMapping("/user-exchange") + Flux retrieveAllUserWithExchange(@PathVariable int id) { + return client.get() + .exchangeToFlux(res -> res.bodyToFlux(User.class)) + .onErrorResume(Flux::error); + } + + @GetMapping("/user-exchange-flux") + Flux retrieveUsersWithExchange() { + return client.get() + .exchangeToFlux(res -> { + if (res.statusCode() + .is2xxSuccessful()) { + return res.bodyToFlux(User.class); + } else { + return Flux.error(new RuntimeException("Error while fetching users")); + } + }); + } +} diff --git a/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/User.java b/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/User.java new file mode 100644 index 0000000000..cc9363c688 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/User.java @@ -0,0 +1,26 @@ +package com.baeldung.webclientretrievevsexchange; + +public class User { + + private int id; + private String name; + + public User() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeBenchmark.java b/spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeBenchmark.java new file mode 100644 index 0000000000..160d6d15a4 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeBenchmark.java @@ -0,0 +1,63 @@ +package com.baeldung.webclientretrievevsexchange; + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Benchmark; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.concurrent.TimeUnit; + +@State(Scope.Benchmark) +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 3, time = 10, timeUnit = TimeUnit.MICROSECONDS) +@Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.MICROSECONDS) +public class RetrieveAndExchangeBenchmark { + private WebClient client; + + @Setup + public void setup() { + this.client = WebClient.create("https://jsonplaceholder.typicode.com/users"); + } + + @Benchmark + public Mono retrieveOneUserUsingRetrieveMethod() { + return client.get() + .uri("/1") + .retrieve() + .bodyToMono(User.class) + .onErrorResume(Mono::error); + + } + + @Benchmark + public Flux retrieveManyUserUsingRetrieveMethod() { + return client.get() + .retrieve() + .bodyToFlux(User.class) + .onErrorResume(Flux::error); + } + + @Benchmark + public Mono retrieveOneUserUsingExchangeToMono() { + return client.get() + .uri("/1") + .exchangeToMono(res -> res.bodyToMono(User.class)) + .onErrorResume(Mono::error); + } + + @Benchmark + public Flux retrieveManyUserUsingExchangeToFlux() { + return client.get() + .exchangeToFlux(res -> res.bodyToFlux(User.class)) + .onErrorResume(Flux::error); + + } + +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeIntegrationTest.java b/spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeIntegrationTest.java new file mode 100644 index 0000000000..874949d5a3 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeIntegrationTest.java @@ -0,0 +1,68 @@ +package com.baeldung.webclientretrievevsexchange; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +@WebFluxTest +class RetrieveAndExchangeIntegrationTest { + + @Autowired + private WebTestClient webTestClient; + + @Test + void givenFirstUser_whenRetrieveMethodIsUsed_returnOk() throws Exception { + this.webTestClient.get() + .uri("/user/1") + .exchange() + .expectStatus() + .isOk(); + } + + @Test + void givenFirstUser_whenRetreiveMethodIsUsedWithOnStatusHandler_returnNotFound() throws Exception { + this.webTestClient.get() + .uri("/user-status/100") + .exchange() + .expectStatus() + .is5xxServerError(); + } + + @Test + void givenFirstUser_whenExchangeMonoMethodIsUsed_returnOk() throws Exception { + this.webTestClient.get() + .uri("/user/exchange-mono/1") + .exchange() + .expectStatus() + .isOk(); + } + + @Test + void givenAllUsers_whenRetrieveMethodIsUsed_returnOk() throws Exception { + this.webTestClient.get() + .uri("/users") + .exchange() + .expectStatus() + .isOk(); + } + + @Test + void givenSingleUser_whenResponseBodyIsAltered_returnOk() throws Exception { + this.webTestClient.get() + .uri("/user/exchange-alter/1") + .exchange() + .expectBody() + .json("{\"id\":101,\"name\":\"LEANNE GRAHAM\"}"); + } + + @Test + void givenAllUsers_whenExchangeFluxMethodIsUsed_returnOk() throws Exception { + this.webTestClient.get() + .uri("/user-exchange-flux") + .exchange() + .expectStatus() + .isOk(); + } + +} \ No newline at end of file From 2f1cd9a53c57b5f96b6cfb496300b406706b494d Mon Sep 17 00:00:00 2001 From: michaelin007 Date: Thu, 25 Apr 2024 04:18:21 +0000 Subject: [PATCH 2/3] Spring WebClient retrieve() vs exchange() --- .../RetrieveAndExchangeController.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeController.java b/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeController.java index 183c8f4a66..7cac164363 100644 --- a/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeController.java +++ b/spring-reactive-modules/spring-reactive-client-2/src/main/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeController.java @@ -29,7 +29,7 @@ public class RetrieveAndExchangeController { } @GetMapping("/user-status/{id}") - Mono retrieveOneUserAndHandleErrorBaseOnStatus(@PathVariable int id) { + Mono retrieveOneUserAndHandleErrorBasedOnStatus(@PathVariable int id) { return client.get() .uri("/{id}", id) .retrieve() @@ -76,10 +76,6 @@ public class RetrieveAndExchangeController { .exchangeToMono(res -> { if (res.statusCode() .is2xxSuccessful()) { - logger.info("Status code: " + res.headers() - .asHttpHeaders()); - logger.info("Content-type" + res.headers() - .contentType()); return res.bodyToMono(User.class); } else if (res.statusCode() .is4xxClientError()) { From 7f87e881e8185d9a6ae58e8788571335cf10244d Mon Sep 17 00:00:00 2001 From: michaelin007 Date: Fri, 26 Apr 2024 02:45:18 +0000 Subject: [PATCH 3/3] Spring WebClient retrieve() vs exchange() --- .../RetrieveAndExchangeIntegrationTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeIntegrationTest.java b/spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeIntegrationTest.java index 874949d5a3..8343f63a37 100644 --- a/spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeIntegrationTest.java +++ b/spring-reactive-modules/spring-reactive-client-2/src/test/java/com/baeldung/webclientretrievevsexchange/RetrieveAndExchangeIntegrationTest.java @@ -12,7 +12,7 @@ class RetrieveAndExchangeIntegrationTest { private WebTestClient webTestClient; @Test - void givenFirstUser_whenRetrieveMethodIsUsed_returnOk() throws Exception { + void givenFirstUser_whenRetrieveMethodIsUsed_thenReturnOk() throws Exception { this.webTestClient.get() .uri("/user/1") .exchange() @@ -21,7 +21,7 @@ class RetrieveAndExchangeIntegrationTest { } @Test - void givenFirstUser_whenRetreiveMethodIsUsedWithOnStatusHandler_returnNotFound() throws Exception { + void givenFirstUser_whenRetreiveMethodIsUsedWithOnStatusHandler_thenReturnNotFound() throws Exception { this.webTestClient.get() .uri("/user-status/100") .exchange() @@ -30,7 +30,7 @@ class RetrieveAndExchangeIntegrationTest { } @Test - void givenFirstUser_whenExchangeMonoMethodIsUsed_returnOk() throws Exception { + void givenFirstUser_whenExchangeMonoMethodIsUsed_thenReturnOk() throws Exception { this.webTestClient.get() .uri("/user/exchange-mono/1") .exchange() @@ -39,7 +39,7 @@ class RetrieveAndExchangeIntegrationTest { } @Test - void givenAllUsers_whenRetrieveMethodIsUsed_returnOk() throws Exception { + void givenAllUsers_whenRetrieveMethodIsUsed_thenReturnOk() throws Exception { this.webTestClient.get() .uri("/users") .exchange() @@ -48,7 +48,7 @@ class RetrieveAndExchangeIntegrationTest { } @Test - void givenSingleUser_whenResponseBodyIsAltered_returnOk() throws Exception { + void givenSingleUser_whenResponseBodyIsAltered_thenReturnOk() throws Exception { this.webTestClient.get() .uri("/user/exchange-alter/1") .exchange() @@ -57,7 +57,7 @@ class RetrieveAndExchangeIntegrationTest { } @Test - void givenAllUsers_whenExchangeFluxMethodIsUsed_returnOk() throws Exception { + void givenAllUsers_whenExchangeFluxMethodIsUsed_thenReturnOk() throws Exception { this.webTestClient.get() .uri("/user-exchange-flux") .exchange()