SpringCache框架加载/拦截原理详解
官网文档
背景
项目A中需要多数据源的实现,比如UserDao.getAllUserList()需要从readonly库中读取,但是UserDao.insert()需要插入主(写)库
就需要在dao层的方法调用上面添加注解!
了解后知道-接口通过jdk代理(mybatis的mapper接口就是通过jdk代理动态生成的->MapperFactoryBean.class)的,没办法被aop的拦截(注解配置的拦截)
//dao @Pointcut("@annotation(com.kaola.cs.data.common.aspect.DataSourceSelect)") publicvoiddao(){ }
然后碰巧接触了项目B,使用了SpringCache模块,但是Spring的Cache模块居然能够拦截(spring-cache也是通过注解拦截!!!)
引起了我的兴趣,就把源码翻了一遍
SpringCache的用途
与mybatis对比
1. spring-cache是基于spring的方法级别的,也就是说你方法做了啥不关心,它只负责缓存方法结果
mybatis的缓存(CachingExecutor/BaseExecutor)是基于数据库查询结果的缓存
2. spring-cache可以配置各种类型的缓存介质(redis,ehcache,hashmap,甚至db等等)->它仅仅是提供接口和默认实现,可以自己拓展
mybatis的缓存是hashmap,单一!!lowb
SpringCache的配置
1.注解(spring-boot)2.xml配置
这里只讲注解,但是初始化的类都是一样的!!!
定义CacheConfigure.java就能直接使用
@EnableCaching @Configuration publicclassCacheConfigureextendsCachingConfigurerSupport{ @Override @Bean publicCacheManagercacheManager(){ SimpleCacheManagerresult=newSimpleCacheManager(); Listcaches=newArrayList<>(); caches.add(newConcurrentMapCache("testCache")); result.setCaches(caches); returnresult; } @Override @Bean publicCacheErrorHandlererrorHandler(){ returnnewSimpleCacheErrorHandler(); } }
通过@EnableCaching注解可以找到Spring-Cache初始化的核心类
ProxyCachingConfiguration.java
@Configuration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) publicclassProxyCachingConfigurationextendsAbstractCachingConfiguration{ @Bean(name=CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) publicBeanFactoryCacheOperationSourceAdvisorcacheAdvisor(){ BeanFactoryCacheOperationSourceAdvisoradvisor=newBeanFactoryCacheOperationSourceAdvisor(); advisor.setCacheOperationSource(cacheOperationSource()); advisor.setAdvice(cacheInterceptor()); if(this.enableCaching!=null){ advisor.setOrder(this.enableCaching.getNumber("order")); } returnadvisor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) publicCacheOperationSourcecacheOperationSource(){ returnnewAnnotationCacheOperationSource(); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) publicCacheInterceptorcacheInterceptor(){ CacheInterceptorinterceptor=newCacheInterceptor(); interceptor.configure(this.errorHandler,this.keyGenerator,this.cacheResolver,this.cacheManager); interceptor.setCacheOperationSource(cacheOperationSource()); returninterceptor; } }
通过注解,把3个类的bean实例化:BeanFactoryCacheOperationSourceAdvisor、CacheOperationSource、CacheInterceptor
说一下这3个类的作用
BeanFactoryCacheOperationSourceAdvisor.java
/* BeanFactoryCacheOperationSourceAdvisor继承了AbstractBeanFactoryPointcutAdvisor 在spring中的效果就是,在每个bean的初始化时(每个bean都会被加载成advised对象->有targetSource和Advisor[]数组) 每个bean被调用方法的时候都是先遍历advisor的方法,然后在调用原生bean(也就是targetSource)的方法,实现了aop的效果 bean加载的时候BeanFactoryCacheOperationSourceAdvisor的getPointcut()->也就是CacheOperationSourcePointcut就会被获取,然后调用 CacheOperationSourcePointcut.matches()方法,用来匹配对应的bean 假设bean在BeanFactoryCacheOperationSourceAdvisor的扫描中matchs()方法返回了true 结果就是 在每个bean的方法被调用的时候CacheInterceptor中的invoke()方法就会被调用 总结: spring-cache也完成了aop一样的实现(spring-aop也是这样做的) 重点就是在CacheOperationSourcePointcut.matchs()方法中,怎么匹配接口的了这里先不说后面具体介绍!!!! */ publicclassBeanFactoryCacheOperationSourceAdvisorextendsAbstractBeanFactoryPointcutAdvisor{ @Nullable privateCacheOperationSourcecacheOperationSource; privatefinalCacheOperationSourcePointcutpointcut=newCacheOperationSourcePointcut(){ @Override @Nullable protectedCacheOperationSourcegetCacheOperationSource(){ returncacheOperationSource; } }; /** *Setthecacheoperationattributesourcewhichisusedtofindcache *attributes.Thisshouldusuallybeidenticaltothesourcereference *setonthecacheinterceptoritself. */ publicvoidsetCacheOperationSource(CacheOperationSourcecacheOperationSource){ this.cacheOperationSource=cacheOperationSource; } /** *Setthe{@linkClassFilter}touseforthispointcut. *Defaultis{@linkClassFilter#TRUE}. */ publicvoidsetClassFilter(ClassFilterclassFilter){ this.pointcut.setClassFilter(classFilter); } @Override publicPointcutgetPointcut(){ returnthis.pointcut; } }
CacheOperationSource.java是个接口
实现类是->AnnotationCacheOperationSource.java重点是父类->AbstractFallbackCacheOperationSource.java
讲解一下:
代码量很少,主要是attributeCache的封装使用,通过把method-CacheOperation
然后在CacheInterceptor.invoke()的时候通过invocation获取到method-class然后调用CacheOperationSource.getCacheOperations()获取到CacheOperation
CacheOperation其实就是触发对应spring-cache注解的操作-获取缓存的实现了
publicabstractclassAbstractFallbackCacheOperationSourceimplementsCacheOperationSource{ /** *Canonicalvalueheldincachetoindicatenocachingattributewas *foundforthismethodandwedon'tneedtolookagain. */ privatestaticfinalCollectionNULL_CACHING_ATTRIBUTE=Collections.emptyList(); /** *Loggeravailabletosubclasses. * AsthisbaseclassisnotmarkedSerializable,theloggerwillberecreated *afterserialization-providedthattheconcretesubclassisSerializable. */ protectedfinalLoglogger=LogFactory.getLog(getClass()); /** *CacheofCacheOperations,keyedbymethodonaspecifictargetclass. *
AsthisbaseclassisnotmarkedSerializable,thecachewillberecreated *afterserialization-providedthattheconcretesubclassisSerializable. */ privatefinalMap
!!!! CacheOperationSourcePointcut.java的matchs()方法
用来判断类是不是符合spring-cache拦截条件也就是@Cachable@CachePut等等的注解怎么识别的地方
经过跟踪代码发现是AnnotationCacheOperationSource.findCacheOperations()调用的
省略部分代码....
publicclassAnnotationCacheOperationSourceextendsAbstractFallbackCacheOperationSourceimplementsSerializable{ privatefinalSetannotationParsers; @Override @Nullable protectedCollection findCacheOperations(Class>clazz){ returndetermineCacheOperations(parser->parser.parseCacheAnnotations(clazz)); } @Override @Nullable protectedCollection findCacheOperations(Methodmethod){ returndetermineCacheOperations(parser->parser.parseCacheAnnotations(method)); } /** *Determinethecacheoperation(s)forthegiven{@linkCacheOperationProvider}. * Thisimplementationdelegatestoconfigured *{@linkCacheAnnotationParserCacheAnnotationParsers} *forparsingknownannotationsintoSpring'smetadataattributeclass. *
Canbeoverriddentosupportcustomannotationsthatcarrycachingmetadata. *@paramproviderthecacheoperationprovidertouse *@returntheconfiguredcachingoperations,or{@codenull}ifnonefound */ @Nullable protectedCollection
determineCacheOperations(CacheOperationProviderprovider){ Collection ops=null; for(CacheAnnotationParserannotationParser:this.annotationParsers){ Collection annOps=provider.getCacheOperations(annotationParser); if(annOps!=null){ if(ops==null){ ops=annOps; } else{ Collection combined=newArrayList<>(ops.size()+annOps.size()); combined.addAll(ops); combined.addAll(annOps); ops=combined; } } } returnops; } }
然后就是注解的解析方法SpringCacheAnnotationParser.java
代码很简单-就不多说了
@Nullable privateCollectionparseCacheAnnotations( DefaultCacheConfigcachingConfig,AnnotatedElementae,booleanlocalOnly){ Collectionanns=(localOnly? AnnotatedElementUtils.getAllMergedAnnotations(ae,CACHE_OPERATION_ANNOTATIONS): AnnotatedElementUtils.findAllMergedAnnotations(ae,CACHE_OPERATION_ANNOTATIONS)); if(anns.isEmpty()){ returnnull; } finalCollection ops=newArrayList<>(1); anns.stream().filter(ann->anninstanceofCacheable).forEach( ann->ops.add(parseCacheableAnnotation(ae,cachingConfig,(Cacheable)ann))); anns.stream().filter(ann->anninstanceofCacheEvict).forEach( ann->ops.add(parseEvictAnnotation(ae,cachingConfig,(CacheEvict)ann))); anns.stream().filter(ann->anninstanceofCachePut).forEach( ann->ops.add(parsePutAnnotation(ae,cachingConfig,(CachePut)ann))); anns.stream().filter(ann->anninstanceofCaching).forEach( ann->parseCachingAnnotation(ae,cachingConfig,(Caching)ann,ops)); returnops; }
总结
1.spring-cache实现了AbstractBeanFactoryPointcutAdvisor提供CacheOperationSourcePointcut(PointCut)作切点判断,提供CacheInterceptor(MethodInterceptor)作方法拦截
2.spring-cache提供CacheOperationSource作为method对应CacheOperation(缓存操作)的查询和加载
3.spring-cache通过SpringCacheAnnotationParser来解析自己定义的@Cacheable@CacheEvict@Caching等注解类
所以spring-cache不使用aspectj的方式,通过CacheOperationSource.getCacheOperations()方式可以使jdk代理的类也能匹配到
jdk代理的类的匹配
代码类在CacheOperationSource.getCacheOperations()
重点在于targetClass和method,如果是对应的dao.xxx()就能matchs()并且拦截
CacheInterceptor->CacheAspectSupport.execute()方法
//代码自己看吧。也很简单->结果就是spring-cache也可以拦截到mybatis的dao层接口,进行缓存 @Nullable protectedObjectexecute(CacheOperationInvokerinvoker,Objecttarget,Methodmethod,Object[]args){ //Checkwhetheraspectisenabled(tocopewithcaseswheretheAJispulledinautomatically) if(this.initialized){ Class>targetClass=getTargetClass(target); CacheOperationSourcecacheOperationSource=getCacheOperationSource(); if(cacheOperationSource!=null){ Collectionoperations=cacheOperationSource.getCacheOperations(method,targetClass); if(!CollectionUtils.isEmpty(operations)){ returnexecute(invoker,method, newCacheOperationContexts(operations,method,args,target,targetClass)); } } } returninvoker.invoke(); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。