扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
这篇文章主要介绍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)") public void dao() { }
然后碰巧接触了项目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 public class CacheConfigure extends CachingConfigurerSupport { @Override @Bean public CacheManager cacheManager() { SimpleCacheManager result = new SimpleCacheManager(); Listcaches = new ArrayList<>(); caches.add(new ConcurrentMapCache("testCache")); result.setCaches(caches); return result; } @Override @Bean public CacheErrorHandler errorHandler() { return new SimpleCacheErrorHandler(); } }
通过 @EnableCaching 注解可以找到 Spring-Cache 初始化的核心类
ProxyCachingConfiguration.java
@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; } }
通过注解,把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() 方法中,怎么匹配接口的了 这里先不说后面具体介绍!!!! */ public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { @Nullable private CacheOperationSource cacheOperationSource; private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() { @Override @Nullable protected CacheOperationSource getCacheOperationSource() { return cacheOperationSource; } }; /** * Set the cache operation attribute source which is used to find cache * attributes. This should usually be identical to the source reference * set on the cache interceptor itself. */ public void setCacheOperationSource(CacheOperationSource cacheOperationSource) { this.cacheOperationSource = cacheOperationSource; } /** * Set the {@link ClassFilter} to use for this pointcut. * Default is {@link ClassFilter#TRUE}. */ public void setClassFilter(ClassFilter classFilter) { this.pointcut.setClassFilter(classFilter); } @Override public Pointcut getPointcut() { return this.pointcut; } }
CacheOperationSource.java 是个接口
实现类是 -> AnnotationCacheOperationSource.java 重点是父类 -> AbstractFallbackCacheOperationSource.java
讲解一下:
代码量很少,主要是 attributeCache 的封装使用,通过把 method - CacheOperation
然后在 CacheInterceptor.invoke() 的时候通过invocation 获取到 method-class 然后调用CacheOperationSource.getCacheOperations() 获取到 CacheOperation
CacheOperation 其实就是触发对应spring-cache 注解的操作-获取缓存的实现了
public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource { /** * Canonical value held in cache to indicate no caching attribute was * found for this method and we don't need to look again. */ private static final CollectionNULL_CACHING_ATTRIBUTE = Collections.emptyList(); /** * Logger available to subclasses. * As this base class is not marked Serializable, the logger will be recreated * after serialization - provided that the concrete subclass is Serializable. */ protected final Log logger = LogFactory.getLog(getClass()); /** * Cache of CacheOperations, keyed by method on a specific target class. *
As this base class is not marked Serializable, the cache will be recreated * after serialization - provided that the concrete subclass is Serializable. */ private final Map
!!!! CacheOperationSourcePointcut.java 的 matchs() 方法
用来判断类是不是符合spring-cache 拦截条件 也就是 @Cachable @CachePut 等等的注解怎么识别的地方
经过跟踪代码发现是 AnnotationCacheOperationSource.findCacheOperations() 调用的
省略部分代码....
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable { private final SetannotationParsers; @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)); } /** * Determine the cache operation(s) for the given {@link CacheOperationProvider}. * This implementation delegates to configured * {@link CacheAnnotationParser CacheAnnotationParsers} * for parsing known annotations into Spring's metadata attribute class. *
Can be overridden to support custom annotations that carry caching metadata. * @param provider the cache operation provider to use * @return the configured caching operations, or {@code null} if none found */ @Nullable 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; } }
然后就是注解的解析方法 SpringCacheAnnotationParser.java
代码很简单-就不多说了
@Nullable private CollectionparseCacheAnnotations( 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; } final Collection 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.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 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) { Collectionoperations = cacheOperationSource.getCacheOperations(method, targetClass); if (!CollectionUtils.isEmpty(operations)) { return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass)); } } } return invoker.invoke(); }
以上是“SpringCache框架加载/拦截原理的示例分析”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注创新互联行业资讯频道!
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流