编辑此页面

Spring Cache API 的 Quarkus 扩展

虽然我们鼓励用户使用 Quarkus 注解进行缓存,但 Quarkus 仍然以 spring-cache 扩展的形式为 Spring Cache 注解提供了一个兼容层。

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

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

  • 已安装 JDK 17+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

  • 熟悉 Spring DI 扩展

创建 Maven 项目

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

CLI
quarkus create app org.acme:spring-cache-quickstart \
    --extension='rest,spring-di,spring-cache' \
    --no-code
cd spring-cache-quickstart

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=spring-cache-quickstart \
    -Dextensions='rest,spring-di,spring-cache' \
    -DnoCode
cd spring-cache-quickstart

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

对于 Windows 用户

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

  • 如果使用 Powershell,请将 -D 参数包裹在双引号中,例如 "-DprojectArtifactId=spring-cache-quickstart"

此命令生成一个项目,该项目导入 spring-cachespring-di 扩展。

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

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

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

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

创建 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

然后,从浏览器调用 https://: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,它总是需要六秒钟才能回答。

启用缓存

现在您的 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 我们只添加了这个注解(以及相关的导入语句)。

让我们尝试再次调用 https://:8080/weather?city=Raleigh。您仍在等待很长时间才能收到答案。这是正常的,因为服务器刚刚重启并且缓存为空。

等一下!服务器在 WeatherForecastService 更新后自行重启?是的,这是 Quarkus 为开发人员提供的名为 live coding 的一项惊人功能。

现在缓存在之前的调用中已加载,尝试调用相同的 URL。这次,您应该得到一个超快的答案,其 executionTimeInMs 值接近 0。

让我们看看如果我们从未来的一天开始使用 https://:8080/weather?city=Raleigh&daysInFuture=1 URL 会发生什么。您应该在两秒钟后收到答案,因为请求的天数中已经有两个被加载到缓存中。

您还可以尝试使用不同的城市调用相同的 URL,并再次查看缓存的作用。第一次调用将花费六秒钟,随后的调用将立即回答。

恭喜!您只需一行代码就将应用程序数据缓存添加到了您的 Quarkus 应用程序中!

支持的功能

Quarkus 提供了与以下 Spring Cache 注解的兼容性

  • @Cacheable

  • @CachePut

  • @CacheEvict

请注意,在此 Spring Cache 注解扩展的第一个版本中,并非支持所有这些注解的功能(尝试使用不支持的功能时会记录适当的错误)。但是,计划在未来版本中添加其他功能。

相关内容