最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Cache原理解析

 更新時間:2024年05月20日 15:06:52   作者:買斷  
Spring Cache是一個框架,它提供了基于注解的緩存功能,使得開發(fā)者可以很方便地將緩存集成到他們的應用程序中,這篇文章主要介紹了Spring Cache原理解析,需要的朋友可以參考下

一. 各個注解回顧

在看 Spring Cache 源碼時,我們先看下回顧一下幾個重要的注解;

1. @Cacheable

在方法執(zhí)行前查看是否有緩存對應的數(shù)據(jù),如果有直接返回數(shù)據(jù),如果沒有則調用方法獲取數(shù)據(jù)返回,并緩存起來;

  • unless屬性:條件符合則不緩存,不符合則緩存;挺容易搞混的;
  • sync屬性:是否使用異步,默認是 false;在一個多線程的環(huán)境中,某些操作可能被相同的參數(shù)并發(fā)地調用,同一個 value 值可能被多次計算(或多次訪問 db),這樣就達不到緩存的目的。針對這些可能高并發(fā)的操作,我們可以使用 sync 參數(shù)來告訴底層的緩存提供者將緩存的入口鎖住,這樣就只能有一個線程計算操作的結果值,而其它線程需要等待。當值為true,相當于同步可以有效的避免緩存擊穿的問題;
@Cacheable(cacheNames = "cache1", key = "#id")
public User getUserById(int id) {
    User user = userMapper.selectById(id);
    log.info("getUserById: {}", user);
    return user;
}

2. @CachePut

@CachePut ,在每次執(zhí)行方法時,將執(zhí)行結果以鍵值對的形式存入指定緩存中;

@CachePut(cacheNames = "cache1", key = "#user.id", unless = "#result == null")
public User updateUser(User user) {
    int count = userMapper.updateUser(user);
    if (count == 0) {
        log.info("update failed");
        return null;
    }
    return user;
}

3. @CacheEvict

@CacheEvict,Evict,表示驅逐,會在調用方法時從緩存中移除已存儲的數(shù)據(jù);

@CacheEvict(cacheNames = "cache1", key = "#id")
public int deleteUserById(int id) {
    int result = userMapper.deleteUserById(id);
    log.info("delete result: {}", result);
    return result;
}

@CacheEvict 中有一個屬性 beforeInvocation:是否在方法執(zhí)行前就清空,默認是 false,表示在方法執(zhí)行后清空緩存;

清除操作默認是在對應方法成功執(zhí)行之后觸發(fā)的,即方法如果因為拋出異常而未能成功返回時不會觸發(fā)清除操作。使用beforeInvocation = true 可以改變觸發(fā)清除操作的時間,當我們指定該屬性值為 true 時,SpringCache 會在調用該方法之前清除緩存中的指定元素;

4. @Caching

可以在一個方法或者類上同時指定多個 SpringCache 相關的注解;

  • 默認情況下不允許同時兩個 @CachePut 作用在方法上,此時需要用到 @Caching 進行組合;
  • @CachePut 和 @Cacheable 和 CacheEvict 三者是可以同時作用在方法上的;
@Caching(
    put = {@CachePut(cacheNames = "cache1", key = "#user.id", unless = "#result == null"),
        @CachePut(cacheNames = "cache1", key = "#user.username", unless = "#result == null")}
)
public User updateUser(User user) {
    int count = userMapper.updateUser(user);
    if (count == 0) {
        log.info("update failed");
        return null;
    }
    return user;
}

二. Spring Cache

Spring Cache 注解對應的 PointcutAdvsior 是 BeanFactoryCacheOperationSourceAdvisor,我們主要看它的 Advice,它的 Advice 是 CacheInterceptor;

CacheInterceptor 的處理邏輯和事務相關的 TransactionInterceptor 非常類似,下面我們對 CacheInterceptor 進行簡單分析;

1. CacheInterceptor

CacheInterceptor 是一個 Advice,它實現(xiàn)了 MethodInterceptor 接口,我們主要看它作為一個 MethodInterceptor 的 invoke() 邏輯;

// ----------------------------------- CacheInterceptor -------------------------------------
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor {
    @Override
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        // 采用函數(shù)對象的方式,把該函數(shù)對象傳給父類的 execute() 執(zhí)行
        // 最終還是執(zhí)行 invocation.proceed()
        CacheOperationInvoker aopAllianceInvoker = () -> {
            try {
                return invocation.proceed();
            }
            catch (Throwable ex) {
                // 這里對異常做了一次封裝,并拋出此異常
                throw new CacheOperationInvoker.ThrowableWrapper(ex);
            }
        };
        Object target = invocation.getThis();
        try {
            // 1. 執(zhí)行父類的 execute()
            return execute(aopAllianceInvoker, target, method, invocation.getArguments());
        }
        catch (CacheOperationInvoker.ThrowableWrapper th) {
            // 拋出原始異常
            throw th.getOriginal();
        }
    }
}

主要還是執(zhí)行父類 CacheAspectSupport#execute(),我們看 CacheAspectSupport#execute() 做了啥;

// --------------------------------- CacheAspectSupport -----------------------------------
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
    if (this.initialized) {
        Class<?> targetClass = getTargetClass(target);
        // 1. 獲取 CacheOperationSource
        // 一般我們使用的都是注解類型的,所以會獲取到 AnnotationCacheOperationSource
        // AnnotationCacheOperationSource 內(nèi)部有注解解析器,可以解析得到 CacheOperations
        CacheOperationSource cacheOperationSource = getCacheOperationSource();
        if (cacheOperationSource != null) {
            // 2. 根據(jù) AnnotationCacheOperationSource 解析得到 targetClass#method() 上的 CacheOperations
            // CacheOperation 是一個抽象類,它的三個實現(xiàn)類分別對應 @Cacheable、@CachePut、@CacheEvict
            // CacheableOperation、CachePutOperation、CacheEvictOperation
            // 其實就是解析到目標類目標方法上的注解元信息 CacheOperations
            Collection<CacheOperation> operations = 
                cacheOperationSource.getCacheOperations(method, targetClass);
            if (!CollectionUtils.isEmpty(operations)) {
                // 3. CacheOperations 不為空,將 CacheOperations 等信息聚合為 CacheOperationContexts 對象
                // CacheOperationContexts 類功能很強大,debug 的時候可以點進去看看
                // 每個 CacheOperation 都會對應一個 CacheOperationContext,塞入 CacheOperationContexts
                // 執(zhí)行重載的 execute()
                return execute(invoker, method,
                     new CacheOperationContexts(operations, method, args, target, targetClass));
            }
        }
    }
    return invoker.invoke();
}

我們看它重載的 execute(),這個是核心方法;

// --------------------------------- CacheAspectSupport -----------------------------------
private Object execute(CacheOperationInvoker invoker, 
                       Method method, 
                       CacheOperationContexts contexts) {
    // 1. 如果方法需要 sync 的話,此處在執(zhí)行 cache.get() 時會加鎖
    // 我們可以先不看此處內(nèi)部邏輯
    if (contexts.isSynchronized()) {
        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) {
                ReflectionUtils.rethrowRuntimeException(ex.getCause());
            }
        }
        else {
            return invokeOperation(invoker);
        }
    }
    // 2. 執(zhí)行早期的 CacheEvict
    // 如果 @CacheEvict 的 beforeInvocation 屬性是 true 的話(默認是 false)
    // 會在方法執(zhí)行前就執(zhí)行 CacheEvict
    processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
                       CacheOperationExpressionEvaluator.NO_RESULT);
    // 3. 如果有 CacheableOperation 的話,也就是有 @Cacheable 注解的話
    // 先去緩存中取緩存值,并包裝為 Cache.ValueWrapper 對象
    // 	如果所有的 @Cacheable 都沒有緩存值,cacheHit 將為 null
    // 	反過來說,但凡有一個 @Cacheable 有緩存值,cacheHit 將不為 null
    Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
    // 4. 創(chuàng)建一個 List<CachePutRequest> 集合
    // 如果 cacheHit == null,此時需要把所有 @Cacheable 中對應 key 都執(zhí)行 CachePut 操作
    // 所以這里會收集 CacheableOperation,并作為 CachePutOperation 塞入到 List<CachePutRequest> 中
    List<CachePutRequest> cachePutRequests = new ArrayList<>();
    if (cacheHit == null) {
        collectPutRequests(contexts.get(CacheableOperation.class),
                           CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
    }
    Object cacheValue;
    Object returnValue;
    if (cacheHit != null && !hasCachePut(contexts)) {
        // 5.1 如果緩存中的值不為空,且沒有 @CachePut
        // 顯然返回值就是 cacheValue,并做一定的包裝
        cacheValue = cacheHit.get();
        returnValue = wrapCacheValue(method, cacheValue);
    }
    else {
        // 5.2 其他情況,緩存中的值為空 || 有 @CachePut
        // 執(zhí)行目標方法,返回值作為新的 cacheValue,并做一定的包裝
        // 這里執(zhí)行目標方法時可能會出現(xiàn)異常,出現(xiàn)異常的情況下就不會執(zhí)行 CachePut 和 CacheEvict 操作了?。?!
        returnValue = invokeOperation(invoker);
        cacheValue = unwrapReturnValue(returnValue);
    }
    // 6. 收集顯式聲明的 CachePutOperation,也就是有顯式聲明的 @CachePut
    collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
    // 7. 執(zhí)行 Cache.put(),處理 CachePut 和沒有緩存命中的 Cacheable 
    for (CachePutRequest cachePutRequest : cachePutRequests) {
        cachePutRequest.apply(cacheValue);
    }
    // 8. 處理 @CacheEvict
    // @CacheEvict 的 beforeInvocation 為 false 時,在目標方法執(zhí)行完才執(zhí)行 CacheEvict
    processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
    // 返回值
    return returnValue;
}

我們簡單看一下 processCacheEvicts()、findCachedItem()、cachePutRequest.apply(),他們分別對應于對 @CacheEvict、@Cacheable、@CachePut 的處理邏輯;

1.1 processCacheEvicts()

processCacheEvicts() 用于處理 @CacheEvict 注解;

// --------------------------------- CacheAspectSupport -----------------------------------
private void processCacheEvicts(Collection<CacheOperationContext> contexts, 
    boolean beforeInvocation, Object result) {
    for (CacheOperationContext context : contexts) {
        CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
        if (beforeInvocation == operation.isBeforeInvocation() 
            && isConditionPassing(context, result)) {
            performCacheEvict(context, operation, result);
        }
    }
}
// --------------------------------- CacheAspectSupport -----------------------------------
private void performCacheEvict(CacheOperationContext context, 
    CacheEvictOperation operation, Object result) {
    Object key = null;
    // 1. 針對每個 @CacheEvict 注解中的每個 cache 都執(zhí)行處理
    for (Cache cache : context.getCaches()) {
        if (operation.isCacheWide()) {
            logInvalidating(context, operation, null);
            doClear(cache, operation.isBeforeInvocation());
        }
        else {
            if (key == null) {
                // 2. 生成 cacheKey
                key = generateKey(context, result);
            }
            logInvalidating(context, operation, key);
            // 3. 執(zhí)行 doEvict()
            doEvict(cache, key, operation.isBeforeInvocation());
        }
    }
}

1.2 findCachedItem()

findCachedItem() 用于處理 @Cacheable 注解;

// --------------------------------- CacheAspectSupport -----------------------------------
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
    Object result = CacheOperationExpressionEvaluator.NO_RESULT;
    // 1. 遍歷所有的 CacheOperationContext
    for (CacheOperationContext context : contexts) {
        if (isConditionPassing(context, result)) {
            Object key = generateKey(context, result);
            // 2. 如果命中了一個 @Cacheable 緩存,直接返回緩存值
            Cache.ValueWrapper cached = findInCaches(context, key);
            if (cached != null) {
                return cached;
            }
            else {
                if (logger.isTraceEnabled()) {
                    logger.trace("No cache entry for key in cache(s) " + context.getCacheNames());
                }
            }
        }
    }
    // 3. 都沒命中,返回 null
    return null;
}
// --------------------------------- CacheAspectSupport -----------------------------------
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
    // 遍歷 @Cacheable 中所有的 cache
    // 有一個 cache 有緩存值就返回該緩存值
    for (Cache cache : context.getCaches()) {
        Cache.ValueWrapper wrapper = doGet(cache, key);
        if (wrapper != null) {
            if (logger.isTraceEnabled()) {
                logger.trace("Cache entry for key found in cache '" + cache.getName());
            }
            return wrapper;
        }
    }
    return null;
}

1.3 cachePutRequest.apply()

cachePutRequest.apply() 用于處理 @CachePut 注解;

public void apply(@Nullable Object result) {
    if (this.context.canPutToCache(result)) {
        // 遍歷 @CachePut 中所有的 cache,執(zhí)行 doPut()
        for (Cache cache : this.context.getCaches()) {
            doPut(cache, this.key, result);
        }
    }
}

三. 補充

1. 方法返回值為null,會緩存嗎

會緩存;

如果方法返回值為 null,此時 Cache.ValueWrapper 值如下:由于此圖無法加載暫時不展示。

它是一個 SimpleValueWrapper,value 值是 null;

但是如果將注解改成如下,就不會緩存 null 值;

// result == null 的話不進行緩存
@Cacheable(cacheNames = "demoCache", key = "#id",unless = "#result == null")

2. @Cacheable注解sync=true的效果

在多線程環(huán)境下,某些操作可能使用相同參數(shù)同步調用(相同的key),默認情況下,緩存不鎖定任何資源,可能導致多次計算,對數(shù)據(jù)庫造成訪問壓力。對于這些特定的情況,屬性 sync 可以指示底層將緩存鎖住,使只有一個線程可以進入計算,而其他線程堵塞,直到返回結果更新到緩存中。

3. 注解可以重復標注嗎

不同的注解可以標注多個,且都能生效;相同的注解不行,編譯器會直接報錯;如果需要相同注解標注多個等更復雜的場景,可以使用 @Caching 注解組合注解;

@CachePut(cacheNames = "demoCache", key = "#id") // 不同的注解可以標注多個
//@Cacheable(cacheNames = "demoCache", key = "#id") // 相同注解標注兩個是不行的 因為它并不是@Repeatable的
@Cacheable(cacheNames = "demoCache", key = "#id")
@Override
public Object getFromDB(Integer id) {
    System.out.println("模擬去db查詢~~~" + id);
    return "hello cache...";
}

編譯器會直接報錯,此圖暫時不展示。

到此這篇關于Spring Cache原理的文章就介紹到這了,更多相關Spring Cache原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringBoot+Guacamole實現(xiàn)遠程桌面教程

    SpringBoot+Guacamole實現(xiàn)遠程桌面教程

    文章討論了坐席遠程幫辦的技術解決方案,比較了VNC和RDP兩種協(xié)議的優(yōu)缺點,最終選擇了基于RDP的Guacamole框架,文中詳細介紹了Guacamole的部署方法和如何通過Springboot集成,同時也提供了一些關于遠程桌面技術的基礎知識和實際應用的建議
    2025-10-10
  • Java解析方法的調用關系示例詳解

    Java解析方法的調用關系示例詳解

    這篇文章主要介紹了Java解析方法的調用關系,方法在Java編程中用于封裝重復的代碼,提高代碼復用性,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-10-10
  • IntelliJ IDEA使用tomcat和jetty配置詳解

    IntelliJ IDEA使用tomcat和jetty配置詳解

    這篇文章主要介紹了IntelliJ IDEA使用tomcat和jetty配置詳解,以便進一步地開發(fā)和調試,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • idea創(chuàng)建springboot項目和springcloud項目的詳細教程

    idea創(chuàng)建springboot項目和springcloud項目的詳細教程

    這篇文章主要介紹了idea創(chuàng)建springboot項目和springcloud項目方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • RocketMQMessageListener注解對rocketmq消息的消費實現(xiàn)機制

    RocketMQMessageListener注解對rocketmq消息的消費實現(xiàn)機制

    這篇文章主要為大家介紹了RocketMQMessageListener注解對rocketmq消息的消費實現(xiàn)機制源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • SpringBoot調整ApplicationContextAware如何實現(xiàn)類加載順序

    SpringBoot調整ApplicationContextAware如何實現(xiàn)類加載順序

    SpringBoot調整ApplicationContextAware實現(xiàn)類加載順序問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • java之阻塞隊列BlockingQueue解析

    java之阻塞隊列BlockingQueue解析

    這篇文章主要介紹了java之阻塞隊列BlockingQueue解析,阻塞隊列是一種線程安全的數(shù)據(jù)結構,用于在多線程環(huán)境下進行數(shù)據(jù)交換,它提供了一種阻塞的機制,當隊列為空時,消費者線程將被阻塞,直到隊列中有數(shù)據(jù)可供消費,需要的朋友可以參考下
    2023-10-10
  • Myeclipse 2016下Aptana安裝教程

    Myeclipse 2016下Aptana安裝教程

    這篇文章主要為大家詳細介紹了Myeclipse 2016下Aptana安裝教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Java中使用WebSocket的幾種方式

    Java中使用WebSocket的幾種方式

    本文主要介紹了Java中使用WebSocket的幾種方式
    2024-06-06
  • 詳解springboot熱啟動與熱部署

    詳解springboot熱啟動與熱部署

    本篇文章主要介紹了詳解springboot熱啟動與熱部署,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06

最新評論

合川市| 马山县| 成安县| 屏边| 织金县| 太谷县| 宁河县| 仁怀市| 渭南市| 朝阳县| 开江县| 鄂托克旗| 冷水江市| 炎陵县| 井冈山市| 五峰| 达孜县| 儋州市| 侯马市| 庆阳市| 日土县| 辽阳市| 涞源县| 广元市| 德钦县| 汾阳市| 南漳县| 广南县| 昌江| 广平县| 新沂市| 鄄城县| 习水县| 晋州市| 新晃| 右玉县| 宁强县| 德惠市| 衡阳县| 奉贤区| 宁城县|