【Spring Cache】七 Spring Cache 流程总结
前言
至此,我了解了 Spring Cache
相关的核心 API
,也了解了基于 @EableCaching
引入 Spring Cache
的流程以及 Spring Cache
的核心实现逻辑
本章节从一段 demo
入手,从上到下再梳理一遍流程做个总结
demo
CacheConfig
@Configuration
@EnableCaching
public class CacheConfig implements CachingConfigurer {
// 注册 ConcurrentMapCacheManager
@Override
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager
= new ConcurrentMapCacheManager();
return cacheManager;
}
// 自定义配置 KeyGenerator
@Override
public KeyGenerator keyGenerator() {
return new PrefixSimpleKeyGenerator("configurer");
}
// 注册一个 KeyGenerator 的 bean,用来在注解上指定
@Bean
public KeyGenerator customKeyGenerator() {
return new PrefixSimpleKeyGenerator("custom");
}
// 稍微拓展一下 SimpleKeyGenerator
public static class PrefixSimpleKeyGenerator extends SimpleKeyGenerator {
private String prefix;
public PrefixSimpleKeyGenerator(String prefix) {
this.prefix = prefix;
}
@Override
public Object generate(Object target, Method method, Object... params) {
return prefix + super.generate(target, method, params);
}
}
}
- 缓存的配置类,实现了
CachingConfigurer
并给出cacheManager
和keyGenerator
的配置,基于直接章节的了解,它们的优先级高于缺省属性,低于缓存注解上指定的属性 - 为了方便演示,这里使用的是
ConcurrentMapCacheManager
,且没有指定固定缓存,具体细节之前的章节有了解过 - 在容器中单独注册一个
KeyGenerator
用来缓存注解上指定对应属性,以方便缓存测试 PrefixSimpleKeyGenerator
,拓展了一下SimpleKeyGenerator
,可以添加指定前缀
DemoHandler
@Component
@CacheConfig(cacheNames = "default")
public class DemoHandler {
// 内存管理一组 User
static MultiValueMap<String, User> map = new LinkedMultiValueMap();
static {
map.add("a", new User(1, "a"));
map.add("a", new User(2, "a"));
map.add("b", new User(3, "b"));
}
@Cacheable(cacheNames = "core")
public List<User> query(String name) {
System.out.println("method query invoke ...");
return map.get(name);
}
@CacheEvict(cacheNames = "core", allEntries = true)
public void add(User user) {
System.out.println("method add invoke ...");
map.add(user.getName(), user);
}
@CacheEvict(allEntries = true)
public void add2(User user) {
System.out.println("method add2 invoke ...");
map.add(user.getName(), user);
}
@CacheEvict(cacheNames = "core", keyGenerator = "customKeyGenerator")
public void add3(User user) {
System.out.println("method add3 invoke ...");
map.add(user.getName(), user);
}
public static class User {
private Integer id;
private String name;
// ...
}
}
缓存操作业务类:
- 基于内存以
name
维度管理了一组User
@CacheConfig
注解可以理解为该类所有缓存方法的全局属性配置,它的优先级低于具体缓存方法操作上的属性,但比CacheConfigurer
自定义的属性优先级高(此处即缓存方法不指定属性cacheNames
时,默认default
)query
方法是一个CacheableOperation
:根据name
获取User
集合并缓存至core
缓存add
方法是一个CacheEvictOperation
:添加一个用户时清空所有的core
缓存(其实我们可以指定更加精细的KeyGenerator
实现指定的缓存更新,示例从简)add2
方法也是一个CacheEvictOperation
,但未指定cacheNames
,因此:添加一个用户会清空所有的default
缓存(而不是core
缓存),即之前的缓存不会失效add3
方法也是一个CacheEvictOperation
,但指定了单独的keyGenerator
,因此:添加一个用户会清空缓存core
对应keyGenerator
生成key
的缓存,即之前的缓存不会失效
TestMain
public class TestMain {
@Test
public void test() {
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext("com.example.springdemoall.cache.demo");
DemoHandler bean = context.getBean(DemoHandler.class);
// 第一次查并插入缓存 core
System.out.println(bean.query("a"));
System.out.println("---------");
// 命中缓存 core
System.out.println(bean.query("a"));
System.out.println("---------");
// 清空缓存 core
bean.add(new DemoHandler.User(4, "a"));
System.out.println("---------");
// 再查一次并缓存 core
System.out.println(bean.query("a"));
System.out.println("---------");
// 清空缓存 default
bean.add2(new DemoHandler.User(5, "a"));
System.out.println("---------");
// 命中缓存 core
System.out.println(bean.query("a"));
System.out.println("---------");
// 清除不同的 key
bean.add3(new DemoHandler.User(6, "a"));
System.out.println("---------");
// 命中缓存 core
System.out.println(bean.query("a"));
}
}
这就是最终的测试类,我们想要展示的场景是:
- 查询
name = a
的数据,此时没有缓存,但CacheableOperation
会在执行查询后更新缓存core
- 此时再查询
name = a
的数据,命中缓存core
直接返回不执行目标方法 - 调用
add
方法会清空缓存core
的所有数据(因为beforeInvocation = true)
- 此时再查询
name = a
的数据,无法命中缓存,CacheableOperation
会在执行查询后再次更新缓存core
- 调用
add2
方法会清空缓存default
的所有数据,则再查询name = a
的数据,还是会命中缓存core
直接返回而不执行目标方法 - 调用
add3
方法会清空缓存core
的另一个key
的数据,则再查询name = a
的数据,还是会命中缓存
整体流程
- 配置类上的
@EnableCaching
注解引入的对应配置类,注册了InfrastructureAdvisorAutoProxyCreator
,它会收集容器中role = BeanDefinition.ROLE_INFRASTRUCTURE
的Advisor
对指定的bean
进行代理 Spring Cache
提供的Advisor
就是BeanFactoryCacheOperationSourceAdvisor
,一般的理解:Advisor = Pointcut + Advice
- 此处的
Pointcut
是基于AnnotationCacheOperationSource
的CacheOperationSourcePointcut
,方法和类的匹配以及对应CacheOperation
s 的解析都是由AnnotationCacheOperationSource
实现的 - 此处的
Advice
就是CacheInterceptor
,因此标注了缓存相关注解的方法在被代理增强后就拥有了Spring Cache
的能力:@Cacheable
注解的方法支持从缓存中查询结果以及未命中后缓存后设置缓存@CachePut
注解的方法支持缓存的插入(更新)@CacheEvict
注解的方法支持缓存的剔除
- 此处的
Spring Boot
当前这个示例可以理解为 Spring
下的使用,那 Spring Boot
自然是提供了大量现成的装配类以供我们方便的使用各种 Spring Cache
的实现
CacheProperties
,此类支持我们使用spring.cache
前缀属性对Spring Cache
的属性进行指定,比如spring.cache.type = redis
,更多属性甚至到某个实现的细节(比如redis
缓存的超时设置等)配置可自行参考CacheType
,枚举类,定义了Spring Boot
都支持哪些缓存实现的装配,比如本示例中的缓存实现在Spring Boot
简化为spring.cache.type = simple
即可,其他的值还有redis
jcache
等CacheConfigurations
维护着每种缓存实现类型对应的配置类,想了解更多细节可以自行参考
总结
收尾篇,结合一个示例展示了简单的 Spring Cache
实现场景以及实现流程