Quarkus Extension for Spring Cache API

尽管鼓励用户使用Quarkus annotations for caching,Quarkus 仍然以 `spring-cache`扩展的形式提供 Spring Cache 注解兼容性层。

While users are encouraged to use Quarkus annotations for caching, Quarkus nevertheless provides a compatibility layer for Spring Cache annotations in the form of the spring-cache extension.

本指南解释了 Quarkus 应用程序如何利用众所周知的 Spring Cache 注解为其 Spring bean 启用应用程序数据缓存。

This guide explains how a Quarkus application can leverage the well known Spring Cache annotations to enable application data caching for their Spring beans.

Prerequisites

include::{includes}/prerequisites.adoc[]* 熟悉 Spring DI 扩展

Unresolved directive in spring-cache.adoc - include::{includes}/prerequisites.adoc[] * Some familiarity with the Spring DI extension

Creating the Maven project

首先,我们需要一个新项目。使用以下命令创建一个新项目:

First, we need a new project. Create a new project with the following command:

Unresolved directive in spring-cache.adoc - include::{includes}/devtools/create-app.adoc[]

此命令生成导入 `spring-cache`和 `spring-di`扩展的项目。

This command generates a project which imports the spring-cache and spring-di extensions.

如果您已经配置了 Quarkus 项目,则可以通过在项目基本目录中运行以下命令将 `spring-cache`扩展添加到项目中:

If you already have your Quarkus project configured, you can add the spring-cache extension to your project by running the following command in your project base directory:

Unresolved directive in spring-cache.adoc - include::{includes}/devtools/extension-add.adoc[]

这会将以下内容添加到构建文件中:

This will add the following to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-spring-cache</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-spring-cache")

Creating the REST API

让我们首先创建一个服务,该服务将模拟对外部气象服务极其缓慢的调用。使用以下内容创建 src/main/java/org/acme/spring/cache/WeatherForecastService.java

Let’s start by creating a service which will simulate an extremely slow call to an external meteorological service. Create src/main/java/org/acme/spring/cache/WeatherForecastService.java with the following content:

package org.acme.spring.cache;

import java.time.LocalDate;

import org.springframework.stereotype.Component;

@Component
public class WeatherForecastService {

    public String getDailyForecast(LocalDate date, String city) {
        try {
            Thread.sleep(2000L); (1)
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return date.getDayOfWeek() + " will be " + getDailyResult(date.getDayOfMonth() % 4) + " in " + city;
    }

    private String getDailyResult(int dayOfMonthModuloFour) {
        switch (dayOfMonthModuloFour) {
            case 0:
                return "sunny";
            case 1:
                return "cloudy";
            case 2:
                return "chilly";
            case 3:
                return "rainy";
            default:
                throw new IllegalArgumentException();
        }
    }
}
1 This is where the slowness comes from.

我们还需要一个类,其中包含当用户询问未来三天天气预报时发送给用户的响应。以这种方式创建 src/main/java/org/acme/spring/cache/WeatherForecast.java

We also need a class which contains the response sent to the users when they ask for the next three days weather forecast. Create src/main/java/org/acme/spring/cache/WeatherForecast.java this way:

package org.acme.spring.cache;

import java.util.List;

public class WeatherForecast {

    private List<String> dailyForecasts;

    private long executionTimeInMs;

    public WeatherForecast(List<String> dailyForecasts, long executionTimeInMs) {
        this.dailyForecasts = dailyForecasts;
        this.executionTimeInMs = executionTimeInMs;
    }

    public List<String> getDailyForecasts() {
        return dailyForecasts;
    }

    public long getExecutionTimeInMs() {
        return executionTimeInMs;
    }
}

现在,我们只需要创建 `src/main/java/org/acme/spring/cache/WeatherForecastResource.java`类来使用服务和响应:

Now, we just need to create the src/main/java/org/acme/spring/cache/WeatherForecastResource.java class to use the service and response:

package org.acme.spring.cache;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.jboss.resteasy.reactive.RestQuery;

@Path("/weather")
public class WeatherForecastResource {

    @Inject
    WeatherForecastService service;

    @GET
    public WeatherForecast getForecast(@RestQuery String city, @RestQuery long daysInFuture) { (1)
        long executionStart = System.currentTimeMillis();
        List<String> dailyForecasts = Arrays.asList(
                service.getDailyForecast(LocalDate.now().plusDays(daysInFuture), city),
                service.getDailyForecast(LocalDate.now().plusDays(daysInFuture + 1L), city),
                service.getDailyForecast(LocalDate.now().plusDays(daysInFuture + 2L), city)
        );
        long executionEnd = System.currentTimeMillis();
        return new WeatherForecast(dailyForecasts, executionEnd - executionStart);
    }
}
1 If the daysInFuture query parameter is omitted, the three days weather forecast will start from the current day. Otherwise, it will start from the current day plus the daysInFuture value.

我们全都完成了!让我们检查一下一切是否正常。

We’re all done! Let’s check if everything’s working.

首先,使用以下命令运行该应用程序:

First, run the application using:

Unresolved directive in spring-cache.adoc - include::{includes}/devtools/dev.adoc[]

然后,在浏览器中调用 http://localhost:8080/weather?city=Raleigh。六秒钟之后,应用程序将响应类似如下内容:

Then, call http://localhost:8080/weather?city=Raleigh from a browser. After six long seconds, the application will answer something like this:

{"dailyForecasts":["MONDAY will be cloudy in Raleigh","TUESDAY will be chilly in Raleigh","WEDNESDAY will be rainy in Raleigh"],"executionTimeInMs":6001}

响应内容可能会因运行代码的日期而异。

The response content may vary depending on the day you run the code.

您可以尝试再次调用相同的 URL,它总是需要六秒钟来响应。

You can try calling the same URL again and again, it will always take six seconds to answer.

Enabling the cache

现在,您的 Quarkus 应用程序已启动并正在运行,让我们通过缓存外部气象服务响应极大地改善其响应时间。按如下方式更新`WeatherForecastService`类:

Now that your Quarkus application is up and running, let’s tremendously improve its response time by caching the external meteorological service responses. Update the WeatherForecastService class as follows:

package org.acme.cache;

import java.time.LocalDate;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component
public class WeatherForecastService {

    @Cacheable("weather-cache") (1)
    public String getDailyForecast(LocalDate date, String city) {
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return date.getDayOfWeek() + " will be " + getDailyResult(date.getDayOfMonth() % 4) + " in " + city;
    }

    private String getDailyResult(int dayOfMonthModuloFour) {
        switch (dayOfMonthModuloFour) {
            case 0:
                return "sunny";
            case 1:
                return "cloudy";
            case 2:
                return "chilly";
            case 3:
                return "rainy";
            default:
                throw new IllegalArgumentException();
        }
    }
}
1 We only added this annotation (and the associated import of course).

让我们再次尝试调用 http://localhost:8080/weather?city=Raleigh。收到答复之前仍需要等待很长的时间。这是因为服务器刚刚重启并清空了缓存。

Let’s try to call http://localhost:8080/weather?city=Raleigh again. You’re still waiting a long time before receiving an answer. This is normal since the server just restarted and the cache was empty.

等等!服务器在 WeatherForecastService`更新后自行重启?是的,这是 Quarkus 开发人员非常棒的功能之一,称为 `live coding

Wait a second! The server restarted by itself after the WeatherForecastService update? Yes, this is one of Quarkus amazing features for developers called live coding.

由于上一次调用中加载了缓存,请尝试再次调用相同的 URL。这次,您应该得到超快速的答复,其中 `executionTimeInMs`值接近 0。

Now that the cache was loaded during the previous call, try calling the same URL. This time, you should get a super fast answer with an executionTimeInMs value close to 0.

我们使用 `http://localhost:8080/weather?city=Raleigh&daysInFuture=1`URL 从未来的一天开始尝试下会发生什么。您应该在两秒后收到答复,因为请求的两天数据已加载到缓存中。

Let’s see what happens if we start from one day in the future using the http://localhost:8080/weather?city=Raleigh&daysInFuture=1 URL. You should get an answer two seconds later since two of the requested days were already loaded in the cache.

您还可以尝试用一个不同的城市来调用相同的 URL 并再次看到缓存如何运行。第一次调用将持续 6 秒钟,而接下来的调用会立即得到答复。

You can also try calling the same URL with a different city and see the cache in action again. The first call will take six seconds and the following ones will be answered immediately.

恭喜!您刚刚通过一行代码向 Quarkus 应用程序添加了应用程序数据缓存!

Congratulations! You just added application data caching to your Quarkus application with a single line of code!

Supported features

Quarkus 提供了对以下 Spring Cache 注释的兼容性:

Quarkus provides compatibility with the following Spring Cache annotations:

  • @Cacheable

  • @CachePut

  • @CacheEvict

请注意,在此第一个版本的 Spring Cache 注释扩展中,并非所有这些注释的功能都得到支持(在尝试使用不支持的功能时会记录适当的错误)。不过,未来版本中将会计划新增其他功能。

Note that in this first version of the Spring Cache annotations extension, not all features of these annotations are supported (with proper errors being logged when trying to use an unsupported feature). However, additional features are planned for future releases.