SpringCache框架加載/攔截原理詳解
背景
項(xiàng)目A中需要多數(shù)據(jù)源的實(shí)現(xiàn),比如UserDao.getAllUserList() 需要從readonly庫中讀取,但是UserDao.insert() 需要插入主(寫)庫
就需要在dao層的方法調(diào)用上面添加注解!
了解后知道-接口通過jdk代理(mybatis的mapper接口就是通過jdk代理動(dòng)態(tài)生成的-> MapperFactoryBean.class )的,沒辦法被aop的攔截(注解配置的攔截)
//dao @Pointcut("@annotation(com.kaola.cs.data.common.aspect.DataSourceSelect)") public void dao() { }
然后碰巧接觸了項(xiàng)目B,使用了SpringCache模塊,但是Spring的Cache模塊居然能夠攔截(spring-cache也是通過注解攔截?。?!)
引起了我的興趣,就把源碼翻了一遍
SpringCache的用途
與 mybatis 對(duì)比
1. spring-cache 是基于spring的方法級(jí)別的,也就是說你方法做了啥不關(guān)心,它只負(fù)責(zé)緩存方法結(jié)果
mybatis 的緩存(CachingExecutor / BaseExecutor) 是基于數(shù)據(jù)庫查詢結(jié)果的緩存
2. spring-cache 可以配置各種類型的緩存介質(zhì)(redis , ehcache , hashmap, 甚至db等等) -> 它僅僅是提供接口和默認(rèn)實(shí)現(xiàn),可以自己拓展
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(); List<Cache> caches = 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.<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; } }
通過注解,把3個(gè)類的bean 實(shí)例化: BeanFactoryCacheOperationSourceAdvisor 、CacheOperationSource 、CacheInterceptor
說一下這3個(gè)類的作用
BeanFactoryCacheOperationSourceAdvisor.java
/* BeanFactoryCacheOperationSourceAdvisor 繼承了 AbstractBeanFactoryPointcutAdvisor 在spring 中的效果就是,在每個(gè)bean的初始化時(shí) (每個(gè)bean都會(huì)被加載成 advised 對(duì)象 -> 有 targetSource 和 Advisor[] 數(shù)組) 每個(gè)bean被調(diào)用方法的時(shí)候都是先遍歷advisor的方法,然后在調(diào)用原生bean(也就是targetSource)的方法,實(shí)現(xiàn)了aop的效果 bean 加載的時(shí)候 BeanFactoryCacheOperationSourceAdvisor 的 getPointcut()-> 也就是 CacheOperationSourcePointcut 就會(huì)被獲取,然后調(diào)用 CacheOperationSourcePointcut.matches()方法, 用來匹配對(duì)應(yīng)的bean 假設(shè)bean 在 BeanFactoryCacheOperationSourceAdvisor 的掃描中 matchs() 方法返回了true 結(jié)果就是 在每個(gè)bean的方法被調(diào)用的時(shí)候 CacheInterceptor 中的 invoke() 方法就會(huì)被調(diào)用 總結(jié): spring-cache 也完成了aop一樣的實(shí)現(xiàn)(spring-aop也是這樣做的) 重點(diǎn)就是在 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 是個(gè)接口
實(shí)現(xiàn)類是 -> AnnotationCacheOperationSource.java 重點(diǎn)是父類 -> AbstractFallbackCacheOperationSource.java
講解一下:
代碼量很少,主要是 attributeCache 的封裝使用,通過把 method - CacheOperation
然后在 CacheInterceptor.invoke() 的時(shí)候通過invocation 獲取到 method-class 然后調(diào)用CacheOperationSource.getCacheOperations() 獲取到 CacheOperation
CacheOperation 其實(shí)就是觸發(fā)對(duì)應(yīng)spring-cache 注解的操作-獲取緩存的實(shí)現(xiàn)了
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 Collection<CacheOperation> NULL_CACHING_ATTRIBUTE = Collections.emptyList(); /** * Logger available to subclasses. * <p>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. * <p>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<Object, Collection<CacheOperation>> attributeCache = new ConcurrentHashMap<>(1024); /** * Determine the caching attribute for this method invocation. * <p>Defaults to the class's caching attribute if no method attribute is found. * @param method the method for the current invocation (never {@code null}) * @param targetClass the target class for this invocation (may be {@code null}) * @return {@link CacheOperation} for this method, or {@code null} if the method * is not cacheable */ @Override @Nullable public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) { if (method.getDeclaringClass() == Object.class) { return null; } Object cacheKey = getCacheKey(method, targetClass); Collection<CacheOperation> cached = this.attributeCache.get(cacheKey); if (cached != null) { return (cached != NULL_CACHING_ATTRIBUTE ? cached : null); } else { Collection<CacheOperation> 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; } } /** * Determine a cache key for the given method and target class. * <p>Must not produce same key for overloaded methods. * Must produce same key for different instances of the same method. * @param method the method (never {@code null}) * @param targetClass the target class (may be {@code null}) * @return the cache key (never {@code null}) */ protected Object getCacheKey(Method method, @Nullable Class<?> targetClass) { return new MethodClassKey(method, targetClass); } @Nullable private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } // The method may be on an interface, but we need attributes from the target class. // If the target class is null, the method will be unchanged. Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); // First try is the method in the target class. Collection<CacheOperation> opDef = findCacheOperations(specificMethod); if (opDef != null) { return opDef; } // Second try is the caching operation on the target class. opDef = findCacheOperations(specificMethod.getDeclaringClass()); if (opDef != null && ClassUtils.isUserLevelMethod(method)) { return opDef; } if (specificMethod != method) { // Fallback is to look at the original method. opDef = findCacheOperations(method); if (opDef != null) { return opDef; } // Last fallback is the class of the original method. opDef = findCacheOperations(method.getDeclaringClass()); if (opDef != null && ClassUtils.isUserLevelMethod(method)) { return opDef; } } return null; } /** * Subclasses need to implement this to return the caching attribute for the * given class, if any. * @param clazz the class to retrieve the attribute for * @return all caching attribute associated with this class, or {@code null} if none */ @Nullable protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz); /** * Subclasses need to implement this to return the caching attribute for the * given method, if any. * @param method the method to retrieve the attribute for * @return all caching attribute associated with this method, or {@code null} if none */ @Nullable protected abstract Collection<CacheOperation> findCacheOperations(Method method); /** * Should only public methods be allowed to have caching semantics? * <p>The default implementation returns {@code false}. */ protected boolean allowPublicMethodsOnly() { return false; } }
!?。?! CacheOperationSourcePointcut.java 的 matchs() 方法
用來判斷類是不是符合spring-cache 攔截條件 也就是 @Cachable @CachePut 等等的注解怎么識(shí)別的地方
經(jīng)過跟蹤代碼發(fā)現(xiàn)是 AnnotationCacheOperationSource.findCacheOperations() 調(diào)用的
省略部分代碼....
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable { private final Set<CacheAnnotationParser> annotationParsers; @Override @Nullable protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) { return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz)); } @Override @Nullable protected Collection<CacheOperation> findCacheOperations(Method method) { return determineCacheOperations(parser -> parser.parseCacheAnnotations(method)); } /** * Determine the cache operation(s) for the given {@link CacheOperationProvider}. * <p>This implementation delegates to configured * {@link CacheAnnotationParser CacheAnnotationParsers} * for parsing known annotations into Spring's metadata attribute class. * <p>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<CacheOperation> determineCacheOperations(CacheOperationProvider provider) { Collection<CacheOperation> ops = null; for (CacheAnnotationParser annotationParser : this.annotationParsers) { Collection<CacheOperation> annOps = provider.getCacheOperations(annotationParser); if (annOps != null) { if (ops == null) { ops = annOps; } else { Collection<CacheOperation> combined = new ArrayList<>(ops.size() + annOps.size()); combined.addAll(ops); combined.addAll(annOps); ops = combined; } } } return ops; } }
然后就是注解的解析方法 SpringCacheAnnotationParser.java
代碼很簡(jiǎn)單-就不多說了
@Nullable 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; } final 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; }
總結(jié)
1.spring-cache 實(shí)現(xiàn)了 AbstractBeanFactoryPointcutAdvisor 提供 CacheOperationSourcePointcut (PointCut) 作切點(diǎn)判斷,提供 CacheInterceptor (MethodInterceptor) 作方法攔截
2.spring-cache 提供 CacheOperationSource 作為 method 對(duì)應(yīng) CacheOperation(緩存操作) 的查詢和加載
3.spring-cache 通過 SpringCacheAnnotationParser 來解析自己定義的 @Cacheable @CacheEvict @Caching 等注解類
所以 spring-cache 不使用 aspectj 的方式,通過 CacheOperationSource.getCacheOperations() 方式可以使jdk代理的類也能匹配到
jdk代理的類的匹配
代碼類在 CacheOperationSource.getCacheOperations()
重點(diǎn)在于 targetClass 和 method ,如果是對(duì)應(yīng)的 dao.xxx() 就能matchs() 并且攔截
CacheInterceptor -> CacheAspectSupport.execute() 方法
// 代碼自己看吧。也很簡(jiǎn)單 -> 結(jié)果就是spring-cache 也可以攔截到mybatis的dao層接口,進(jìn)行緩存 @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) { Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass); if (!CollectionUtils.isEmpty(operations)) { return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass)); } } } return invoker.invoke(); }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用mybatis-plus的insert方法遇到的問題及解決方法(添加時(shí)id值不存在異常)
這篇文章主要介紹了使用mybatis-plus的insert方法遇到的問題及解決方法(添加時(shí)id值不存在異常),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08Java創(chuàng)建student類詳細(xì)代碼例子
這篇文章主要給大家介紹了關(guān)于Java創(chuàng)建student類的相關(guān)資料,學(xué)生類(Student)是一種面向?qū)ο蟮木幊谈拍?其主要用于描述學(xué)生的屬性和行為,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11Spring配置中transactionAttributes的使用方法介紹
這篇文章主要介紹了Spring配置中transactionAttributes的使用方法介紹的相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。2017-09-09java常用數(shù)據(jù)流應(yīng)用實(shí)例解析
這篇文章主要介紹了java常用數(shù)據(jù)流應(yīng)用實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12