使用 Spring 缓存数据

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

您将构建什么

您将构建一个在简单书籍仓库上启用缓存的应用。

您需要什么

如何完成本指南

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

从零开始,请继续阅读从 Spring Initializr 开始

跳过基础部分,请执行以下操作

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

从 Spring Initializr 开始

您可以使用这个预初始化的项目,然后点击 Generate 下载 ZIP 文件。该项目配置适用于本教程中的示例。

手动初始化项目

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

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

  3. 点击 Dependencies,然后选择 Spring cache abstraction

  4. 点击 Generate

  5. 下载生成的 ZIP 文件,它是一个根据您的选择配置好的 Web 应用压缩包。

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

创建 Book 模型

首先,您需要为您的书创建一个简单的模型。以下清单(来自 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 + '\'' + '}';
  }

}

创建 Book 仓库

您还需要一个该模型的仓库。以下清单(来自 src/main/java/com/example/caching/BookRepository.java)展示了这样的仓库

package com.example.caching;

public interface BookRepository {

  Book getByIsbn(String isbn);

}

您可以使用 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 根据 classpath 设置、其他 bean 和各种属性设置开始添加 bean。例如,如果 classpath 中有 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 的额外调用来查看差异。这应该会产生巨大影响。以下清单显示了启用缓存后的输出

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 许可证发布,文字内容则以 署名-禁止演绎创作共用许可证 发布。

获取代码