使用 Spring 缓存数据

本指南将引导您完成在 Spring 管理的 bean 上启用缓存的过程。

您将构建什么

您将构建一个应用程序,为简单的图书存储库启用缓存。

您需要什么

如何完成本指南

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

从头开始,请转到从 Spring Initializr 开始

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

完成后,您可以将您的结果与 gs-caching/complete 中的代码进行比较。

从 Spring Initializr 开始

您可以使用这个 预初始化项目 并单击“生成”下载 ZIP 文件。该项目已配置为符合本教程中的示例。

手动初始化项目

  1. 导航到 https://start.spring.io。此服务会为您拉取应用程序所需的所有依赖项,并为您完成大部分设置。

  2. 选择 Gradle 或 Maven 以及您想要使用的语言。本指南假设您选择了 Java。

  3. 单击 Dependencies 并选择 Spring cache abstraction

  4. 单击生成

  5. 下载生成的 ZIP 文件,这是一个已根据您的选择配置好的 Web 应用程序存档。

如果您的 IDE 集成了 Spring Initializr,您可以从 IDE 中完成此过程。
您还可以从 Github fork 该项目并在您的 IDE 或其他编辑器中打开它。

创建图书模型

首先,您需要为您的图书创建一个简单的模型。以下列表(来自 src/main/java/com/example/caching/Book.java)显示了如何做到这一点

package com.example.caching;

public class Book {

  private String isbn;
  private String title;

  public Book(String isbn, String title) {
    this.isbn = isbn;
    this.title = title;
  }

  public String getIsbn() {
    return isbn;
  }

  public void setIsbn(String isbn) {
    this.isbn = isbn;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  @Override
  public String toString() {
    return "Book{" + "isbn='" + isbn + '\'' + ", title='" + title + '\'' + '}';
  }

}

创建图书存储库

您还需要该模型的存储库。以下列表(来自 src/main/java/com/example/caching/BookRepository.java)显示了一个这样的存储库

package com.example.caching;

public interface BookRepository {

  Book getByIsbn(String isbn);

}

您可以使用 {SpringData}[Spring Data] 为各种 SQL 或 NoSQL 存储提供存储库的实现。但是,在本指南的目的下,您将仅使用一个朴素的实现,该实现模拟一些延迟(网络服务、缓慢的延迟或其他问题)。以下列表(来自 src/main/java/com/example/caching/SimpleBookRepository.java)显示了一个这样的存储库

package com.example.caching;

import org.springframework.stereotype.Component;

@Component
public class SimpleBookRepository implements BookRepository {

  @Override
  public Book getByIsbn(String isbn) {
    simulateSlowService();
    return new Book(isbn, "Some book");
  }

  // Don't do this at home
  private void simulateSlowService() {
    try {
      long time = 3000L;
      Thread.sleep(time);
    } catch (InterruptedException e) {
      throw new IllegalStateException(e);
    }
  }

}

simulateSlowService 会在每次 getByIsbn 调用中故意插入三秒钟的延迟。稍后,您将使用缓存来加速此示例。

使用存储库

接下来,您需要将存储库连接起来并使用它来访问一些图书。以下列表(来自 src/main/java/com/example/caching/CachingApplication.java)显示了如何做到这一点

package com.example.caching;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CachingApplication {

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

}

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

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

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

  • @ComponentScan:告诉 Spring 在 com/example 包中查找其他组件、配置和服务,使其能够找到控制器。

main() 方法使用 Spring Boot 的 SpringApplication.run() 方法启动应用程序。您是否注意到没有一行 XML?也没有 web.xml 文件。这个 Web 应用程序是 100% 纯 Java,您不必处理任何管道或基础设施的配置。

您还需要一个 CommandLineRunner 来注入 BookRepository 并用不同的参数多次调用它。以下列表(来自 src/main/java/com/example/caching/AppRunner.java)显示了该类

package com.example.caching;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class AppRunner implements CommandLineRunner {

  private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);

  private final BookRepository bookRepository;

  public AppRunner(BookRepository bookRepository) {
    this.bookRepository = bookRepository;
  }

  @Override
  public void run(String... args) throws Exception {
    logger.info(".... Fetching books");
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
  }

}

如果您此时尝试运行应用程序,您应该会注意到它相当慢,即使您多次检索完全相同的图书。以下示例输出显示了我们(故意糟糕)的代码创建的三秒延迟

2014-06-05 12:15:35.783  ... : .... Fetching books
2014-06-05 12:15:40.783  ... : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
2014-06-05 12:15:43.784  ... : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
2014-06-05 12:15:46.786  ... : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}

我们可以通过启用缓存来改善这种情况。

启用缓存

现在您可以为您的 SimpleBookRepository 启用缓存,以便图书被缓存到 books 缓存中。以下列表(来自 src/main/java/com/example/caching/SimpleBookRepository.java)显示了存储库定义

package com.example.caching;

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

@Component
public class SimpleBookRepository implements BookRepository {

  @Override
  @Cacheable("books")
  public Book getByIsbn(String isbn) {
    simulateSlowService();
    return new Book(isbn, "Some book");
  }

  // Don't do this at home
  private void simulateSlowService() {
    try {
      long time = 3000L;
      Thread.sleep(time);
    } catch (InterruptedException e) {
      throw new IllegalStateException(e);
    }
  }

}

您现在需要启用缓存注解的处理,如下例(来自 src/main/java/com/example/caching/CachingApplication.java)所示

package com.example.caching;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class CachingApplication {

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

}

@EnableCaching 注解会触发一个后处理器,该后处理器会检查每个 Spring bean 的公共方法上是否存在缓存注解。如果找到此类注解,会自动创建一个代理来拦截方法调用并相应地处理缓存行为。

后处理器处理 @Cacheable@CachePut@CacheEvict 注解。您可以参考 Javadoc 和 参考指南 以获取更多详细信息。

Spring Boot 会自动配置一个合适的 CacheManager 作为相关缓存的提供程序。有关更多详细信息,请参阅 Spring Boot 文档

我们的示例不使用特定的缓存库,因此我们的缓存存储是使用 ConcurrentHashMap 的简单后备方案。缓存抽象支持广泛的缓存库,并且完全符合 JSR-107 (JCache)。

构建可执行 JAR

您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必要依赖项、类和资源并运行的单个可执行 JAR 文件。构建可执行 JAR 使在整个开发生命周期中,跨不同环境等,轻松交付、版本化和部署服务作为应用程序。

如果您使用 Gradle,您可以通过使用 ./gradlew bootRun 运行应用程序。或者,您可以通过使用 ./gradlew build 构建 JAR 文件,然后按如下方式运行 JAR 文件

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

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

java -jar target/gs-caching-0.1.0.jar
这里描述的步骤创建了一个可运行的 JAR。您还可以构建一个经典的 WAR 文件

测试应用程序

现在缓存已启用,您可以再次运行应用程序,并通过添加具有相同 ISBN 或不同 ISBN 的其他调用来查看差异。这应该会带来巨大的不同。以下列表显示了启用缓存后的输出

2016-09-01 11:12:47.033  .. : .... Fetching books
2016-09-01 11:12:50.039  .. : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
2016-09-01 11:12:53.044  .. : isbn-4567 -->Book{isbn='isbn-4567', title='Some book'}
2016-09-01 11:12:53.045  .. : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
2016-09-01 11:12:53.045  .. : isbn-4567 -->Book{isbn='isbn-4567', title='Some book'}
2016-09-01 11:12:53.045  .. : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
2016-09-01 11:12:53.045  .. : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}

在前面的示例输出中,第一次检索图书仍然需要三秒钟。但是,第二次及后续相同图书的检索速度要快得多,这表明缓存正在发挥作用。

总结

恭喜!您刚刚为 Spring 管理的 bean 启用了缓存。

另请参阅

以下指南也可能有所帮助

想写新指南或为现有指南做贡献吗?请查看我们的贡献指南

所有指南的代码均采用 ASLv2 许可,文字内容采用署名-禁止演绎知识共享许可

获取代码