当前位置: 首页 > news >正文

SpringCache源码解析(三)——@EnableCaching

一、源码阅读

让我们进行源码阅读把。

1.1 阅读源码基础:

@Import(xxx.class)里的类可以有两种类:

  • ImportSelector接口的实现类;
  • ImportBeanDefinitionRegistrar接口的实现类;

两种接口简介:

  • ImportSelector接口:在配置类中被@Import加入到Spring容器中以后。Spring容器就会把ImportSelector接口方法返回的字符串数组中的类new出来对象然后放到工厂中去。

  • ImportBeanDefinitionRegistrar:ImportBeanDefinitionRegistrar接口是也是spring的扩展点之一,它可以支持我们自己写的代码封装成BeanDefinition对象,注册到Spring容器中,功能类似于注解@Service @Component。

Spring框架中ImportBeanDefinitionRegistrar的应用详解

ImportBeanDefinitionRegistrar的作用

1.2 CachingConfigurationSelector

在这里插入图片描述

@EnableCaching导入了CachingConfigurationSelector。
org.springframework.cache.annotation.CachingConfigurationSelector:

@Overridepublic String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {case PROXY:return getProxyImports();case ASPECTJ:return getAspectJImports();default:return null;}}

getProxyImports方法:

private String[] getProxyImports() {List<String> result = new ArrayList<>(3);//将AutoProxyRegistrar注入到容器result.add(AutoProxyRegistrar.class.getName());result.add(ProxyCachingConfiguration.class.getName());if (jsr107Present && jcacheImplPresent) {result.add(PROXY_JCACHE_CONFIGURATION_CLASS);}return StringUtils.toStringArray(result);}

getAspectJImports方法:

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);}

1.3 getProxyImports方法内部

1.3.1 AutoProxyRegistrar

  顾名思义,AutoProxyRegistrar从名称上就可以看出其本质是一个ImportBeanDefinitionRegistrar,而ImportBeanDefinitionRegistrar最重要的方法为registerBeanDefinitions(),这个方法的主要作用就是用方法参数中的registry向容器中注入一些BeanDefinition。

源码追踪:

@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {boolean candidateFound = false;Set<String> annTypes = importingClassMetadata.getAnnotationTypes();//1.遍历类元数据中的所有注解类型for (String annType : annTypes) {AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);if (candidate == null) {continue;}Object mode = candidate.get("mode");Object proxyTargetClass = candidate.get("proxyTargetClass");//2.检查每个注解是否包含mode和proxyTargetClass属性,并判断其类型是否正确。if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&Boolean.class == proxyTargetClass.getClass()) {candidateFound = true;//3.如果属性类型正确且mode为AdviceMode.PROXY,则根据proxyTargetClass值配置自动代理创建器。if (mode == AdviceMode.PROXY) {AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);if ((Boolean) proxyTargetClass) {AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);return;}}}}if (!candidateFound && logger.isInfoEnabled()) {String name = getClass().getSimpleName();logger.info(String.format("%s was imported but no annotations were found " +"having both 'mode' and 'proxyTargetClass' attributes of type " +"AdviceMode and boolean respectively. This means that auto proxy " +"creator registration and configuration may not have occurred as " +"intended, and components may not be proxied as expected. Check to " +"ensure that %s has been @Import'ed on the same class where these " +"annotations are declared; otherwise remove the import of %s " +"altogether.", name, name, name));}}

该方法用于注册Bean定义。主要功能如下:

  1. 遍历类元数据中的所有注解类型。
  2. 检查每个注解是否包含mode和proxyTargetClass属性,并判断其类型是否正确。
  3. 如果属性类型正确且mode为AdviceMode.PROXY,则根据proxyTargetClass值配置自动代理创建器。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

下面这是@EnableCaching里的mode值和proxyTargetClass值:
在这里插入图片描述
下面这个是@Configration注解里的mode值和proxyTargetClass的值,可以看到是null
在这里插入图片描述
友友们,这两个值是从哪来的呢?看这里:
在这里插入图片描述
其实就是注解里定义的呀。那么问题来了,这两个值是干嘛的,什么情况下要定义这两个值?

拿@EnableCaching 里的这两个值打个比方。它提供了几个属性来控制缓存的行为:

  1. mode():此属性决定了 Spring AOP(面向切面编程)使用的模式。它可以取两个值:
      AdviceMode.PROXY:表示使用代理模式实现AOP,这是默认值。
      AdviceMode.ASPECTJ:表示使用 AspectJ 模式实现AOP。
  2. proxyTargetClass():此属性仅在 mode 设置为 AdviceMode.PROXY 时有效。它指定了是否使用 CGLIB 代理(true)而不是 >JDK 动态代理(false,默认)。CGLIB 代理可以代理不可代理的类(即没有实现接口的类),而 JDK 动态代理只能代理实现了接口的类。

这些属性帮助开发者根据应用的需求选择合适的代理机制,从而更灵活地管理缓存逻辑。

拓展:AutoProxyRegistrar里关键代码分析

1.3.1.1 AutoProxyRegistrar.registerBeanDefinitions()方法总结

总结:AutoProxyRegistrar.registerBeanDefinitions()往容器中注入了一个类InfrastructureAdvisorAutoProxyCreator。

这段是不是很熟悉,没错,声明式事务中注入的也是这个InfrastructureAdvisorAutoProxyCreator类,用来生成代理对象。

1.3.2 ProxyCachingConfiguration

ProxyCachingConfiguration向Spring容器中注入了三个Bean:

  1. BeanFactoryCacheOperationSourceAdvisor:切面,用来匹配目标方法和目标类,看那些方法需要实现缓存的增强;
  2. CacheOperationSource:用来解析缓存相关的注解;
  3. CacheInterceptor:实现了Advice,是一个通知,实现对目标方法的增强。
@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.<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;}}

ProxyCachingConfiguration继承自AbstractCachingConfiguration,AbstractCachingConfiguration是抽象基础配置类,为启用Spring的注解驱动的缓存管理功能提供通用结构;代码如下:
在这里插入图片描述

1.3.2.1 BeanFactoryCacheOperationSourceAdvisor

BeanFactoryCacheOperationSourceAdvisor是一个切面,切面需要包括通知和切点:

  1. Advice通知:CacheInterceptor,由ProxyCachingConfiguration注入;
  2. Pointcut切点:内部CacheOperationSourcePointcut类型的属性;
1.3.2.2 CacheOperationSourcePointcut

Pointcut主要包含两部分,对目标类的匹配,对目标方法的匹配。
对目标类的匹配,其实啥也没干,返回true,主要逻辑在对目标方法的匹配上,大部分Pointcut都是如此:

private class CacheOperationSourceClassFilter implements ClassFilter {@Overridepublic boolean matches(Class<?> clazz) {if (CacheManager.class.isAssignableFrom(clazz)) {return false;}// 啥也没干CacheOperationSource cas = getCacheOperationSource();return (cas == null || cas.isCandidateClass(clazz));}
}

对目标方法的匹配:

public boolean matches(Method method, Class<?> targetClass) {CacheOperationSource cas = getCacheOperationSource();/*** @see AbstractFallbackCacheOperationSource#getCacheOperations(java.lang.reflect.Method, java.lang.Class)*/// 就是看目标方法上面有没有缓存注解return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}

这里会委托给CacheOperationSource来解析缓存注解,这个类是在ProxyCachingConfiguration中注入的。

1.3.3 org.springframework.cache.jcache.config.ProxyJCacheConfiguration

@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.<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;}}

ProxyCachingConfiguration向Spring容器中注入了三个Bean:

  1. BeanFactoryCacheOperationSourceAdvisor:切面,用来匹配目标方法和目标类,看那些方法需要实现缓存的增强;
  2. CacheOperationSource:用来解析缓存相关的注解;
  3. CacheInterceptor:实现了Advice,是一个通知,实现对目标方法的增强。
1.3.3.1 BeanFactoryCacheOperationSourceAdvisor

BeanFactoryCacheOperationSourceAdvisor是一个切面,切面需要包括通知和切点:

  • Advice通知:CacheInterceptor,由ProxyCachingConfiguration注入;
  • Pointcut切点:内部CacheOperationSourcePointcut类型的属性;
1.3.3.2 CacheOperationSourcePointcut

Pointcut主要包含两部分,对目标类的匹配,对目标方法的匹配。

对目标类的匹配,其实啥也没干,返回true,主要逻辑在对目标方法的匹配上,大部分Pointcut都是如此:
在这里插入图片描述

abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {@Overridepublic boolean matches(Method method, Class<?> targetClass) {if (CacheManager.class.isAssignableFrom(targetClass)) {return false;}// 就是看目标方法上面有没有缓存注解CacheOperationSource cas = getCacheOperationSource();return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));}
}

这里会委托给CacheOperationSource来解析缓存注解,这个类是在ProxyCachingConfiguration中注入的。

最终会调用到org.springframework.cache.annotation.SpringCacheAnnotationParser#parseCacheAnnotations(org.springframework.cache.annotation.SpringCacheAnnotationParser.DefaultCacheConfig, java.lang.reflect.AnnotatedElement, boolean)

private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {Collection<? extends Annotation> anns = (localOnly ?AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));if (anns.isEmpty()) {return null;}// @Cacheable解析为CacheableOperation// @CacheEvict解析为CacheEvictOperation// @CachePut解析为CachePutOperationfinal Collection<CacheOperation> ops = new ArrayList<>(1);anns.stream().filter(ann -> ann instanceof Cacheable).forEach(ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));anns.stream().filter(ann -> ann instanceof CachePut).forEach(ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));anns.stream().filter(ann -> ann instanceof Caching).forEach(ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));return ops;
}
1.3.3.3 CacheInterceptor

CacheInterceptor是一个Advice,用来实现对目标方法的增强,当调用目标方法时会先进入CacheInterceptor.invoke()方法:

org.springframework.cache.interceptor.CacheInterceptor#invoke

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();}}}

org.springframework.cache.interceptor.CacheAspectSupport#execute(org.springframework.cache.interceptor.CacheOperationInvoker, java.lang.reflect.Method, org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContexts)

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {// Special handling of synchronized invocationif (contexts.isSynchronized()) { // false不会进入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, handleSynchronizedGet(invoker, key, cache));}catch (Cache.ValueRetrievalException ex) {// Directly propagate ThrowableWrapper from the invoker,// or potentially also an IllegalArgumentException etc.ReflectionUtils.rethrowRuntimeException(ex.getCause());}}else {// No caching required, only call the underlying methodreturn invokeOperation(invoker);}}// Process any early evictions// 先调用@CacheEvict注解中的属性beforeInvocation=true的,实际上这个值默认为false,这里一般不会干啥,看后面的调用// cache.evict()processCacheEvicts(contexts.get(CacheEvictOperation.class), true,CacheOperationExpressionEvaluator.NO_RESULT);// Check if we have a cached item matching the conditions// cache.get()Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));// Collect puts from any @Cacheable miss, if no cached item is foundList<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 hitcacheValue = cacheHit.get();returnValue = wrapCacheValue(method, cacheValue);}else {// Invoke the method if we don't have a cache hit// 调用目标方法returnValue = invokeOperation(invoker);cacheValue = unwrapReturnValue(returnValue);}// Collect any explicit @CachePutscollectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);// Process any collected put requests, either from @CachePut or a @Cacheable missfor (CachePutRequest cachePutRequest : cachePutRequests) {cachePutRequest.apply(cacheValue);}// Process any late evictions// 调用@CacheEvict注解中的属性beforeInvocation=false的processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);return returnValue;
}

1.4 扩展缓存注解

有时候由于业务需要或者Spring提供的缓存不满足我们的要求,如无法解决缓存雪崩问题,扩展步骤如下:

  1. 实现CacheManager接口或继承AbstractCacheManager,管理自身的cache实例,也可以直接使用内置的SimpleCacheManager;
  2. 实现Cache接口,自定义缓存实现逻辑;
  3. 将自定义的Cache和CacheManager进行关联并注入到Spring容器中。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Vue 中实现视频播放的艺术
  • git使用手册
  • 离线安装NuGet组件方法
  • 大学生租房平台:SpringBoot技术实现详解
  • Anthropic 的 Claude AI 如何可能超过 OpenAI 的 ChatGPT?
  • 网络拓扑结构介绍
  • C++继承问题
  • three.js线框模式
  • 如果 Google 解雇 Go 团队怎么办?
  • 信息架构的战略视角:驱动数字化转型的设计原则与实践创新
  • Spring事务失效场景及解决方法
  • MATLAB实现PID参数自动整定
  • MATLAB求解0-1线性规划问题的详细分析
  • FFmpeg的日志系统(ubuntu 环境)
  • pgAdmin4使用
  • E-HPC支持多队列管理和自动伸缩
  • Fundebug计费标准解释:事件数是如何定义的?
  • gops —— Go 程序诊断分析工具
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • PHP面试之三:MySQL数据库
  • Python 基础起步 (十) 什么叫函数?
  • Redis字符串类型内部编码剖析
  • select2 取值 遍历 设置默认值
  • vue.js框架原理浅析
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 阿里云Kubernetes容器服务上体验Knative
  • 程序员该如何有效的找工作?
  • 给初学者:JavaScript 中数组操作注意点
  • 力扣(LeetCode)56
  • 排序算法学习笔记
  • 前端攻城师
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 使用API自动生成工具优化前端工作流
  • 双管齐下,VMware的容器新战略
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • ​人工智能书单(数学基础篇)
  • ​探讨元宇宙和VR虚拟现实之间的区别​
  • #systemverilog# 之 event region 和 timeslot 仿真调度(十)高层次视角看仿真调度事件的发生
  • $LayoutParams cannot be cast to android.widget.RelativeLayout$LayoutParams
  • (1)Jupyter Notebook 下载及安装
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (10)Linux冯诺依曼结构操作系统的再次理解
  • (12)目标检测_SSD基于pytorch搭建代码
  • (160)时序收敛--->(10)时序收敛十
  • (2022 CVPR) Unbiased Teacher v2
  • (day 2)JavaScript学习笔记(基础之变量、常量和注释)
  • (function(){})()的分步解析
  • (Redis使用系列) SpringBoot中Redis的RedisConfig 二
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (五)关系数据库标准语言SQL
  • (转)iOS字体
  • (状压dp)uva 10817 Headmaster's Headache