原作者:Spring 团队(页面无个人署名)。来源:Caching Data with Spring。源页未标独立发布日期;按2026年10月5日取回内容整理,要求Java 17+、Gradle 7.5+或Maven 3.5+。

本指南演示如何为 Spring 管理的 bean 启用缓存。我们会建立一个简单的图书仓库:第一次查询某个 ISBN 时故意等待三秒,再用重复调用观察缓存是否绕过了这段耗时工作。示例既提供 Java,也提供 Kotlin;两种语言实现的是同一条流程,选择其中一种即可。
准备什么,以及从哪里开始
原文预计需要约15分钟、一个顺手的编辑器或IDE,以及 Java 17或更高版本;构建工具使用 Gradle 7.5+或 Maven 3.5+。也可以直接把代码导入 Spring Tool Suite、IntelliJ IDEA 或 VS Code。这里保留的是取回时指南列出的要求,实际创建项目时仍需检查所选 Spring Boot 版本对 JDK 和构建器的要求。
可以从零逐步完成,也可以跳过熟悉的项目初始化:下载并解压指南仓库,或用下列命令克隆,再进入 gs-caching/initial,从创建图书模型开始。完成后,用 gs-caching/complete 对照结果。
git clone https://github.com/spring-guides/gs-caching.git
cd gs-caching/initial
使用 Spring Initializr 建立项目
原文提供了已预选适合本例配置的 Initializr 项目链接;也可以手动打开 Spring Initializr:
- 选择 Gradle 或 Maven,并选择要使用的语言。
- 打开 Dependencies,加入 Spring Cache Abstraction。
- 点击 Generate,下载并解压生成的ZIP文件。
- 在IDE中打开生成的项目。若IDE集成了Initializr,也可直接在那里完成这些选择。
Initializr 会配置依赖并完成大部分初始化工作。另一种方式是 fork GitHub 仓库后直接在编辑器中打开。原文使用通用的“web application”表述,不过这个练习靠命令行 Runner 展示行为,本身并不要求编写控制器或网页接口。
创建图书模型
图书只需要 ISBN 和书名两个值。Java 使用 record,Kotlin 使用 data class。
Java:Book
package com.example.caching;
public record Book(String isbn, String title) {
}
Kotlin:Book
package com.example.caching
data class Book(
val isbn: String,
val title: String
)
创建图书仓库
接下来为模型定义一个仓库接口。getByIsbn 接受 ISBN,返回一本书。
Java:BookRepository
package com.example.caching;
public interface BookRepository {
Book getByIsbn(String isbn);
}
Kotlin:BookRepository
package com.example.caching
interface BookRepository {
fun getByIsbn(isbn: String): Book
}
实际项目可以使用 Spring Data 对接多种 SQL 或 NoSQL 存储。不过本指南只需要一个简单实现,模拟网络服务或其他慢操作造成的延迟。每次调用先进入 simulateSlowService(),然后用传入的 ISBN 和固定书名 Some book 建立结果。
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);
}
}
}
Kotlin:未启用缓存的实现
package com.example.caching
import org.springframework.stereotype.Component
@Component
class SimpleBookRepository : BookRepository {
override fun getByIsbn(isbn: String): Book {
simulateSlowService()
return Book(isbn, "Some book")
}
// Don't do this at home
private fun simulateSlowService() {
Thread.sleep(3000L)
}
}
simulateSlowService 故意在每次 getByIsbn 调用中暂停三秒。原文的注释“Don’t do this at home”就是提醒这只是一种演示手段;稍后缓存会让相同 ISBN 的后续调用不必再次走进这段方法。
装配应用并多次调用仓库
先建立 Spring Boot 入口。main 调用 SpringApplication.run(),或 Kotlin 的 runApplication,启动应用。
Java:CachingApplication
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);
}
}
Kotlin:CachingApplication
package com.example.caching
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class CachingApplication
fun main(args: Array<String>) {
runApplication<CachingApplication>(*args)
}
@SpringBootApplication 把几项常见配置组合在一起:配置类可以提供 bean 定义;自动配置根据类路径、现有 bean 与属性决定加入哪些基础设施;组件扫描找到同包及子包中的组件、配置和服务。本例的类位于 com.example.caching,应放在入口类可扫描到的位置。原文还举例说明,类路径上存在 Spring MVC 时,自动配置可启用 DispatcherServlet 等Web行为。
这里无需编写 XML 或 web.xml。下一步添加 CommandLineRunner,通过构造器注入 BookRepository,再使用不同和重复的参数调用它。
Java:AppRunner
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"));
}
}
Kotlin:AppRunner
package com.example.caching
import org.slf4j.LoggerFactory
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component
@Component
class AppRunner(private val bookRepository: BookRepository) : CommandLineRunner {
private val logger = LoggerFactory.getLogger(javaClass)
override fun run(vararg args: String) {
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")}")
}
}
Runner 依次查询 isbn-1234、isbn-4567,然后重复这两个 ISBN,并再查询两次第一个 ISBN。尚未启用缓存时,即使取的是同一本书,仍然会进入慢仓库。原文提供以下历史输出片段,展示重复查询之间的等待。
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]
日志说明:这段原文日志只列出部分调用,而且第一段时间差并非恰好三秒;它是指南保留的示意输出,不能当作完整六次调用的逐行记录。代码中的延迟值是3000毫秒,本次整理并未运行程序或测量耗时。
启用缓存
在 SimpleBookRepository.getByIsbn 上添加 @Cacheable("books"),让查询结果进入名为 books 的缓存。方法仍然保留慢服务调用;变化在于后续经过缓存代理、且命中相同键的调用可直接返回已保存结果。这个示例只有一个字符串参数,因此重复 ISBN 可以复用对应缓存项。
Java:带 @Cacheable 的仓库
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);
}
}
}
Kotlin:带 @Cacheable 的仓库
package com.example.caching
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Component
@Component
class SimpleBookRepository : BookRepository {
@Cacheable("books")
override fun getByIsbn(isbn: String): Book {
simulateSlowService()
return Book(isbn, "Some book")
}
// Don't do this at home
private fun simulateSlowService() {
Thread.sleep(3000L)
}
}
仅仅添加方法注解还不够,还需要启用缓存注解处理。在入口类上添加 @EnableCaching。
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);
}
}
Kotlin:启用注解处理
package com.example.caching
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.cache.annotation.EnableCaching
@SpringBootApplication
@EnableCaching
class CachingApplication
fun main(args: Array<String>) {
runApplication<CachingApplication>(*args)
}
@EnableCaching 会启用相应的后处理机制,检查 Spring bean 中的缓存注解,并通过代理拦截方法调用,完成查缓存、执行方法及更新缓存等行为。除了 @Cacheable,这套机制也处理 @CachePut 和 @CacheEvict;具体含义可查阅 Spring Framework缓存参考文档与相应Javadoc。
Spring Boot 会自动配置合适的 CacheManager。本例没有加入专门的缓存库,因此使用以 ConcurrentHashMap 为基础的简单后备实现。Spring 缓存抽象还能对接多种缓存库,并支持 JSR-107(JCache)。缓存抽象规定如何使用缓存,本例后备实现则决定数据实际放在哪里,二者需要区分。
构建一个可执行 JAR
可以通过 Gradle 或 Maven 直接运行,也可以把依赖、类和资源打进一个可执行 JAR,再作为应用在不同环境中交付、版本化与部署。以下是原文命令,本文仅展示,未执行。
使用 Gradle 时,./gradlew bootRun 可直接启动;也可先运行 ./gradlew build,再运行生成的JAR:
java -jar build/libs/gs-caching-0.0.1-SNAPSHOT.jar
使用 Maven 时,./mvnw spring-boot:run 可直接启动;也可先运行 ./mvnw clean package,再运行:
java -jar target/gs-caching-0.0.1-SNAPSHOT.jar
JAR文件名取决于项目名称和版本;若自己生成的文件名不同,应以实际构建产物为准。
对照启用缓存后的调用
启用缓存后再次运行应用,并尝试加入相同或不同 ISBN 的调用,就可以观察差别。下面是原文使用 Java record 的缓存后日志:
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]
第一个 ISBN 的首次查询仍约等三秒,第二个 ISBN 的首次查询也约等三秒。随后重复查询已有 ISBN,日志几乎连续出现,因为结果已经存在于缓存中。这个对照说明 Spring 管理的 bean 已经通过缓存代理复用了结果。数值来自原文示例,并非本次环境的性能测试。
从入门示例走向实际使用
到这里,指南要完成的工作已经结束:为 Spring 管理的 bean 启用缓存,并从重复查询中看见缓存命中带来的行为变化。若继续学习,原文还推荐 Caching Data with Gemfire、Serving Web Content with Spring MVC 与 Building an Application with Spring Boot;贡献指南可见 Getting Started Guides wiki。
静态审核补充:这里没有设置过期时间、容量上限、数据变更时的失效规则,也没有实现多实例一致性。真实业务还需决定是否允许陈旧结果、缓存键是否应包含租户或权限上下文,以及如何防止缓存持续占用内存。不要把演示里的三秒阻塞搬到生产服务。
Java 版本捕获 InterruptedException 后只抛出包装异常,没有恢复线程中断标记。如果实际代码仍采用这个异常结构,应在重新抛出前调用 Thread.currentThread().interrupt();这是整理者的审查建议,以上代码按原文保留。缓存依赖 Spring 管理的代理调用路径;直接创建对象或在对象内部自调用,也不应直接套用本例结论。Kotlin 项目的代理相关编译配置则应随所选 Initializr 项目核实。
本稿只进行了静态阅读与代码文本核对,没有编译、运行或验证缓存命中。指南文字与代码的许可不同:代码是 Apache License 2.0,文字是 CC BY-ND 3.0。
版权与来源:示例代码采用Apache License 2.0(ASLv2);指南文字采用Creative Commons Attribution-NoDerivatives(源站链接指向CC BY-ND)。原文版权归原作者及相应权利人所有。Copyright © 2005–2026 Broadcom. All Rights Reserved.
版权与许可全文
以下保留本页涉及的来源材料或示例代码的版权、许可条件与免责声明;各自适用范围依原声明。中文翻译及编辑标注:未完纪,2026-10-05。
Apache-2.0
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.













暂无评论内容