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
protectedCollectionfindCacheOperations(Class>clazz){
returndetermineCacheOperations(parser->parser.parseCacheAnnotations(clazz));
}
@Override
@Nullable
protectedCollectionfindCacheOperations(Methodmethod){
returndetermineCacheOperations(parser->parser.parseCacheAnnotations(method));
}
/**
*Determinethecacheoperation(s)forthegiven{@linkCacheOperationProvider}.
*Thisimplementationdelegatestoconfigured
*{@linkCacheAnnotationParserCacheAnnotationParsers}
*forparsingknownannotationsintoSpring'smetadataattributeclass.
*
Canbeoverriddentosupportcustomannotationsthatcarrycachingmetadata.
*@paramproviderthecacheoperationprovidertouse
*@returntheconfiguredcachingoperations,or{@codenull}ifnonefound
*/
@Nullable
protectedCollectiondetermineCacheOperations(CacheOperationProviderprovider){
Collectionops=null;
for(CacheAnnotationParserannotationParser:this.annotationParsers){
CollectionannOps=provider.getCacheOperations(annotationParser);
if(annOps!=null){
if(ops==null){
ops=annOps;
}
else{
Collectioncombined=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();
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。