一篇文章搞定Spring Cache
Spring Cache 提供了一种灵活而强大的缓存抽象,使得在应用中集成缓存变得更加容易。让我们从一些基本概念开始,然后深入讨论 Spring Cache 的用法和配置。
基本概念
-
缓存注解: Spring Cache 提供了一组注解,用于在方法级别声明缓存行为。其中包括
@Cacheable
、@CachePut
、@CacheEvict
等。这些注解允许你控制方法的缓存行为,如何从缓存中读取、写入和清除数据。 -
CacheManager:
CacheManager
是 Spring Cache 的核心接口,负责创建和管理缓存实例。Spring Cache 提供了默认的ConcurrentMapCacheManager
,同时也支持集成其他缓存提供者,如 Ehcache、Caffeine、Redis 等。 - 缓存键(Key): 缓存中的每个条目都有一个唯一的键,用于在缓存中标识数据。在 Spring Cache 中,键可以使用 SpEL 表达式构建,以根据方法参数、返回值等动态生成。
基本使用示例
让我们通过一个简单的示例来演示 Spring Cache 的基本使用。假设有一个服务类,其中包含一个根据用户ID获取用户信息的方法,我们希望将用户信息缓存起来:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Cacheable(value = "userCache", key = "#userId")
public String getUserInfo(String userId) {
// 模拟从数据库或其他数据源获取用户信息的操作
System.out.println("Fetching user info for userId: " + userId);
return "User info for userId: " + userId;
}
}
在上面的示例中,@Cacheable
注解表示 getUserInfo
方法的结果应该被缓存,缓存的名称为 "userCache",键为方法的参数 userId
。如果多次调用 getUserInfo
方法并传递相同的 userId
,则只有第一次会执行方法体,后续的调用将直接从缓存中获取结果。
配置缓存管理器
在Spring中,配置常见的缓存管理器通常涉及使用Spring Cache抽象,并选择适当的缓存提供者。以下是一些常见的缓存管理器配置示例,包括Ehcache、Caffeine、Redis等。
1. 使用 Ehcache 作为缓存管理器
首先,确保项目中引入了Ehcache的相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
然后,在Spring Boot的配置文件中添加Ehcache的配置,比如ehcache.xml
:
<!-- src/main/resources/ehcache.xml -->
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.ehcache.org/ehcache.xsd">
<defaultCache
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
/>
<!-- 其他缓存配置 -->
</ehcache>
然后,在Spring Boot的配置类中配置Ehcache的CacheManager
:
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}
@Bean
public EhCacheManagerFactoryBean ehCacheCacheManager() {
EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
factoryBean.setShared(true);
return factoryBean;
}
}
2. 使用 Caffeine 作为缓存管理器
添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
接下来,在 Spring Boot 项目的配置类中配置 Caffeine Cache 作为缓存管理器:
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeineCacheBuilder());
return cacheManager;
}
private Caffeine<Object, Object> caffeineCacheBuilder() {
return Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存失效时间为10分钟
.maximumSize(100); // 设置缓存的最大条目数
}
}
在上述配置中,CaffeineCacheManager
被配置为缓存管理器,通过 caffeineCacheBuilder
方法配置了 Caffeine Cache 的一些属性,比如失效时间和最大条目数。现在,你可以在服务类中使用 @Cacheable
注解来声明缓存:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Cacheable(value = "userCache", key = "#userId")
public String getUserInfo(String userId) {
// 模拟从数据库或其他数据源获取用户信息的操作
System.out.println("Fetching user info for userId: " + userId);
return "User info for userId: " + userId;
}
}
3. 使用 Redis 作为缓存管理器
当使用 Redis 作为缓存管理器时,首先确保项目中引入了 Redis 相关的依赖。在 Maven 中,可以添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
接下来,在 Spring Boot 项目的配置文件中配置 Redis 连接信息:
# src/main/resources/application.properties
spring.redis.host=localhost
spring.redis.port=6379
然后,在 Spring Boot 项目的配置类中配置 Redis Cache 作为缓存管理器:
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return RedisCacheManager.builder(redisConnectionFactory)
.build();
}
}
在这个配置中,RedisCacheManager
被配置为缓存管理器,并使用了 RedisConnectionFactory
连接到 Redis 服务器。现在,你可以在服务类中使用 @Cacheable
注解来声明缓存:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Cacheable(value = "userCache", key = "#userId")
public String getUserInfo(String userId) {
// 模拟从数据库或其他数据源获取用户信息的操作
System.out.println("Fetching user info for userId: " + userId);
return "User info for userId: " + userId;
}
}
4.使用Guava Cache 作为缓存容器时
添加以下依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version> <!-- 请根据最新版本调整 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
然后,可以在 Spring Boot 项目的配置类中配置 Guava Cache 作为缓存管理器:
import com.google.common.cache.CacheBuilder;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.guava.GuavaCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager();
cacheManager.setCacheBuilder(guavaCacheBuilder());
return cacheManager;
}
private CacheBuilder<Object, Object> guavaCacheBuilder() {
return CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存失效时间为10分钟
.maximumSize(100); // 设置缓存的最大条目数
}
}
@Service
public class MyService {
@Cacheable(value = "userCache", key = "#userId")
public String getUserInfo(String userId) {
// 模拟从数据库或其他数据源获取用户信息的操作
System.out.println("Fetching user info for userId: " + userId);
return "User info for userId: " + userId;
}
}
在上述配置中,GuavaCacheManager
被配置为缓存管理器,通过 guavaCacheBuilder
方法配置了 Guava Cache 的一些属性,比如失效时间和最大条目数。这样,你就可以在 Spring Boot 项目中使用 Guava Cache 作为缓存容器了。在实际应用中,可以根据具体的业务需求调整 Guava Cache 的配置参数。
5.使用多种缓存管理器
在 Spring 中,一个项目中可以有多个 CacheManager
,并且可以在不同的地方使用不同的 CacheManager
实例。这种情况下,每个 CacheManager
可以配置不同的缓存提供者,例如使用 Ehcache、Guava Cache 或者 Redis 等。
在实际项目中,有时候需要使用多个 CacheManager
的场景可能包括:
-
多种缓存需求: 不同的业务模块可能有不同的缓存需求,使用不同的
CacheManager
可以更好地满足各自的要求。 -
本地缓存与分布式缓存: 有些数据可能适合存储在本地缓存中,而有些可能需要存储在分布式缓存中。这时可以使用两个不同的
CacheManager
分别配置本地缓存和分布式缓存。 -
缓存过期策略: 不同的缓存数据可能需要不同的过期策略,使用不同的
CacheManager
可以灵活配置每个缓存的过期时间。
在配置多个 CacheManager
时,需要确保为它们分配唯一的名称,以便在使用时能够正确地引用。例如:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager localCacheManager() {
// 配置本地缓存管理器
return new ConcurrentMapCacheManager("localCache");
}
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
// 配置使用 Redis 的缓存管理器
return RedisCacheManager.builder(redisConnectionFactory).build();
}
}
在上述示例中,通过 localCacheManager
和 redisCacheManager
分别配置了本地缓存管理器和 Redis 缓存管理器。可以在需要的地方使用 @Cacheable
注解时指定使用哪个缓存管理器,例如:
@Service
public class MyService {
@Cacheable(value = "localCache", key = "#userId", cacheManager = "localCacheManager")
public String getLocalUserInfo(String userId) {
// 从本地缓存获取用户信息
// ...
}
@Cacheable(value = "redisCache", key = "#userId", cacheManager = "redisCacheManager")
public String getRedisUserInfo(String userId) {
// 从 Redis 缓存获取用户信息
// ...
}
}
总的来说,虽然一个项目可以有多个 CacheManager
,但需要根据具体需求和业务场景来决定是否需要配置多个 CacheManager
,以及如何使用它们。
常见的缓存管理器的区别以及怎么选型
常见的缓存管理器有不同的实现,如 Ehcache、Caffeine、Guava Cache、Redis 等,它们之间有一些区别,选型时需要考虑项目的需求和特点。以下是一些常见缓存管理器的区别和选型考虑:
- Ehcache:
- 本地缓存: Ehcache 是一个本地缓存库,适用于单节点的应用程序。它提供了丰富的配置选项,包括缓存过期策略、最大条目数等。
- 分布式: Ehcache 也有分布式版本(Terracotta),可以用于构建分布式缓存。
- Caffeine:
- 本地缓存: Caffeine 是一个高性能的本地缓存库,设计用于提供快速的、无锁的缓存。它支持过期策略、最大条目数等。
- 异步加载: Caffeine 支持异步加载数据,可用于优化缓存的冷启动问题。
- Guava Cache:
- 本地缓存: Guava Cache 是 Google Guava 提供的本地缓存实现,类似于 Caffeine。它提供了基本的过期策略和最大条目数的配置。
- 功能全面: Guava Cache 包含一些额外的功能,如监听缓存的变化、自动加载等。
- Redis:
- 分布式缓存: Redis 是一种分布式缓存数据库,适用于分布式系统。它可以存储大量数据,并提供灵活的过期策略、持久性、集群支持等。
- 数据类型支持: Redis 不仅可以作为缓存,还可以存储复杂的数据结构,如字符串、列表、哈希、集合等。
选型考虑因素:
- 项目规模: 对于小规模的单节点应用,本地缓存(如 Ehcache、Caffeine、Guava Cache)可能已经足够。对于大规模分布式系统,可能需要选择分布式缓存(如 Redis)。
- 性能需求: 如果追求高性能、低延迟,可以考虑使用 Caffeine 或 Guava Cache。它们都是本地缓存,具有快速的读写性能。
- 分布式需求: 如果应用部署在多个节点上,需要考虑使用支持分布式的缓存管理器,比如 Redis。Ehcache 的分布式版本也是一个选择。
- 数据持久性: 如果需要在系统重启后保留缓存数据,可以选择 Redis,因为它支持数据的持久化。
- 数据复杂性: 如果缓存的数据结构较为简单,且只需要基本的过期策略和最大条目数配置,本地缓存可能更轻量。如果需要更丰富的功能,如监听缓存变化、异步加载等,可以选择 Guava Cache。
- 系统架构: 考虑项目的整体架构和技术栈。如果已经在项目中使用了某个缓存管理器,且满足需求,可以继续使用,避免引入额外的复杂性。
常用注解
-
**@Cacheable**
:
- 用于声明一个方法的结果应该被缓存,如果缓存中存在相同的键(根据 SpEL 表达式生成的),则直接返回缓存的结果,不执行方法体。 (缓存中有则返回无读取数据后加入缓存并返回)
- 示例:
@Cacheable(value = "userCache", key = "#userId")
public String getUserInfo(String userId) {
// 方法体逻辑
}
-
**@CachePut**
:
- 用于声明一个方法的结果应该被缓存,不论缓存中是否已存在相同的键,都会执行方法体,并将结果更新到缓存中。 (更新缓存)
- 示例:
@CachePut(value = "userCache", key = "#userId")
public String updateUserInfo(String userId, String newInfo) {
// 方法体逻辑
}
-
**@CacheEvict**
:
- 用于声明一个方法执行后应清除缓存。可以通过
allEntries
属性清除缓存中的所有条目,也可以通过beforeInvocation
属性指定在方法执行前还是执行后清除缓存。 (删除缓存) - 示例:
@CacheEvict(value = "userCache", key = "#userId")
public void clearUserInfoCache(String userId) {
// 方法体逻辑
}
-
**@Caching**
:
- 用于将多个缓存注解组合在一起,可以在同一个方法上同时声明多个缓存注解。 (批量删除缓存)
- 示例:
@Caching(
cacheable = {
@Cacheable(value = "userCache", key = "#userId"),
@Cacheable(value = "emailCache", key = "#email")
},
put = {
@CachePut(value = "userCache", key = "#userId"),
@CachePut(value = "emailCache", key = "#email")
}
)
public String getUserInfoByEmailOrId(String userId, String email) {
// 方法体逻辑
}
这些注解可以与 SpEL 表达式一起使用,以动态生成缓存键。通过在方法上使用这些注解,可以方便地配置和管理缓存,而无需手动编写缓存逻辑。要使用这些注解,你需要在配置类上添加 @EnableCaching
注解,以启用 Spring Cache 功能。
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
// 配置相关的 CacheManager 等
}
这只是 Spring Cache 的一小部分功能,你可以根据具体的需求和业务场景选择合适的注解来实现缓存。在实际使用中,还可以配置一些高级功能,比如缓存的失效策略、条件缓存、缓存的自定义过期时间等。
缓存键的多种写法
在 Spring Cache 中,key
属性是用于指定缓存键的关键属性,它是一个 SpEL(Spring Expression Language)表达式。这意味着你可以使用 SpEL 表达式动态地构建缓存键。以下是一些常见的 key
属性的写法:
- 基本写法:
- 最简单的情况下,可以直接使用方法的参数作为缓存键:
@Cacheable(value = "userCache", key = "#userId")
public String getUserInfo(String userId) {
// 方法体逻辑
}
- 多个参数:
- 如果方法有多个参数,你可以通过使用
#
符号和参数名来引用这些参数:
@Cacheable(value = "userCache", key = "#userId + '-' + #userName")
public String getUserInfo(String userId, String userName) {
// 方法体逻辑
}
- SpEL 表达式:
- 使用 SpEL 表达式进行更复杂的键的构建,你可以在表达式中使用条件、方法调用等:
@Cacheable(value = "userCache", key = "#userId.concat('-').concat(#userName.toUpperCase())")
public String getUserInfo(String userId, String userName) {
// 方法体逻辑
}
- 返回值作为键:
- 有时,你可能想使用方法的返回值作为缓存键。可以使用
result
关键字引用方法的返回值:
@Cacheable(value = "userCache", key = "#result.userId")
public UserInfo getUserInfo(String userId) {
// 方法体逻辑
}
- 自定义 SpEL 函数:
- 你还可以定义自己的 SpEL 函数,以便在表达式中重用逻辑:
@Cacheable(value = "userCache", key = "#myUtil.generateKey(#userId)")
public String getUserInfo(String userId) {
// 方法体逻辑
}
在这个例子中,假设你已经在项目中定义了 myUtil
这个工具类,并在该类中有一个 generateKey
方法。
高级功能简单介绍
- 缓存失效策略:
- 使用
@Cacheable
、@CachePut
、@CacheEvict
注解时,可以通过condition
属性设置缓存失效的条件。例如,只有满足某个条件时才进行缓存:
@Cacheable(value = "userCache", key = "#userId", condition = "#result != null && #result.isValid()")
public UserInfo getUserInfo(String userId) {
// 方法体逻辑
}
- 条件缓存:
- 使用
unless
属性设置条件,如果满足条件,则不会缓存结果。与condition
相反,unless
表达式的值为true
时,不缓存:
@Cacheable(value = "userCache", key = "#userId", unless = "#result.isDisabled()")
public UserInfo getUserInfo(String userId) {
// 方法体逻辑
}
- 自定义缓存管理器:
- 如果你想使用除默认的
ConcurrentMapCacheManager
之外的缓存管理器,可以自定义CacheManager
:
@Configuration
@EnableCaching
public class CustomCacheConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager() {
// 自定义的缓存管理器
return new MyCustomCacheManager();
}
}
- 自定义缓存注解:
- 通过自定义缓存注解,你可以在方法上使用自定义的缓存注解,提供更复杂的缓存逻辑。例如:
@MyCustomCacheable(value = "userCache", key = "#userId")
public String getUserInfo(String userId) {
// 方法体逻辑
}
其中 @MyCustomCacheable
是一个自定义的缓存注解,你需要在配置类中注册它,并实现相应的缓存逻辑。
- 缓存的自定义过期时间:
- 在缓存管理器配置中,你可以设置默认的缓存过期时间。但有时,你可能需要对不同的缓存键设置不同的过期时间,可以使用
@Cacheable
的expire
属性:
@Cacheable(value = "userCache", key = "#userId", expire = 600) // 过期时间为 600 秒
public UserInfo getUserInfo(String userId) {
// 方法体逻辑
}