From 126a2c515e9bc171a2e8056e179106c9168296e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Fri, 20 Sep 2024 18:56:53 +0200 Subject: [PATCH] New round of spring-webflux optimizations - Upgrade to Spring Boot 3.3.4 - Disable Netty leak detection - Turn a flapMap operation to a map - Switch to jstachio for view rendering - Remove unused JdbcDbRepository class - Polishing --- frameworks/Java/spring-webflux/pom.xml | 28 +++++++-- .../spring-webflux-mongo.dockerfile | 2 +- .../spring-webflux/spring-webflux.dockerfile | 2 +- .../src/main/java/benchmark/App.java | 5 +- .../main/java/benchmark/model/Fortune.java | 13 ++-- .../main/java/benchmark/model/Message.java | 15 ----- .../src/main/java/benchmark/model/World.java | 1 + .../benchmark/repository/DbRepository.java | 1 + .../repository/JdbcDbRepository.java | 63 ------------------- .../main/java/benchmark/web/DbHandler.java | 25 ++++---- .../src/main/java/benchmark/web/Fortunes.java | 10 +++ .../src/main/resources/application.yml | 2 +- .../{templates => }/fortunes.mustache | 0 13 files changed, 58 insertions(+), 109 deletions(-) delete mode 100644 frameworks/Java/spring-webflux/src/main/java/benchmark/model/Message.java delete mode 100644 frameworks/Java/spring-webflux/src/main/java/benchmark/repository/JdbcDbRepository.java create mode 100644 frameworks/Java/spring-webflux/src/main/java/benchmark/web/Fortunes.java rename frameworks/Java/spring-webflux/src/main/resources/{templates => }/fortunes.mustache (100%) diff --git a/frameworks/Java/spring-webflux/pom.xml b/frameworks/Java/spring-webflux/pom.xml index c15b211c92d..500e9fc8e07 100644 --- a/frameworks/Java/spring-webflux/pom.xml +++ b/frameworks/Java/spring-webflux/pom.xml @@ -13,13 +13,12 @@ org.springframework.boot spring-boot-starter-parent - 3.3.3 + 3.3.4 - 21 - 21 - UTF-8 + 21 + 1.3.6 @@ -36,8 +35,16 @@ r2dbc-postgresql - org.springframework.boot - spring-boot-starter-mustache + io.jstach + jstachio + ${jstachio.version} + + + io.jstach + jstachio-apt + ${jstachio.version} + provided + true org.springframework.boot @@ -55,6 +62,15 @@ org.apache.maven.plugins maven-compiler-plugin + + + + io.jstach + jstachio-apt + ${jstachio.version} + + + diff --git a/frameworks/Java/spring-webflux/spring-webflux-mongo.dockerfile b/frameworks/Java/spring-webflux/spring-webflux-mongo.dockerfile index 41eedefa4c6..d565d1556c3 100644 --- a/frameworks/Java/spring-webflux/spring-webflux-mongo.dockerfile +++ b/frameworks/Java/spring-webflux/spring-webflux-mongo.dockerfile @@ -13,4 +13,4 @@ RUN java -Djarmode=tools -jar app.jar extract EXPOSE 8080 -CMD ["java", "-Dlogging.level.root=OFF", "-Dreactor.netty.http.server.lastFlushWhenNoRead=true", "-jar", "app/app.jar", "--spring.profiles.active=mongo"] \ No newline at end of file +CMD ["java", "-Dlogging.level.root=OFF", "-Dio.netty.leakDetection.level=disabled", "-Dreactor.netty.http.server.lastFlushWhenNoRead=true", "-jar", "app/app.jar", "--spring.profiles.active=mongo"] \ No newline at end of file diff --git a/frameworks/Java/spring-webflux/spring-webflux.dockerfile b/frameworks/Java/spring-webflux/spring-webflux.dockerfile index 6ff2ed4e537..e1cee08ff31 100644 --- a/frameworks/Java/spring-webflux/spring-webflux.dockerfile +++ b/frameworks/Java/spring-webflux/spring-webflux.dockerfile @@ -12,4 +12,4 @@ RUN java -Djarmode=tools -jar app.jar extract EXPOSE 8080 -CMD ["java", "-Dlogging.level.root=OFF", "-Dreactor.netty.http.server.lastFlushWhenNoRead=true", "-jar", "app/app.jar", "--spring.profiles.active=r2dbc"] +CMD ["java", "-Dlogging.level.root=OFF", "-Dio.netty.leakDetection.level=disabled", "-Dreactor.netty.http.server.lastFlushWhenNoRead=true", "-jar", "app/app.jar", "--spring.profiles.active=r2dbc"] diff --git a/frameworks/Java/spring-webflux/src/main/java/benchmark/App.java b/frameworks/Java/spring-webflux/src/main/java/benchmark/App.java index c574863a9a9..3ba49056611 100644 --- a/frameworks/Java/spring-webflux/src/main/java/benchmark/App.java +++ b/frameworks/Java/spring-webflux/src/main/java/benchmark/App.java @@ -11,7 +11,6 @@ import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerResponse; -import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.WebHandler; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; @@ -20,8 +19,8 @@ public class App { @Bean - public HttpHandler httpHandler(RouterFunction route, ServerFilter serverFilter, ViewResolver viewResolver) { - WebHandler webHandler = RouterFunctions.toWebHandler(route, HandlerStrategies.builder().viewResolver(viewResolver).build()); + public HttpHandler httpHandler(RouterFunction route, ServerFilter serverFilter) { + WebHandler webHandler = RouterFunctions.toWebHandler(route, HandlerStrategies.builder().build()); return WebHttpHandlerBuilder.webHandler(webHandler).filter(serverFilter).build(); } diff --git a/frameworks/Java/spring-webflux/src/main/java/benchmark/model/Fortune.java b/frameworks/Java/spring-webflux/src/main/java/benchmark/model/Fortune.java index 12ae17e0448..d66f56215fc 100644 --- a/frameworks/Java/spring-webflux/src/main/java/benchmark/model/Fortune.java +++ b/frameworks/Java/spring-webflux/src/main/java/benchmark/model/Fortune.java @@ -4,9 +4,11 @@ import org.springframework.data.mongodb.core.mapping.Document; @Document -public final class Fortune { +public final class Fortune implements Comparable { + @Id public int id; + public String message; public Fortune(int id, String message) { @@ -14,11 +16,8 @@ public Fortune(int id, String message) { this.message = message; } - public int getId() { - return id; - } - - public String getMessage() { - return message; + @Override + public int compareTo(final Fortune other) { + return message.compareTo(other.message); } } diff --git a/frameworks/Java/spring-webflux/src/main/java/benchmark/model/Message.java b/frameworks/Java/spring-webflux/src/main/java/benchmark/model/Message.java deleted file mode 100644 index 8a94c8d3ed1..00000000000 --- a/frameworks/Java/spring-webflux/src/main/java/benchmark/model/Message.java +++ /dev/null @@ -1,15 +0,0 @@ -package benchmark.model; - -public class Message { - - private final String message; - - public Message(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - -} \ No newline at end of file diff --git a/frameworks/Java/spring-webflux/src/main/java/benchmark/model/World.java b/frameworks/Java/spring-webflux/src/main/java/benchmark/model/World.java index 612c7fef03a..ab096a1e313 100644 --- a/frameworks/Java/spring-webflux/src/main/java/benchmark/model/World.java +++ b/frameworks/Java/spring-webflux/src/main/java/benchmark/model/World.java @@ -9,6 +9,7 @@ public final class World { @Id public int id; + @Field("randomNumber") public int randomnumber; diff --git a/frameworks/Java/spring-webflux/src/main/java/benchmark/repository/DbRepository.java b/frameworks/Java/spring-webflux/src/main/java/benchmark/repository/DbRepository.java index 20e753e317e..54b6d0d9d02 100644 --- a/frameworks/Java/spring-webflux/src/main/java/benchmark/repository/DbRepository.java +++ b/frameworks/Java/spring-webflux/src/main/java/benchmark/repository/DbRepository.java @@ -6,6 +6,7 @@ import reactor.core.publisher.Mono; public interface DbRepository { + Mono getWorld(int id); Mono findAndUpdateWorld(int id, int randomNumber); diff --git a/frameworks/Java/spring-webflux/src/main/java/benchmark/repository/JdbcDbRepository.java b/frameworks/Java/spring-webflux/src/main/java/benchmark/repository/JdbcDbRepository.java deleted file mode 100644 index 5e159f816ed..00000000000 --- a/frameworks/Java/spring-webflux/src/main/java/benchmark/repository/JdbcDbRepository.java +++ /dev/null @@ -1,63 +0,0 @@ -package benchmark.repository; - -import benchmark.model.Fortune; -import benchmark.model.World; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Component; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Scheduler; - -@Component -@Profile("jdbc") -public class JdbcDbRepository implements DbRepository { - private final Logger log = LoggerFactory.getLogger(getClass()); - private final JdbcTemplate jdbcTemplate; - private final Scheduler scheduler; - - public JdbcDbRepository(JdbcTemplate jdbcTemplate, Scheduler scheduler) { - this.jdbcTemplate = jdbcTemplate; - this.scheduler = scheduler; - } - - @Override - public Mono getWorld(int id) { - log.debug("getWorld({})", id); - return Mono.fromCallable(() -> { - return jdbcTemplate.queryForObject( - "SELECT * FROM world WHERE id = ?", - (rs, rn) -> new World(rs.getInt("id"), rs.getInt("randomnumber")), - id); - }).subscribeOn(scheduler); - } - - private Mono updateWorld(World world) { - return Mono.fromCallable(() -> { - jdbcTemplate.update( - "UPDATE world SET randomnumber = ? WHERE id = ?", - world.randomnumber, - world.id); - return world; - }).subscribeOn(scheduler); - } - - @Override - public Mono findAndUpdateWorld(int id, int randomNumber) { - return getWorld(id).flatMap(world -> { - world.randomnumber = randomNumber; - return updateWorld(world); - }); - } - - @Override - public Flux fortunes() { - return Mono.fromCallable(() -> { - return jdbcTemplate.query( - "SELECT * FROM fortune", - (rs, rn) -> new Fortune(rs.getInt("id"), rs.getString("message"))); - }).subscribeOn(scheduler).flatMapIterable(fortunes -> fortunes); - } -} \ No newline at end of file diff --git a/frameworks/Java/spring-webflux/src/main/java/benchmark/web/DbHandler.java b/frameworks/Java/spring-webflux/src/main/java/benchmark/web/DbHandler.java index bafddaf83c6..e820e356254 100644 --- a/frameworks/Java/spring-webflux/src/main/java/benchmark/web/DbHandler.java +++ b/frameworks/Java/spring-webflux/src/main/java/benchmark/web/DbHandler.java @@ -7,17 +7,17 @@ import benchmark.model.Fortune; import benchmark.model.World; import benchmark.repository.DbRepository; +import io.jstach.jstachio.JStachio; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; -import static java.util.Comparator.comparing; - @Component public class DbHandler { @@ -33,7 +33,7 @@ public Mono db(ServerRequest request) { .switchIfEmpty(Mono.error(new Exception("No World found with Id: " + id))); return ServerResponse.ok() - .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .body(world, World.class); } @@ -45,7 +45,7 @@ public Mono queries(ServerRequest request) { .collectList(); return ServerResponse.ok() - .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .body(worlds, new ParameterizedTypeReference>() { }); } @@ -71,20 +71,21 @@ public Mono updates(ServerRequest request) { .collectList(); return ServerResponse.ok() - .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .body(worlds, new ParameterizedTypeReference>() { }); } public Mono fortunes(ServerRequest request) { - Mono> result = dbRepository.fortunes().collectList().flatMap(fortunes -> { - fortunes.add(new Fortune(0, "Additional fortune added at request time.")); - fortunes.sort(comparing(fortune -> fortune.message)); - return Mono.just(fortunes); + return Mono.just(new Fortune(0, "Additional fortune added at request time.")) + .concatWith(dbRepository.fortunes()) + .collectList() + .flatMap(fortunes -> { + Collections.sort(fortunes); + return ServerResponse.ok() + .header(HttpHeaders.CONTENT_TYPE, "text/html; charset=utf-8") + .bodyValue(JStachio.render(new Fortunes(fortunes))); }); - - return ServerResponse.ok() - .render("fortunes", Collections.singletonMap("fortunes", result)); } private static int randomWorldNumber() { diff --git a/frameworks/Java/spring-webflux/src/main/java/benchmark/web/Fortunes.java b/frameworks/Java/spring-webflux/src/main/java/benchmark/web/Fortunes.java new file mode 100644 index 00000000000..d8fc3dd7e2d --- /dev/null +++ b/frameworks/Java/spring-webflux/src/main/java/benchmark/web/Fortunes.java @@ -0,0 +1,10 @@ +package benchmark.web; + +import java.util.List; + +import benchmark.model.Fortune; +import io.jstach.jstache.JStache; + +@JStache(path = "fortunes.mustache") +public record Fortunes(List fortunes) { +} diff --git a/frameworks/Java/spring-webflux/src/main/resources/application.yml b/frameworks/Java/spring-webflux/src/main/resources/application.yml index af7743831e3..5737ad1f1c0 100755 --- a/frameworks/Java/spring-webflux/src/main/resources/application.yml +++ b/frameworks/Java/spring-webflux/src/main/resources/application.yml @@ -15,7 +15,7 @@ spring: r2dbc: username: ${database.username} password: ${database.password} - url: r2dbc:postgresql://${database.host}:${database.port}/${database.name} + url: r2dbc:postgresql://${database.host}:${database.port}/${database.name}?sslmode=loggerLevel=OFF&disableColumnSanitiser=true&assumeMinServerVersion=16&sslmode=disable --- spring: diff --git a/frameworks/Java/spring-webflux/src/main/resources/templates/fortunes.mustache b/frameworks/Java/spring-webflux/src/main/resources/fortunes.mustache similarity index 100% rename from frameworks/Java/spring-webflux/src/main/resources/templates/fortunes.mustache rename to frameworks/Java/spring-webflux/src/main/resources/fortunes.mustache