Quarkus Extension for Spring Cache API

尽管鼓励用户使用Quarkus annotations for caching,Quarkus 仍然以 `spring-cache`扩展的形式提供 Spring Cache 注解兼容性层。 本指南解释了 Quarkus 应用程序如何利用众所周知的 Spring Cache 注解为其 Spring bean 启用应用程序数据缓存。

Prerequisites

include::./_includes/prerequisites.adoc[]* 熟悉 Spring DI 扩展

Creating the Maven project

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

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

适用于 Windows 用户:

  • 如果使用 cmd,(不要使用反斜杠 \ ,并将所有内容放在同一行上)

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

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

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

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'

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

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

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 缓慢便由此产生。

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

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`类来使用服务和响应:

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 如果省略 `daysInFuture`查询参数,则三日天气预报将从当天开始。否则,它将从当天加上 `daysInFuture`值开始。

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

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

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

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

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

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

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

Enabling the cache

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

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 我们只添加了此注释(当然还有关联的导入)。

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

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

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

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

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

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

Supported features

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

  • @Cacheable

  • @CachePut

  • @CacheEvict

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