Spring Cache 详细介绍——补充
Spring Cache 是 Spring Framework 提供的一个强大的缓存抽象层,它允许开发者通过简单的注解方式将缓存功能集成到应用程序中,而无需关心底层缓存实现的细节。下面我将从多个方面详细介绍 Spring Cache。
一、核心概念
Spring Cache 基于以下几个核心概念构建:
-
缓存抽象接口:Spring 定义了一套缓存抽象接口,如
Cache
和CacheManager
,使应用能够与各种缓存实现进行交互。 -
声明式缓存:通过注解(如
@Cacheable
、@CachePut
、@CacheEvict
等)来声明缓存行为,无需编写复杂的缓存逻辑代码。 -
缓存管理器:
CacheManager
接口负责创建、配置和管理缓存实例。 -
缓存解析器:
CacheResolver
接口用于确定在特定操作中应该使用哪些缓存。 -
键生成器:
KeyGenerator
接口用于生成缓存键,默认实现使用方法参数。
二、常用注解
Spring Cache 提供了几个核心注解:
-
@Cacheable:标记方法的返回值应该被缓存。如果缓存中已存在相同的键,则直接返回缓存值,否则执行方法并缓存结果。
-
@CachePut:强制执行方法并将结果放入缓存,不考虑缓存中是否已存在相同的键。
-
@CacheEvict:标记方法用于从缓存中移除数据。
-
@Caching:组合多个缓存注解。
-
@CacheConfig:在类级别设置缓存配置。
三、缓存管理器实现
Spring Cache 支持多种缓存管理器实现:
- SimpleCacheManager:使用简单的内存 Map 实现,主要用于测试。
- ConcurrentMapCacheManager:使用 ConcurrentHashMap 作为缓存存储。
- EhCacheCacheManager:与 EhCache 集成。
- RedisCacheManager:与 Redis 集成。
- CaffeineCacheManager:与 Caffeine 集成。
- JCacheCacheManager:与 JSR-107 (JCache) 兼容的缓存提供程序集成。
四、基本配置示例
下面是一个使用 Spring Boot 和 Redis 配置 Spring Cache 的示例:
controller:
@RestController
@RequestMapping("/api/users")
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/{id}")public ResponseEntity<?> getUser(@PathVariable Long id) {Optional<User> user = userService.getUserById(id);return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());}@PostMappingpublic ResponseEntity<User> saveUser(@RequestBody User user) {return ResponseEntity.ok(userService.saveUser(user));}@DeleteMapping("/{id}")public ResponseEntity<Void> deleteUser(@PathVariable Long id) {userService.deleteUser(id);return ResponseEntity.noContent().build();}@DeleteMapping("/cache")public ResponseEntity<Void> clearCache() {userService.clearAllUserCache();return ResponseEntity.noContent().build();}
}
service
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;// 缓存注解示例@Cacheable(value = "users", key = "#id", unless = "#result == null")public Optional<User> getUserById(Long id) {return userRepository.findById(id);}@CachePut(value = "users", key = "#user.id")public User saveUser(User user) {return userRepository.save(user);}@CacheEvict(value = "users", key = "#id")public void deleteUser(Long id) {userRepository.deleteById(id);}// 清空所有用户缓存@CacheEvict(value = "users", allEntries = true)public void clearAllUserCache() {// 方法体为空,仅用于触发缓存清除}
}
Repository:
@Repository
public class UserRepository {private final Map<Long, User> userMap = new HashMap<>();// 初始化一些测试数据public UserRepository() {userMap.put(1L, new User(1L, "张三", 25));userMap.put(2L, new User(2L, "李四", 30));userMap.put(3L, new User(3L, "王五", 35));}public Optional<User> findById(Long id) {System.out.println("从数据库查询用户: " + id);return Optional.ofNullable(userMap.get(id));}public User save(User user) {userMap.put(user.getId(), user);System.out.println("保存用户到数据库: " + user);return user;}public void deleteById(Long id) {userMap.remove(id);System.out.println("从数据库删除用户: " + id);}
}
model
public class User implements Serializable {private Long id;private String name;private Integer age;// 构造函数、getter和setter方法public User() {}public User(Long id, String name, Integer age) {this.id = id;this.name = name;this.age = age;}// getter和setter方法public Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getName() { return name; }public void setName(String name) { this.name = name; }public Integer getAge() { return age; }public void setAge(Integer age) { this.age = age; }@Overridepublic String toString() {return "User{id=" + id + ", name='" + name + "', age=" + age + '}';}
}
DemoApplication.java
@SpringBootApplication
@EnableCaching // 启用缓存功能
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}
application.yml
spring:redis:host: localhostport: 6379cache:type: redis
UserServiceTest.java
package com.example.demo;import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.Optional;import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
class UserServiceTest {@Autowiredprivate UserService userService;@Testvoid testGetUserById_CacheHit() {// 第一次调用 - 应该从数据库获取Optional<User> user1 = userService.getUserById(1L);assertTrue(user1.isPresent());assertEquals("张三", user1.get().getName());// 第二次调用 - 应该从缓存获取Optional<User> user2 = userService.getUserById(1L);assertTrue(user2.isPresent());assertEquals("张三", user2.get().getName());// 验证两次获取的是同一个对象(缓存命中)assertSame(user1.get(), user2.get());}@Testvoid testSaveUser() {// 创建新用户User newUser = new User(4L, "赵六", 40);// 保存用户,应该触发缓存更新User savedUser = userService.saveUser(newUser);assertEquals("赵六", savedUser.getName());// 获取用户,应该从缓存中获取Optional<User> retrievedUser = userService.getUserById(4L);assertTrue(retrievedUser.isPresent());assertEquals("赵六", retrievedUser.get().getName());}@Testvoid testDeleteUser() {// 确保用户存在Optional<User> user = userService.getUserById(1L);assertTrue(user.isPresent());// 删除用户,应该触发缓存清除userService.deleteUser(1L);// 再次获取用户,应该从数据库获取且不存在Optional<User> deletedUser = userService.getUserById(1L);assertFalse(deletedUser.isPresent());}
}
五、注解详解
1.@Cacheable
这个注解标记方法的返回值应该被缓存。常用属性:
value
/cacheNames
:指定缓存名称。key
:指定缓存键,可以使用 SpEL 表达式。condition
:指定缓存条件,使用 SpEL 表达式。unless
:指定不缓存的条件,使用 SpEL 表达式。sync
:是否同步缓存,防止缓存击穿。
2.@CachePut
这个注解标记方法的返回值应该被更新到缓存中,无论缓存中是否已存在该键。常用属性与@Cacheable
相同。
3.@CacheEvict
这个注解标记方法用于从缓存中移除数据。常用属性:
value
/cacheNames
:指定缓存名称。key
:指定要移除的缓存键。allEntries
:是否移除所有缓存项。beforeInvocation
:是否在方法执行前移除缓存。
4.@Caching
这个注解用于组合多个缓存注解。
5.@CacheConfig
这个注解用于类级别,设置缓存的默认配置。
六、高级特性
1.自定义键生成器
可以通过实现KeyGenerator
接口来自定义缓存键的生成策略:
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;@Component("myKeyGenerator")
public class MyKeyGenerator implements KeyGenerator {@Overridepublic Object generate(Object target, Method method, Object... params) {// 自定义键生成逻辑return target.getClass().getSimpleName() + "_" + method.getName() + "_" + String.join("_", params);}
}
2.自定义缓存解析器
可以通过实现CacheResolver
接口来自定义缓存解析策略。
3.条件缓存
使用condition
和unless
属性可以实现条件缓存:
@Cacheable(value = "users", key = "#id", condition = "#id > 0", unless = "#result == null")
public User getUserById(Long id) {return userRepository.findById(id).orElse(null);
}
4.缓存同步
使用sync
属性可以防止缓存击穿:
@Cacheable(value = "users", key = "#id", sync = true)
public User getUserById(Long id) {// 当缓存不存在时,只有一个线程会执行此方法return userRepository.findById(id).orElse(null);
}