AsyncLoadingCache
in a Vert.x applicationThis document will show you how to use Caffeine’s AsyncLoadingCache
in a Vert.x application.
You will build an application that rotates images in a web page.
The images will be downloaded by the server from a public API exposed at https://http.cat.
Each image represents an HTTP status code as a 🐱.
Ready?
Note
|
Images created by Tomomi Imura (@girlie_mac) |
A text editor or IDE,
Java 11 or higher,
Maven or Gradle.
The code of this project contains Maven and Gradle build files that are functionally equivalent.
Here is the content of the pom.xml
file you should be using:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.vertx.howtos</groupId>
<artifactId>async-loading-cache-caffeine-howto</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<vertx.version>5.0.0.CR2</vertx.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<mainClass>io.vertx.howtos.caffeine.CatsVerticle</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Assuming you use Gradle with the Kotlin DSL, here is what your build.gradle.kts
file should look like:
build.gradle.kts
plugins {
java
application
}
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
}
dependencies {
implementation(platform("io.vertx:vertx-stack-depchain:5.0.0.CR2"))
implementation("io.vertx:vertx-core")
implementation("io.vertx:vertx-web")
implementation("io.vertx:vertx-web-client")
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
}
application {
mainClass = "io.vertx.howtos.caffeine.CatsVerticle"
}
The index.html
web page consists mainly of:
an <img>
tag in the body, and
a script that, after the page is loaded, changes the src
attribute of the image.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Rotating Cats Page</title>
<script type="application/javascript">
window.onload = function () { // (2)
const codes = [ // (3)
100,
200, 201, 204, 206,
301, 302, 303, 304, 307, 308,
401, 403, 404, 406, 407, 409, 410, 412, 416, 418, 425, 451,
500, 501, 502, 503, 504,
]
setInterval(function () { // (4)
let img = document.getElementById("cat-img"); // (5)
img.src = "/api/cats/" + codes[Math.floor(Math.random() * codes.length)]; // (6)
}, 250)
}
</script>
</head>
<body>
<div>
<img src="/api/cats/200" id="cat-img" alt="Cat image"/> <!--(1)-->
</div>
</body>
</html>
img
tag in the body with id
set to cat-img
run the script function when the page is loaded
define some HTTP status codes
schedule a function to execute periodically at a fixed-delay of 250 milliseconds
retrieve the img
element from the DOM using its id
update the src
attribute using an HTTP status code chosen randomly
We will need a Vert.x Web Client instance to fetch images:
request = WebClient.create(vertx)
.request(GET, new RequestOptions().setHost("http.cat").setPort(443).setSsl(true))
.as(BodyCodec.buffer());
We will also need a cache because we do not want to overload the backend API:
cache = Caffeine.newBuilder() // (1)
.expireAfterWrite(Duration.ofMinutes(1)) // (2)
.recordStats() // (3)
.executor(cmd -> context.runOnContext(v -> cmd.run())) // (4)
.buildAsync((key, exec) -> CompletableFuture.supplyAsync(() -> { // (5)
Future<Buffer> future = fetchCatImage(key); // (6)
return future.toCompletionStage(); // (7)
}, exec).thenComposeAsync(Function.identity(), exec));
vertx.setPeriodic(20000, l -> { // (8)
CacheStats stats = cache.synchronous().stats();
log.info("Stats: " + stats);
});
create a cache builder
configure the cache to expire items after 1 minute
enable statistics recording
define an executor which invokes tasks on the verticle context
create an asynchronous loader, which much return a CompletableFuture
, using the cache executor
fetch the cat image
convert the Vert.x Future
to a CompletionStage
log cache statistics periodically
Note
|
The executor definition and the complex loader implementation are not strictly needed here. Indeed, we will deploy a single verticle instance, bind the cache to a field and always invoke its methods from the event-loop. If that is your use-case, you may simplify the setup to:
If, however, you plan to deploy several instances of the verticle and to share the cache between them, stick to the previous implementation. It guarantees that the asynchronous loader is always invoked on the right context. |
Fetching the cat image consists in sending the request to the backend using the corresponding HTTP status code as URI:
private Future<Buffer> fetchCatImage(int code) {
return request.uri("/" + code)
.send()
.expecting(HttpResponseExpectation.SC_OK)
.map(HttpResponse::body);
}
Using Vert.x Web, creating the HTTP server for our API and static file is pretty straightforward:
Router router = Router.router(vertx);
router.get("/api/cats/:id").produces("image/*").handler(this::handleImageRequest);
router.get().handler(StaticHandler.create());
return vertx.createHttpServer()
.requestHandler(router)
.listen(8080)
.onSuccess(v -> log.info("Server started on port 8080"));
Here is how we will implement image request handling:
private void handleImageRequest(RoutingContext rc) {
Integer code = Integer.valueOf(rc.pathParam("id")); // (1)
CompletableFuture<Buffer> completableFuture = cache.get(code); // (2)
Future<Buffer> future = Future.fromCompletionStage(completableFuture, context); // (3)
future.onComplete(ar -> { // (4)
if (ar.succeeded()) {
rc.response()
.putHeader("Cache-Control", "no-store") // (5)
.end(ar.result());
} else {
rc.fail(ar.cause());
}
});
}
retrieve the specified code from the request path
invoke Caffeine (the image will be loaded from the backend transparently, if needed)
convert the CompletableFuture
returned by Caffeine to a Vert.x Future
on completion, send the image bytes (or the failure) to the client
instruct the browser to disable caching of the image (otherwise, it would query our server only once for a given code!)
The CatsVerticle
needs a main
method:
public static void main(String[] args) {
Vertx vertx = Vertx.vertx(); // (1)
vertx.deployVerticle(new CatsVerticle()).await(); // (2)
}
Create a Vert.x instance
Deploy CatsVerticle
You can run the application from:
your IDE, by running the main
method from the CatsVerticle
class, or
with Maven: mvn compile exec:java
, or
with Gradle: ./gradlew run
(Linux, macOS) or gradlew run
(Windows).
Browse to http://localhost:8080.
You should see the cat images rotating in the web page:
After some time, inspect the program output. You should read something like:
Mar 22, 2022 3:45:17 PM io.vertx.howtos.caffeine.CatsVerticle lambda$start$4 INFO: Stats: CacheStats{hitCount=52, missCount=28, loadSuccessCount=28, loadFailureCount=0, totalLoadTime=2514949257, evictionCount=0, evictionWeight=0} Mar 22, 2022 3:45:37 PM io.vertx.howtos.caffeine.CatsVerticle lambda$start$4 INFO: Stats: CacheStats{hitCount=132, missCount=28, loadSuccessCount=28, loadFailureCount=0, totalLoadTime=2514949257, evictionCount=0, evictionWeight=0} Mar 22, 2022 3:45:57 PM io.vertx.howtos.caffeine.CatsVerticle lambda$start$4 INFO: Stats: CacheStats{hitCount=212, missCount=28, loadSuccessCount=28, loadFailureCount=0, totalLoadTime=2514949257, evictionCount=0, evictionWeight=0} Mar 22, 2022 3:46:17 PM io.vertx.howtos.caffeine.CatsVerticle lambda$start$4 INFO: Stats: CacheStats{hitCount=267, missCount=53, loadSuccessCount=52, loadFailureCount=0, totalLoadTime=3337599348, evictionCount=28, evictionWeight=28} Mar 22, 2022 3:46:37 PM io.vertx.howtos.caffeine.CatsVerticle lambda$start$4 INFO: Stats: CacheStats{hitCount=344, missCount=56, loadSuccessCount=56, loadFailureCount=0, totalLoadTime=3480880213, evictionCount=28, evictionWeight=28}
Notice the changes in hitCount
, missCount
, or evictionCount
.
This document covered:
the Vert.x web client for making HTTP requests,
the Vert.x web server and router,
the integration of Caffeine’s asynchronous loading cache in a Vert.x application,
Vert.x periodic tasks.
Last published: 2024-12-21 01:22:18 +0000.