本指南逐步介绍了使用Pivotal GemFire的数据结构来缓存代码中的某些调用的过程。

有关Pivotal GemFire概念以及从Pivotal GemFire访问数据的更多常规知识,请通读指南“ 使用Pivotal GemFire访问数据”

你会建立什么

您将构建一个服务,该服务从CloudFoundry托管的报价服务请求报价,并将其缓存在Pivotal GemFire中。

然后,您将看到再次获取相同的报价将消除对Quote服务的昂贵调用,因为在给定相同请求的情况下,由Pivotal GemFire支持的Spring Cache Abstraction将用于缓存结果。

报价服务位于...

http://gturnquist-quoters.cfapps.io

Quote服务具有以下API ...

GET /api         - get all quotes
GET /api/random  - get random quote
GET /api/{id}    - get specific quote

你需要什么

如何完成本指南

像大多数Spring 入门指南一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都可以使用工作代码。

从头开始 ,请继续使用Gradle构建

跳过基础知识 ,请执行以下操作:

完成后 ,您可以根据中的代码检查结果gs-caching-gemfire/complete

用Gradle构建

用Gradle构建

首先,您设置一个基本的构建脚本。在使用Spring构建应用程序时,可以使用任何喜欢的构建系统,但是此处包含使用GradleMaven所需的代码。如果您都不熟悉,请参阅使用Gradle 构建Java项目使用Maven构建Java项目

创建目录结构

在您选择的项目目录中,创建以下子目录结构;例如, mkdir -p src/main/java/hello在* nix系统上:

└── src
    └── main
        └── java
            └── hello

创建一个Gradle构建文件

build.gradle

buildscript {
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/libs-release" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.6.RELEASE")
    }
}

plugins {
    id "io.spring.dependency-management" version "1.0.5.RELEASE"
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'

sourceCompatibility = 1.8
targetCompatibility = 1.8

bootJar {
    baseName = 'gs-caching-gemfire'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/libs-release" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter") {
        exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
    }
    compile("org.springframework.data:spring-data-gemfire")
    compile("com.fasterxml.jackson.core:jackson-databind")
    compile("org.projectlombok:lombok")
    runtime("javax.cache:cache-api")
    runtime("org.springframework.shell:spring-shell:1.2.0.RELEASE")
}

Spring Boot gradle插件提供了许多方便的功能:

  • 它收集类路径上的所有jar,并构建一个可运行的单个“über-jar”,这使执行和传输服务更加方便。

  • 它搜索public static void main()标记为可运行类的方法。

  • 它提供了一个内置的依赖项解析器,用于设置版本号以匹配Spring Boot依赖项 。您可以覆盖所需的任何版本,但是它将默认为Boot选择的一组版本。

用Maven构建

用Maven构建

首先,您设置一个基本的构建脚本。使用Spring构建应用程序时,可以使用任何喜欢的构建系统,但是此处包含了使用Maven所需的代码。如果您不熟悉Maven,请参阅使用Maven 构建Java项目

创建目录结构

在您选择的项目目录中,创建以下子目录结构;例如, mkdir -p src/main/java/hello在* nix系统上:

└── src
    └── main
        └── java
            └── hello

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <groupId>org.springframework</groupId>
    <artifactId>gs-caching-gemfire</artifactId>
    <version>0.1.0</version>

    <properties>
        <spring-shell.version>1.2.0.RELEASE</spring-shell.version>
    </properties>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>javax.cache</groupId>
            <artifactId>cache-api</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-gemfire</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.shell</groupId>
            <artifactId>spring-shell</artifactId>
            <version>${spring-shell.version}</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Spring Boot Maven插件提供了许多方便的功能:

  • 它收集类路径上的所有jar,并构建一个可运行的单个“über-jar”,这使执行和传输服务更加方便。

  • 它搜索public static void main()标记为可运行类的方法。

  • 它提供了一个内置的依赖项解析器,用于设置版本号以匹配Spring Boot依赖项 。您可以覆盖所需的任何版本,但是它将默认为Boot选择的一组版本。

使用您的IDE进行构建

使用您的IDE进行构建

创建可绑定对象以获取数据

现在,您已经设置了项目和构建系统,您可以集中精力定义域对象,以捕获从Quote服务中获取引号(数据)所需的位。

src/main/java/hello/Quote.java

package hello;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import org.springframework.util.ObjectUtils;

import lombok.Data;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@SuppressWarnings("unused")
public class Quote {

	private Long id;

	private String quote;

	@Override
	public boolean equals(Object obj) {

		if (this == obj) {
			return true;
		}

		if (!(obj instanceof Quote)) {
			return false;
		}

		Quote that = (Quote) obj;

		return ObjectUtils.nullSafeEquals(this.getId(), that.getId());
	}

	@Override
	public int hashCode() {

		int hashValue = 17;

		hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getId());

		return hashValue;
	}

	@Override
	public String toString() {
		return getQuote();
	}
}

Quote域类有idquote属性。这是您将在本指南中进一步了解的两个主要属性。实施Quote使用Lombok项目已简化了该类。

此外QuoteQuoteResponse捕获报价请求中发送的报价服务发送的响应的整个有效负载。它包括status (又名type )以及quote 。此类还使用Project Lombok简化了实现。

src/main/java/hello/QuoteResponse.java

package hello;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.Data;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class QuoteResponse {

	@JsonProperty("value")
	private Quote quote;

	@JsonProperty("type")
	private String status;

	@Override
	public String toString() {
		return String.format("{ @type = %1$s, quote = '%2$s', status = %3$s }",
			getClass().getName(), getQuote(), getStatus());
	}
}

Quote服务的典型响应如下所示:

{
  "type":"success",
  "value": {
    "id":1,
    "quote":"Working with Spring Boot is like pair-programming with the Spring developers."
  }
}

这两个类别都标有@JsonIgnoreProperties(ignoreUnknown=true) 。这意味着即使可以检索其他JSON属性,也将忽略它们。

查询报价服务以获取数据

下一步是创建一个查询报价的服务类。

src/main/java/hello/QuoteService.java

package hello;

import java.util.Collections;
import java.util.Map;
import java.util.Optional;

import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@SuppressWarnings("unused")
@Service
public class QuoteService {

	protected static final String ID_BASED_QUOTE_SERVICE_URL = "http://gturnquist-quoters.cfapps.io/api/{id}";
	protected static final String RANDOM_QUOTE_SERVICE_URL = "http://gturnquist-quoters.cfapps.io/api/random";

	private volatile boolean cacheMiss = false;

	private final RestTemplate quoteServiceTemplate = new RestTemplate();

	/**
	 * Determines whether the previous service method invocation resulted in a cache miss.
	 *
	 * @return a boolean value indicating whether the previous service method invocation resulted in a cache miss.
	 */
	public boolean isCacheMiss() {
		boolean cacheMiss = this.cacheMiss;
		this.cacheMiss = false;
		return cacheMiss;
	}

	protected void setCacheMiss() {
		this.cacheMiss = true;
	}

	/**
	 * Requests a quote with the given identifier.
	 *
	 * @param id the identifier of the {@link Quote} to request.
	 * @return a {@link Quote} with the given ID.
	 */
	@Cacheable("Quotes")
	public Quote requestQuote(Long id) {
		setCacheMiss();
		return requestQuote(ID_BASED_QUOTE_SERVICE_URL, Collections.singletonMap("id", id));
	}

	/**
	 * Requests a random quote.
	 *
	 * @return a random {@link Quote}.
	 */
	@CachePut(cacheNames = "Quotes", key = "#result.id")
	public Quote requestRandomQuote() {
		setCacheMiss();
		return requestQuote(RANDOM_QUOTE_SERVICE_URL);
	}

	protected Quote requestQuote(String URL) {
		return requestQuote(URL, Collections.emptyMap());
	}

	protected Quote requestQuote(String URL, Map<String, Object> urlVariables) {

		return Optional.ofNullable(this.quoteServiceTemplate.getForObject(URL, QuoteResponse.class, urlVariables))
			.map(QuoteResponse::getQuote)
			.orElse(null);
	}
}

QuoteService使用Spring的 RestTemplate查询Quote服务的API 。 Quote服务返回一个JSON对象,但是Spring使用Jackson将数据绑定到QuoteResponse最终, Quote宾语。

该服务类的关键是requestQuote已被注释@Cacheable("Quotes")Spring的缓存抽象拦截了对requestQuote检查服务方法是否已经被调用。如果是这样, Spring的缓存抽象只会返回缓存的副本。否则, Spring继续调用该方法,将响应存储在缓存中,然后将结果返回给调用方。

我们还使用了@CachePut上的注释requestRandomQuote服务方法。由于此服务方法调用返回的报价是随机的,因此我们不知道会收到哪个报价。因此,我们无法查询缓存(即Quotes ),但我们可以缓存该调用的结果,这将对后续的调用产生积极影响requestQuote(id)调用,假设感兴趣的报价是先前随机选择和缓存的。

@CachePut使用SpEL表达式(“#result.id”)访问服务方法调用的结果并检索Quote用作缓存键。您可以在此处了解有关Spring的Cache Abstraction SpEL上下文的更多信息。

您必须提供缓存的名称。出于演示目的,我们将其命名为“ Quotes”,但在生产中,建议选择适当的描述性名称。这也意味着可以将不同的方法与不同的缓存关联。如果您对每个缓存使用不同的配置设置(例如不同的到期或逐出策略等),这将很有用。

稍后,当您运行代码时,您将看到运行每个调用所花费的时间,并且能够辨别缓存对服务响应时间的影响。这证明了缓存某些调用的价值。如果您的应用程序不断查找相同的数据,则缓存结果可以显着提高性能。

使应用程序可执行

尽管Pivotal GemFire缓存可以嵌入到Web应用程序和WAR文件中,但是下面演示的更简单的方法创建了一个独立的应用程序。您将所有内容打包在一个可运行的JAR文件中,由一个好的旧Java驱动main()方法。

src/main/java/hello/Application.java

package hello;

import java.util.Optional;

import org.apache.geode.cache.client.ClientRegionShortcut;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.cache.config.EnableGemfireCaching;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions;

@SpringBootApplication
@ClientCacheApplication(name = "CachingGemFireApplication", logLevel = "error")
@EnableCachingDefinedRegions(clientRegionShortcut = ClientRegionShortcut.LOCAL)
@EnableGemfireCaching
@SuppressWarnings("unused")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    ApplicationRunner runner(QuoteService quoteService) {

        return args -> {
            Quote quote = requestQuote(quoteService, 12L);
            requestQuote(quoteService, quote.getId());
            requestQuote(quoteService, 10L);
        };
    }

    private Quote requestQuote(QuoteService quoteService, Long id) {

        long startTime = System.currentTimeMillis();

        Quote quote = Optional.ofNullable(id)
            .map(quoteService::requestQuote)
            .orElseGet(quoteService::requestRandomQuote);

        long elapsedTime = System.currentTimeMillis();

        System.out.printf("\"%1$s\"%nCache Miss [%2$s] - Elapsed Time [%3$s ms]%n", quote,
            quoteService.isCacheMiss(), (elapsedTime - startTime));

        return quote;
    }
}

@SpringBootApplication是一个方便注释,它添加了以下所有内容:

  • @Configuration :将类标记为应用程序上下文的Bean定义的源。

  • @EnableAutoConfiguration :告诉Spring Boot根据类路径设置,其他bean和各种属性设置开始添加bean。例如,如果spring-webmvc在类路径上,此注释将应用程序标记为Web应用程序并激活关键行为,例如设置DispatcherServlet

  • @ComponentScan :告诉Spring在其中寻找其他组件,配置和服务hello包,让它找到控制器。

main()方法使用Spring Boot的SpringApplication.run()启动应用程序的方法。您是否注意到没有一行XML?没有web.xml文件。该Web应用程序是100%纯Java,因此您无需处理任何管道或基础结构。

配置的顶部是一个重要的注释: @EnableGemfireCaching 。这会启用缓存(即使用Spring的元注释@EnableCaching注释),并在后台声明其他重要的bean,以支持使用Pivotal GemFire作为缓存提供程序进行缓存。

第一个bean是的实例QuoteService用于访问Quotes REST-ful Web服务。

需要另外两个来缓存引号并执行应用程序的操作。

  • quotesRegion定义了关键的GemFire LOCAL客户端区域在缓存内存储报价。它专门命名为“ Quotes”以匹配@Cacheable("Quotes")在我们的QuoteService方法。

  • runnerSpring Boot的实例ApplicationRunner用于运行我们的应用程序的界面。

第一次请求报价(使用requestQuote(id) ),就会发生缓存未命中,并且将调用服务方法,从而导致明显的延迟,在接近零毫秒的位置上没有延迟。在这种情况下,缓存由输入参数链接(即id )的服务方法, requestQuote 。换句话说, id方法参数是缓存键。随后请求由ID标识的相同报价将导致缓存命中,从而避免了昂贵的服务调用。

为了演示起见, QuoteService包裹在一个单独的方法中( requestQuote在 - 的里面Application类)以捕获进行服务调用的时间。这样一来,您就可以准确地看到任何一个请求花费的时间。

构建可执行的JAR

您可以使用Gradle或Maven从命令行运行该应用程序。您还可以构建一个包含所有必需的依赖项,类和资源的可执行JAR文件,然后运行该文件。构建可执行的jar使得在整个开发生命周期中,跨不同环境等等的情况下,可以轻松地将服务作为应用程序进行发布,版本化和部署。

如果您使用Gradle,则可以使用./gradlew bootRun 。或者,您可以通过使用以下命令构建JAR文件: ./gradlew build然后运行JAR文件,如下所示:

java -jar build/libs/gs-caching-gemfire-0.1.0.jar

如果使用Maven,则可以通过使用以下命令运行应用程序./mvnw spring-boot:run 。或者,您可以使用以下命令构建JAR文件: ./mvnw clean package然后运行JAR文件,如下所示:

java -jar target/gs-caching-gemfire-0.1.0.jar
此处描述的步骤将创建可运行的JAR。您还可以构建经典的WAR文件

显示日志记录输出。该服务应在几秒钟内启动并运行。

"@springboot with @springframework is pure productivity! Who said in #java one has to write double the code than in other langs? #newFavLib"
Cache Miss [true] - Elapsed Time [776 ms]
"@springboot with @springframework is pure productivity! Who said in #java one has to write double the code than in other langs? #newFavLib"
Cache Miss [false] - Elapsed Time [0 ms]
"Really loving Spring Boot, makes stand alone Spring apps easy."
Cache Miss [true] - Elapsed Time [96 ms]

从中您可以看到,对报价服务的第一次调用花费了776毫秒,并导致缓存未命中。但是,第二个请求相同报价的调用花费了0毫秒,导致缓存命中。这清楚地表明第二个呼叫已缓存,并且从未真正到达Quote服务。但是,当对特定的非缓存报价请求进行最终服务调用时,它花费了96毫秒,并导致缓存未命中,因为此新报价在调用之前就不在缓存中。

摘要

恭喜你!您刚刚构建了执行一项昂贵操作的服务并对其进行了标记,以便可以缓存结果。

也可以看看

以下指南也可能会有所帮助:

是否要编写新指南或为现有指南做出贡献?查看我们的贡献准则

所有指南均以代码的ASLv2许可证和写作的Attribution,NoDerivatives创作共用许可证发布