spring cache 学习文档
1, spring cache 的使用
1.1 依赖导入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
1.2 代码示例
@RestController
@RequestMapping("/books")
public class BookController {
@Cacheable(value = "books", key = "#role+':'+#id")
@GetMapping("/{id}")
public Book getBook(@PathVariable("id") String id, @RequestHeader("role") String role) {
sleep(5000);
Book book = MockUtils.selectById(id);
return book;
}
@PostMapping
@Caching(put = {@CachePut(value = "books", key = "#role+':'+#book.id")}, evict = {@CacheEvict(cacheNames = {"book2"}, allEntries = true)})
public Book updateById(@RequestBody Book book, @RequestHeader("role") String role) {
MockUtils.updateById(book);
return book;
}
@Cacheable(cacheNames = {"book2", "book3"}, key = "#arg")
@GetMapping("/all")
public Object getAll(String arg) {
sleep(5000);
return MockUtils.books();
}
@CacheEvict(cacheNames = {"book3"},allEntries = true)
@GetMapping("/clear")
public void clear() {
}
@SneakyThrows
public void sleep(long time) {
Thread.sleep(time);
}
}
1.3 cache 注解介绍
Spring Cache 注解是 Spring 框架提供的一种方便的方式,用于简化应用程序中的缓存操作。通过使用这些注解,您可以轻松地将方法结果缓存起来,以提高应用程序的性能。以下是 Spring Cache 注解的使用介绍:
@EnableCaching: 这个注解通常用于配置类上,用于启用 Spring Cache 支持。它告诉 Spring 框架要启用缓存功能。配置类上使用@EnableCaching后,您可以在其他组件中使用缓存注解。-
@Cacheable: 这个注解用于标记方法,表示该方法的结果应该被缓存。您可以指定一个或多个缓存名称,以及一个缓存键(可使用 SpEL 表达式动态生成)。当方法被调用时,Spring 框架会首先检查缓存,如果找到缓存值,则直接返回缓存值,而不执行方法体。如果缓存中没有找到值,方法会执行,结果将被缓存。示例:
@Cacheable(value = "books", key = "#id") public Book getBook(String id) { // ... } -
@CacheEvict: 这个注解用于标记方法,表示该方法会清除一个或多个缓存中的值。您可以指定一个或多个缓存名称,以及一个缓存键(可使用 SpEL 表达式动态生成)。当方法执行后,它会清除指定缓存中的值。示例:
@CacheEvict(value = "books", key = "#id") public void deleteBook(String id) { // ... } -
@CachePut: 这个注解用于标记方法,表示该方法会将结果放入缓存。与@Cacheable不同,@CachePut无论缓存中是否已存在相同的键,都会执行方法体并将结果放入缓存。示例:
@CachePut(value = "books", key = "#book.id") public Book updateBook(Book book) { // ... } -
@Caching: 这个注解允许您同时应用多个缓存注解在同一个方法上。您可以在@Caching注解中包含多个@Cacheable,@CacheEvict, 或@CachePut注解,以定义多个缓存操作。示例:
javaCopy code @Caching(put = {@CachePut(value = "books", key = "#book.id")}, evict = {@CacheEvict(value = "books", allEntries = true)}) public void saveOrUpdateBook(Book book) { // ... } -
@CacheConfig: 这个注解通常用于配置类上,用于指定缓存的默认配置。您可以在该类中使用@Cacheable、@CacheEvict等注解时,省略缓存名称等配置,因为它们会继承自@CacheConfig。示例:
@CacheConfig(cacheNames = "books") public class BookService { // ... }
这些注解使缓存配置更加简单,让您可以通过注解方式轻松地控制缓存操作。通过在适当的方法上添加这些注解,您可以提高应用程序的性能,尤其是对于需要频繁访问的数据
1.4 缓存 key 的生成
@FunctionalInterface
public interface KeyGenerator {
/**
* Generate a key for the given method and its parameters.
* @param target the target instance
* @param method the method being called
* @param params the method parameters (with any var-args expanded)
* @return a generated key
*/
Object generate(Object target, Method method, Object... params);
}
2 spring cache 源码解析
2.1 开启注解
2.2 配置类引入
2.2.1 CachingConfigurationSelector
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
private static final String PROXY_JCACHE_CONFIGURATION_CLASS =
"org.springframework.cache.jcache.config.ProxyJCacheConfiguration";
private static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJCachingConfiguration";
private static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJJCacheConfiguration";
private static final boolean jsr107Present;
private static final boolean jcacheImplPresent;
static {
ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
jcacheImplPresent = ClassUtils.isPresent(PROXY_JCACHE_CONFIGURATION_CLASS, classLoader);
}
/**
* Returns {@link ProxyCachingConfiguration} or {@code AspectJCachingConfiguration}
* for {@code PROXY} and {@code ASPECTJ} values of {@link EnableCaching#mode()},
* respectively. Potentially includes corresponding JCache configuration as well.
*/
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
/**
* Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#PROXY}.
* <p>Take care of adding the necessary JSR-107 import if it is available.
*/
private String[] getProxyImports() {
List<String> result = new ArrayList<>(3);
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
}
return StringUtils.toStringArray(result);
}
/**
* Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#ASPECTJ}.
* <p>Take care of adding the necessary JSR-107 import if it is available.
*/
private String[] getAspectJImports() {
List<String> result = new ArrayList<>(2);
result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
if (jsr107Present && jcacheImplPresent) {
result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
}
return StringUtils.toStringArray(result);
}
}
2.2.2 ProxyCachingConfiguration 默认
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
//advistor 顾问 ,负责把
切点 pointcut(用来指定需要将通知使用到哪些地方,比如需要用在哪些类的哪些方法上,切入点就是做这个配置的。
) 和
advice(需要增强的功能)
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource());
advisor.setAdvice(cacheInterceptor());
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheInterceptor cacheInterceptor() {
CacheInterceptor interceptor = new CacheInterceptor();
interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
interceptor.setCacheOperationSource(cacheOperationSource());
return interceptor;
}
}
- AbstractCachingConfiguration : ProxyCachingConfiguration 的父类
@Configuration
public abstract class AbstractCachingConfiguration implements ImportAware {
@Nullable
protected AnnotationAttributes enableCaching;
@Nullable
protected Supplier<CacheManager> cacheManager;
@Nullable
protected Supplier<CacheResolver> cacheResolver;
@Nullable
protected Supplier<KeyGenerator> keyGenerator;
@Nullable
protected Supplier<CacheErrorHandler> errorHandler;
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.enableCaching = AnnotationAttributes.fromMap(
importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false));
if (this.enableCaching == null) {
throw new IllegalArgumentException(
"@EnableCaching is not present on importing class " + importMetadata.getClassName());
}
}
@Autowired(required = false)
void setConfigurers(Collection<CachingConfigurer> configurers) {
if (CollectionUtils.isEmpty(configurers)) {
return;
}
if (configurers.size() > 1) {
throw new IllegalStateException(configurers.size() + " implementations of " +
"CachingConfigurer were found when only 1 was expected. " +
"Refactor the configuration such that CachingConfigurer is " +
"implemented only once or not at all.");
}
CachingConfigurer configurer = configurers.iterator().next();
useCachingConfigurer(configurer);
}
/**
* Extract the configuration from the nominated {@link CachingConfigurer}.
*/
protected void useCachingConfigurer(CachingConfigurer config) {
this.cacheManager = config::cacheManager;
this.cacheResolver = config::cacheResolver;
this.keyGenerator = config::keyGenerator;
this.errorHandler = config::errorHandler;
}
}
- CacheOperationSourcePointcut: 配置切入点
- CacheOperationSource: 解析方法缓存配置用的
-
BeanFactoryCacheOperationSourceAdvisor :
CacheOperationSourcePointcut :
-
matches 最终调用的是CacheOperationSource 的方法 即系带有
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable { private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8); static { CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class); CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class); CACHE_OPERATION_ANNOTATIONS.add(CachePut.class); CACHE_OPERATION_ANNOTATIONS.add(Caching.class); } @Override public boolean isCandidateClass(Class<?> targetClass) { return AnnotationUtils.isCandidateClass(targetClass, CACHE_OPERATION_ANNOTATIONS); }
```
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
//过滤类
protected CacheOperationSourcePointcut() {
setClassFilter(new CacheOperationSourceClassFilter());
}
@Override
//过滤方法
public boolean matches(Method method, Class<?> targetClass) {
CacheOperationSource cas = getCacheOperationSource();
return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}
protected abstract CacheOperationSource getCacheOperationSource();
private class CacheOperationSourceClassFilter implements ClassFilter {
@Override
public boolean matches(Class<?> clazz) {
if (CacheManager.class.isAssignableFrom(clazz)) {
return false;
}
CacheOperationSource cas = getCacheOperationSource();
return (cas == null || cas.isCandidateClass(clazz));
}
}
}
```
2.3 aop 相关
2.3.1 cache 对应的 advistor
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
@Nullable
private CacheOperationSource cacheOperationSource;
private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
@Override
@Nullable
protected CacheOperationSource getCacheOperationSource() {
return cacheOperationSource;
}
};
public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
this.cacheOperationSource = cacheOperationSource;
}
public void setClassFilter(ClassFilter classFilter) {
this.pointcut.setClassFilter(classFilter);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
}
2.3 .2 aop 的 MethodInterceptor 方法拦截器
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
CacheOperationInvoker aopAllianceInvoker = () -> {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
};
try {
// CacheAspectSupport 提供通用逻辑
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
}
2.3.3 , CacheOperationInvoker
实际上只是个函数式接口 用来开执行 代理对象的MethodInvocation.proceed 方法,来获取返回值
@FunctionalInterface
public interface CacheOperationInvoker {
*/
Object invoke() throws ThrowableWrapper;
/**
* Wrap any exception thrown while invoking {@link #invoke()}.
*/
@SuppressWarnings("serial")
class ThrowableWrapper extends RuntimeException {
private final Throwable original;
public ThrowableWrapper(Throwable original) {
super(original.getMessage(), original);
this.original = original;
}
public Throwable getOriginal() {
return this.original;
}
}
}
2 .3.4 CacheAspectSupport : 用来处理缓存的操作
-
CacheOperationSource 用于获取缓存配置信息
[图片上传失败...(image-204870-1697073547857)]
BasicOperation:
[图片上传失败...(image-8ac017-1697073547857)]
- CacheOperation: 对应于缓存注解配置信息
[图片上传失败...(image-be5fe2-1697073547857)]
- CachePutRequest: 实际上就是获取cache ,然后根据key 来更新缓存
private class CachePutRequest {
private final CacheOperationContext context;
private final Object key;
public CachePutRequest(CacheOperationContext context, Object key) {
this.context = context;
this.key = key;
}
public void apply(@Nullable Object result) {
if (this.context.canPutToCache(result)) {
for (Cache cache : this.context.getCaches()) {
//更新缓存
doPut(cache, this.key, result);
}
}
}
}
@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
if (this.initialized) {
Class<?> targetClass = getTargetClass(target);
CacheOperationSource cacheOperationSource = getCacheOperationSource();
if (cacheOperationSource != null) {
Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
return execute(invoker, method,
new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
}
return invoker.invoke();
}
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 方法调用前的缓存过期处理,设置了CacheEvict.isBeforeInvocation属性时
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// Cacheable注解时,去查找缓存
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// 缓存未命中时,把Cacheable加入到CachePut的集合,后面用于写入缓存
List<CachePutRequest> cachePutRequests = new LinkedList<>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// 未找到缓存时,调用目标方法
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// 收集CachePut注解,后面写入缓存
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// 写入缓存
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// 处理缓存过期逻辑
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
// 返回结果给代理类
return returnValue;
}
3 spring cache 落地
3.1 spring cache 集成 caffeine
3.1.1 caffeine 介绍
Caffeine 是一个高性能的 Java 缓存库,用于在应用程序中管理内存缓存。它提供了许多功能和优化,以帮助应用程序提高数据的访问速度和降低资源消耗。以下是 Caffeine 的一些主要特点和优势:
- 高性能: Caffeine 被设计成一个高性能的缓存库,具有低延迟和高吞吐量。它通过各种算法和数据结构的使用来优化缓存操作,以确保快速的数据访问。
- 内存管理: Caffeine 具有灵活的内存管理功能,可以控制缓存的容量和淘汰策略。您可以指定缓存的最大容量、最大条目数、过期时间等,以满足您的应用程序需求。
- 并发支持: Caffeine 是线程安全的,支持高并发访问。它使用先进的并发数据结构来确保多线程环境下的数据一致性和性能。
- 加载和刷新策略: 您可以定义自定义的加载策略,以便在缓存中没有找到数据时加载数据。还可以定义自动刷新策略,以确保缓存中的数据保持新鲜。
- 监听器支持: Caffeine 允许您注册监听器以监听缓存事件,如数据加载、数据移除等。
- 统计信息: Caffeine 提供了丰富的缓存统计信息,帮助您了解缓存的使用情况和性能。
- 自定义策略: 您可以自定义各种策略,包括淘汰策略、加载策略和刷新策略,以满足不同的使用场景。
- 轻量级: Caffeine 是一个轻量级的库,没有过多的依赖,易于集成到各种 Java 项目中。
- 广泛使用: Caffeine 被广泛用于各种应用程序中,包括大型企业级应用、微服务架构、桌面应用程序等。
总的来说,Caffeine 是一个强大的缓存库,适用于需要高性能和可配置性的应用程序,它提供了丰富的功能和优化,可以显著提高应用程序的性能。
3.1.2 caffeine 依赖引入
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.1</version> <!-- 按需替换为最新版本 -->
</dependency>
3.1.3 配置 CaffeineCacheManager
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
.initialCapacity(100)
.maximumSize(10_000));
caffeineCacheManager.setCacheLoader(new CacheLoader<Object, Object>() {
@Override
public @Nullable Object load(@NonNull Object key) throws Exception {
log.info("数据过期了");
return null;
}
});
return caffeineCacheManager;
}
3.1.4 给不同的cacheName 配置不同的过期时间
3.1.4.1 ,自定义 CacheResolver
public class CustomCacheResolver extends SimpleCacheResolver {
private Map<String, Caffeine<Object, Object>> caffeineConfigMap;
public CustomCacheResolver(CacheManager cacheManager, Map<String, Caffeine<Object, Object>> caffeineConfigMap) {
super(cacheManager);
this.caffeineConfigMap = caffeineConfigMap;
}
@Override
protected Caffeine<Object, Object> createCacheConfiguration(String cacheName) {
return caffeineConfigMap.get(cacheName);
}
}
@Bean
public CustomCacheResolver customCacheResolver(CacheManager cacheManager) {
Map<String, Caffeine<Object, Object>> caffeineConfigMap = new HashMap<>();
caffeineConfigMap.put("cache1", caffeineConfig1());
caffeineConfigMap.put("cache2", caffeineConfig2());
return new CustomCacheResolver(cacheManager, caffeineConfigMap);
}
3.1.5 配置多个 cachemanger
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CaffeineCacheManager cacheManager1() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeineConfig1());
return cacheManager;
}
@Bean
public CaffeineCacheManager cacheManager2() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeineConfig2());
return cacheManager;
}
@Bean
public Caffeine<Object, Object> caffeineConfig1() {
return Caffeine.newBuilder()
.maximumSize(500)
.expireAfterWrite(10, TimeUnit.MINUTES);
}
@Bean
public Caffeine<Object, Object> caffeineConfig2() {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.MINUTES);
}
}
@Cacheable(cacheNames = "cache1", cacheManager = "cacheManager1")
public Object cache1Method() {
// ...
}
@Cacheable(cacheNames = "cache2", cacheManager = "cacheManager2")
public Object cache2Method() {
// ...
}
3.2 spring cache 集成redis
3.2.1 依赖
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)); // 设置缓存过期时间
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
3.2.2 配置类
@Configuration
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)); // 设置缓存过期时间
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
}