Spring Cache
admin
2024-03-21 00:53:21
0

使用方式

1、 开启缓存能力

org.springframework.bootspring-boot-starter-cache2.1.3.RELEASE

2、在应用启动类添加@EnableCaching注解:

@SpringBootApplication
@EnableCaching
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

3、在业务方法添加@Cacheable等注解

@Cacheable(cacheNames = {"task"})
public TaskInfoDTO getTask(String taskId) {log.info("TestBuzz.getTask mock query from DB......");TaskInfoDTO taskInfoDTO = new TaskInfoDTO();taskInfoDTO.setTaskId(taskId);taskInfoDTO.setApplicantId("system");taskInfoDTO.setDescription("test");return taskInfoDTO;
}

4、模拟请求

@GetMapping("/test_cache")
public IResp testCache() {TaskInfoDTO taskInfoDTO = this.testBuzz.getTask("task123");return IResp.getSuccessResult(taskInfoDTO);
}

最佳实践

扩展性分析

原理解析

关键概念

CachingConfigurer
BeanFactoryCacheOperationSourceAdvisor
CacheOperationSource
CacheInterceptor
CacheAnnotationParser :解析方法、类上的缓存注解转换成CacheOperation

关键接口

缓存配置

spring-boot-autoconfigure有个包叫cache,毫无以为这里就是springboot定义并自动开启缓存配置的地方,该包下基本都是*Configuration类型的类,也就是Springboot自带的缓存相关配置,我们简单分析一下CacheAutoConfiguration/CacheConfigurations/GenericCacheConfiguration/NoOpCacheConfiguration/SimpleCacheConfiguration/CaffeineCacheConfiguration和RedisCacheConfiguration这几个配置类。

@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider> customizers) {return new CacheManagerCustomizers(customizers.orderedStream().collect(Collectors.toList()));}@Beanpublic CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties, ObjectProvider cacheManager) {return new CacheManagerValidator(cacheProperties, cacheManager);}@Configuration@ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)protected static class CacheManagerJpaDependencyConfigurationextends EntityManagerFactoryDependsOnPostProcessor {public CacheManagerJpaDependencyConfiguration() {super("cacheManager");}}
}

/*** {@link ImportSelector} to add {@link CacheType} configuration classes.*/
static class CacheConfigurationImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {CacheType[] types = CacheType.values();String[] imports = new String[types.length];for (int i = 0; i < types.length; i++) {imports[i] = CacheConfigurations.getConfigurationClass(types[i]);}return imports;}
}final class CacheConfigurations {private static final Map> MAPPINGS;static {Map> mappings = new EnumMap<>(CacheType.class);mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);MAPPINGS = Collections.unmodifiableMap(mappings);}public static String getConfigurationClass(CacheType cacheType) {Class configurationClass = MAPPINGS.get(cacheType);Assert.state(configurationClass != null, () -> "Unknown cache type " + cacheType);return configurationClass.getName();}public static CacheType getType(String configurationClassName) {for (Map.Entry> entry : MAPPINGS.entrySet()) {if (entry.getValue().getName().equals(configurationClassName)) {return entry.getKey();}}throw new IllegalStateException("Unknown configuration class " + configurationClassName);}
}

代理创建

创建代理工厂,然后选择是否需要直接代理目标类,然后装配增强器,然后调用JdkDynamicAopProxy或者CglibAopProxy创建代理。

参考 Spring AOP 章节。

初始化代理

ProxyCachingConfiguration 复用了父类的能力并且定了AOP的三个核心组件(Pointcut,Advice和Advisor)


@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();advisor.setCacheOperationSource(cacheOperationSource());advisor.setAdvice(cacheInterceptor());if (this.enableCaching != null) {advisor.setOrder(this.enableCaching.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;}
}

解析缓存注解

public AnnotationCacheOperationSource(boolean publicMethodsOnly) {this.publicMethodsOnly = publicMethodsOnly;this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
}
@Override
@Nullable
protected Collection findCacheOperations(Class clazz) {return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
}
@Override
@Nullable
protected Collection findCacheOperations(Method method) {return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
}protected Collection determineCacheOperations(CacheOperationProvider provider) {Collection ops = null;for (CacheAnnotationParser annotationParser : this.annotationParsers) {Collection annOps = provider.getCacheOperations(annotationParser);if (annOps != null) {if (ops == null) {ops = annOps;}else {Collection combined = new ArrayList<>(ops.size() + annOps.size());combined.addAll(ops);combined.addAll(annOps);ops = combined;}}}return ops;
}

AnnotationCacheOperationSource默认构造器使用的是SpringCacheAnnotationParser解析器,解析操作最终委托给SpringCacheAnnotationParser.parseCacheAnnotations,将注解分别解析成对应的操作

保存缓存注解

1、根据 method, targetClass 生成 cacheKey
2、将每个方法的缓存注解解析成 Collection
3、保存在 Map>

public Collection getCacheOperations(Method method, @Nullable Class targetClass) {if (method.getDeclaringClass() == Object.class) {return null;}Object cacheKey = getCacheKey(method, targetClass);Collection cached = this.attributeCache.get(cacheKey);if (cached != null) {return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);}else {// 依次从方法,类,原方法、原类查找缓存注解。遵循短路原理。Collection cacheOps = computeCacheOperations(method, targetClass);if (cacheOps != null) {if (logger.isTraceEnabled()) {logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);}this.attributeCache.put(cacheKey, cacheOps);}else {this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);}return cacheOps;}
}

缓存拦截

当拦截到调用时,将调用封装成CacheOperationInvoker并交给父类执行,父类CacheAspectSupport实现了SmartInitializingSingleton接口,在单例初始化后容器会调用afterSingletonsInstantiated方法:

1、首先检查是否是同步操作(@Cacheable特性),
1.1、如果是且满足条件缓存,获取逻辑并返回
1.2、否则返回业务逻辑免缓存调用invokeOperation
2、执行@CacheEvict的前置清除(beforeInvocation=true)
3、检查@Cacheable是否命中缓存
3.1、如果没有命中,则放入需要执行CachePutRequest列表暂存
3.2、如果检查缓存命中且没有@CachePut,则返回结果
3.3、否则使用业务查询结果作为返回结果,并且填充需要缓存的结果。
4、收集@CachePut操作,把@CachePut和@Cacheable未命中的请求同步到缓存
5、清理@CacheEvict的缓存(beforeInvocation=false)。


public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {@Override@Nullablepublic 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 {return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());}catch (CacheOperationInvoker.ThrowableWrapper th) {throw th.getOriginal();}}
}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 operations = cacheOperationSource.getCacheOperations(method, targetClass);if (!CollectionUtils.isEmpty(operations)) {return execute(invoker, method,new CacheOperationContexts(operations, method, args, target, targetClass));}}}return invoker.invoke();
}private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {// Special handling of synchronized invocationif (contexts.isSynchronized()) {CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);Cache cache = context.getCaches().iterator().next();try {return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));}catch (Cache.ValueRetrievalException ex) {throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();}}else {return invokeOperation(invoker);}}// 处理 @CacheEvictsprocessCacheEvicts(contexts.get(CacheEvictOperation.class), true,CacheOperationExpressionEvaluator.NO_RESULT);// 是否命中缓存Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));List cachePutRequests = new LinkedList<>();// 缓存没有命中,if (cacheHit == null) {collectPutRequests(contexts.get(CacheableOperation.class),CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);}Object cacheValue;Object returnValue;// 缓存命中,且不是 @CachePutif (cacheHit != null && !hasCachePut(contexts)) {// If there are no put requests, just use the cache hitcacheValue = cacheHit.get();returnValue = wrapCacheValue(method, cacheValue);}else {// 缓存没有命中或 @CachePutreturnValue = invokeOperation(invoker);cacheValue = unwrapReturnValue(returnValue);}collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);// 处理 @CachePutfor (CachePutRequest cachePutRequest : cachePutRequests) {cachePutRequest.apply(cacheValue);}// 处理 @CacheEvictsprocessCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);return returnValue;
}

相关内容

热门资讯

【MySQL】锁 锁 文章目录锁全局锁表级锁表锁元数据锁(MDL)意向锁AUTO-INC锁...
【内网安全】 隧道搭建穿透上线... 文章目录内网穿透-Ngrok-入门-上线1、服务端配置:2、客户端连接服务端ÿ...
GCN的几种模型复现笔记 引言 本篇笔记紧接上文,主要是上一篇看写了快2w字,再去接入代码感觉有点...
数据分页展示逻辑 import java.util.Arrays;import java.util.List;impo...
Redis为什么选择单线程?R... 目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程?三、R...
【已解决】ERROR: Cou... 正确指令: pip install pyyaml
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
Lock 接口解读 前置知识点Synchronized synchronized 是 Java 中的关键字,...
Win7 专业版安装中文包、汉... 参考资料:http://www.metsky.com/archives/350.htm...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
大模型未来趋势 大模型是人工智能领域的重要发展趋势之一,未来有着广阔的应用前景和发展空间。以下是大模型未来的趋势和展...
python实战应用讲解-【n... 目录 如何在Python中计算残余的平方和 方法1:使用其Base公式 方法2:使用statsmod...
学习u-boot 需要了解的m... 一、常用函数 1. origin 函数 origin 函数的返回值就是变量来源。使用格式如下...
常用python爬虫库介绍与简... 通用 urllib -网络库(stdlib)。 requests -网络库。 grab – 网络库&...
药品批准文号查询|药融云-中国... 药品批文是国家食品药品监督管理局(NMPA)对药品的审评和批准的证明文件...
【2023-03-22】SRS... 【2023-03-22】SRS推流搭配FFmpeg实现目标检测 说明: 外侧测试使用SRS播放器测...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
初级算法-哈希表 主要记录算法和数据结构学习笔记,新的一年更上一层楼! 初级算法-哈希表...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
【Docker】P3 Dock... Docker数据卷、宿主机与挂载数据卷的概念及作用挂载宿主机配置数据卷挂载操作示例一个容器挂载多个目...