1. 程式人生 > >以Spring Cache擴充套件為例介紹如何進行高效的原始碼的閱讀

以Spring Cache擴充套件為例介紹如何進行高效的原始碼的閱讀

摘要

日常開發中,需要用到各種各樣的框架來實現API、系統的構建。作為程式設計師,除了會使用框架還必須要了解框架工作的原理。這樣可以便於我們排查問題,和自定義的擴充套件。那麼如何去學習框架呢。通常我們通過閱讀文件、檢視原始碼,然後又很快忘記。始終不能融匯貫通。本文主要基於Spring Cache擴充套件為例,介紹如何進行高效的原始碼閱讀。

SpringCache的介紹

為什麼以Spring Cache為例呢,原因有兩個

  1. Spring框架是web開發最常用的框架,值得開發者去閱讀程式碼,吸收思想
  2. 快取是企業級應用開發必不可少的,而隨著系統的迭代,我們可能會需要用到記憶體快取、分散式快取。那麼Spring Cache作為膠水層,能夠遮蔽掉我們底層的快取實現。

一句話解釋Spring Cache: 通過註解的方式,利用AOP的思想來解放快取的管理。

step1 檢視文件

首先通過檢視官方文件,概括瞭解Spring Cache
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html

重點兩點

  1. 兩個介面抽象 Cache,CacheManager,具體的實現都是基於這兩個抽象實現。
    典型的SPI機制,和eat your dog food。當需要提供介面給外部呼叫,首先自己內部的實現也必須基於同樣一套抽象機制

The cache abstraction does not provide an actual store and relies on abstraction materialized by the org.springframework.cache.Cache and org.springframework.cache.CacheManager interfaces.

  1. Spring Cache提供了這些快取的實現,如果沒有一種CacheManage,或者CacheResolver,會按照指定的順序去實現

    If you have not defined a bean of type CacheManager or a CacheResolver named cacheResolver (see CachingConfigurer), Spring Boot tries to detect the following providers (in the indicated order):
    1.Generic
    2.JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)

    3.EhCache 2.x
    4.Hazelcast
    5.Infinispan
    6.Couchbase
    7.Redis
    8.Caffeine
    9.Simple

step2 run demo

對Spring Cache有了一個大概的瞭解後,我們首先使用起來,跑個demo。

定義一個使用者查詢方法

@Component
public class CacheSample {
    @Cacheable(cacheNames = "users")
    public Map<Long, User> getUser(final Collection<Long> userIds) {
        System.out.println("not cache");
        final Map<Long, User> mapUser = new HashMap<>();
        userIds.forEach(userId -> {
            mapUser.put(userId, User.builder().userId(userId).name("name").build());
        });
        return mapUser;
    }

配置一個CacheManager

@Configuration
public class CacheConfig {
    @Primary
    @Bean(name = { "cacheManager" })
    public CacheManager getCache() {
      return new ConcurrentMapCacheManager("users");
    }

API呼叫

@RestController
@RequestMapping("/api/cache")
public class CacheController {
    @Autowired
    private CacheSample cacheSample;
    @GetMapping("/user/v1/1")
    public List<User> getUser() {
        return cacheSample.getUser(Arrays.asList(1L,2L)).values().stream().collect(Collectors.toList());
    }
    }

step3 debug 檢視實現

demo跑起來後,就是debug看看程式碼如何實現的了。
因為直接看原始碼的,沒有呼叫關係,看起來會一頭霧水。通過debug能夠使你更快了解一個實現。


通過debug我們會發現主要控制邏輯是在切面CacheAspectSupport
會先根據cache key找快取資料,沒有的話put進去。

// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<>();
if (cacheHit == null) {
   collectPutRequests(contexts.get(CacheableOperation.class),
         CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}

step4 實現擴充套件

知道如何使用Spring Cache後,我們需要進一步思考,就是如何擴充套件。那麼帶著問題出發。
比如Spring Cache不支援批量key的快取,像上文我們舉的例子,我們希望快取的key是userId,而不是Collection userIds。以userId為key,這樣的快取命中率更高,儲存的成本更小。

  @Cacheable(cacheNames = "users")
    public Map<Long, User> getUser(final Collection<Long> userIds) {

所以我們要實現對Spring Cache進行擴充套件。step3中我們已經大致瞭解了Spring Cache的實現。那麼實現這個擴充套件的功能就是拆分Collection userIds,快取命中的從快取中獲取,沒有命中的,呼叫源方法。

@Aspect
@Component
public class CacheExtenionAspect {

    @Autowired
    private CacheExtensionManage cacheExtensionManage;

    /**
     * 返回的結果中快取命中的從快取中獲取,沒有命中的呼叫原來的方法獲取
     * @param joinPoint
     * @return
     */
    @Around("@annotation(org.springframework.cache.annotation.Cacheable)")
    @SuppressWarnings("unchecked")
    public Object aroundCache(final ProceedingJoinPoint joinPoint) {
    
        // 修改掉Collection值,cacheResult需要重新構造一個
        args[0] = cacheResult.getMiss();
        try {
            final Map<Object, Object> notHit = CollectionUtils.isEmpty(cacheResult.getMiss()) ? null
                    : (Map<Object, Object>) (method.invoke(target, args));
            final Map<Object, Object> hits = cacheResult.getHit();
            if (Objects.isNull(notHit)) {
                return hits;
            }
            // 設定快取
            cacheResult.getCache().putAll(notHit);
            hits.putAll(notHit);
            return hits;
    }
}

然後擴充套件Cache,CacheManage
重寫Cache的查詢快取方法,返回新的CacheResult

  public static Object lookup(final CacheExtension cache, final Object key) {
        if (key instanceof Collection) {
            final Collection<Object> originalKeys = ((Collection) key);
            if (originalKeys == null || originalKeys.isEmpty()) {
                return CacheResult.builder().cache(cache).miss(
                        Collections.emptySet())
                        .build();
            }
            final List<Object> keys = originalKeys.stream()
                    .filter(Objects::nonNull).collect(Collectors.toList());
            final Map<Object, Object> hits = cache.getAll(keys);
            final Set<Object> miss = new HashSet(keys);
            miss.removeAll(hits.keySet());
            return CacheResult.builder().cache(cache).hit(hits).miss(miss).build();
        }
        return null;
    }

CacheResult就是新的快取結果格式

 @Builder
    @Setter
    @Getter
    static class CacheResult {
        final CacheExtension cache;
        // 命中的快取結果
        final Map<Object, Object> hit;
        // 需要重新呼叫源方法的keys
        private Set<Object> miss;
    }

然後擴充套件CacheManager,沒什麼重寫,就是自定義一種manager型別
為快取指定新的CacheManager
@Primary @Bean public CacheManager getExtensionCache() { return new CacheExtensionManage("users2"); }
完整程式碼
https://github.com/FS1360472174/javaweb/tree/master/web/src/main/java/com/fs/web/cache

總結

本文主要介紹一種原始碼學習方法,純屬拋磚引玉,如果你有好的方法,歡迎分享。

關注公眾號【方丈的寺院】,第一時間收到文章的更新,與方丈一起開始技術修行之路

相關推薦

Spring Cache擴充套件介紹如何進行高效原始碼閱讀

摘要 日常開發中,需要用到各種各樣的框架來實現API、系統的構建。作為程式設計師,除了會使用框架還必須要了解框架工作的原理。這樣可以便於我們排查問題,和自定義的擴充套件。那麼如何去學習框架呢。通常我們通過閱讀文件、檢視原始碼,然後又很快忘記。始終不能融匯貫通。本文主要基於Spring Cache擴充套件為例,

如何進行高效原始碼閱讀Spring Cache擴充套件帶你搞清楚

摘要 日常開發中,需要用到各種各樣的框架來實現API、系統的構建。作為程式設計師,除了會使用框架還必須要了解框架工作的原理。這樣可以便於我們排查問題,和自定義的擴充套件。那麼如何去學習框架呢。通常我們通過閱讀文件、檢視原始碼,然後又很快忘記。始終不能融匯貫通。本文主要基於Spring Cache擴充套件為例

activiti配置檔案介紹Spring管理的bean工廠

配置工廠Bean 通常由應用程式直接使用new建立新的物件,為了將物件的建立和使用相分離,採用工廠模式,即應用程式將物件的建立及初始化職責交給工廠物件. 一般情況下,應用程式有自己的工廠物件來建立bean.如果將應用程式自己的工廠物件交給Spring管理,那麼Spring管理的就不是

Spring整合EhCache從根本上了解Spring緩存這件事(轉)

內置 tor 重要 date 清單 one display 結合 boa 前兩節“Spring緩存抽象”和“基於註解驅動的緩存”是為了更加清晰的了解Spring緩存機制,整合任何一個緩存實現或者叫緩存供應商都應該了解並清楚前兩

kaggle資料探勘——Titanic介紹處理資料大致步驟

Titanic是kaggle上的一道just for fun的題,沒有獎金,但是資料整潔,拿來練手最好不過。 本文以 Titanic 的資料,使用較為簡單的決策樹,介紹處理資料大致過程、步驟 注意,本文的目的,在於幫助你入門資料探勘,熟悉處理資料步驟、流程

欠驅動Or全驅動?——關於機械手驅動方式的介紹與更優選擇(德國Schunk Hand

這篇文章主要將介紹機械手兩種不同驅動方式——即欠驅動和全驅動的定義、應用背景、優劣效能等特性,並將以雄克靈巧手為例,討論現階段機械手驅動方式的更優選擇方案。 定義: 首先先來明確欠驅動和全驅動的定義(在機械設計領域):欠驅動 系統執行器的數目小於其自由度數目;全驅動 系統執行器的數目等於其自由度數目

IntelliJ IDEA 中如何匯入jar包(引入spring包,構建spring測試環境)

我就簡單的以圖例的方式示範下,這個編輯器是怎麼樣匯入jar包的。觀眾們可以觸類旁通一下。 就我下面的例子,就是簡單的把Java 的 輸出 hello world 的專案,擴充套件一下,要引入spr

mysql介紹PreparedStatement防止sql注入原理

最近,在寫程式時開始注意到sql注入的問題,由於以前寫程式碼時不是很注意,有一些sql會存在被注入的風險,那麼防止sql注入的原理是什麼呢?我們首先通過PrepareStatement這個類來學習一下吧! 作為一個IT業內人士只要接觸過資料庫的人都應該知道sq

關於Android手機MTP模式連接的一些設置(win7和ubuntu下,紅米1s

sta start .net eno bcm htm web date ati 有些手機的MTP模式在電腦上識別不了,須要一些設置才幹夠,以下就網上收集來的一些設置方法集中貼過來: 一、 win7下 參考:http://blog.ammrli.com/?p=11

linux 磁盤分區,主分區,擴展分區,邏輯分區sata接口

sat pos linux內核 tracking 限制 設置 art lin pop ?? 以sata接口(依據linux內核檢測其順序 sda,sdb...)為例, 1, 硬盤的限制,最多僅僅能設置4個分區(主分區+擴展分區),路徑例如以下, /dev/sda1

又見關系並查集 POJ 1182 食物鏈

for _id scanf rac sin merge als ++ mod 簡單的關系並查集一般非常easy依據給出的關系搞出一個有向的環,那麽兩者之間的關系就變成了兩者之間的距離。 對於此題: 若u。v不在一個集合內,則顯然此條語句會合法(暫且忽略後兩條。下

基於HttpClient實現網絡爬蟲~百度新聞

rom pcl 音頻 lba 瀏覽器中 sts 更新 @override erro 轉載請註明出處:http://blog.csdn.net/xiaojimanman/article/details/40891791 基於HttpClient4.5實現網絡爬蟲

關於端口被占用的問題(61440端口

internet 其他 直接 按鍵 str es2017 查看 window 原因   先來說一下寫這篇blog的原因吧,本人今天下午回到宿舍打開電腦準備上網下載開發Python的環境,結果發現下載速度和以前相比是大大的不如。然後看了一下室友的網絡一切ok,然後我突然只能登

當你輸入一個網址/點擊一個鏈接,發生了什麽?(www.baidu.com

var aid 查找 style str 在服務器 開始 傳輸 http響應 >>>點擊網址後,應用層的DNS協議會將網址解析為IP地址; DNS查找過程: 瀏覽器會檢查緩存中有沒有這個域名對應的解析過的IP地址,如果緩存中有,這個解析過程就將結束。 如果

linux 忘記root(這裏centos 6.5)密碼的解決辦法

理發 我們 bsp ext sso 用戶模式 正常 修改 啟動 在使用linux的過程中有時候會忘記root用戶的密碼(尤其是進行交接而文檔內容不全的時候),這個時候我們就可以進入單用戶模式來重置root用戶密碼。下面來講解重置root密碼的方式,也可以說是破解root密碼

ionic2+Angular2:套接口明細步驟,登錄功能

var efi root lencod a13 資源 彈出 nbsp 取數據 1、在app.module.ts引用HttpModul,並在imports內引用。截圖如下: 2、在src目錄下新建http服務。命令行:ionic g provider HttpSe

使用Altium Designer10軟件繪制芯片引腳圖(IC1114芯片

name 類型 對話 圖1 菜單欄 參數設置 計算 sch 位置 運用Altium Designer軟件繪制IC1114-F48LQ芯片的原理圖庫,詳細步驟如下: 第一步:在“文檔”裏新建一個文件夾,命名為“電子產品創新設計源文件”,用以存放每次實驗的源文件和原理圖庫文件等

Visual Studio高低版本的問題(2008和2015

image 記得 標準 方法 log 版本 問題 不同 查看 第一個就是改這個裏面的配置 第二個是改這個裏面的配置 方法:通過觀察這兩個的不同,並且同時看低版本打開高版本的時候的錯誤提示來看應該改哪一些配置文件之後就可以進行對比操作,還有就是如果不小心改錯了,比如說20

Python——爬取人口遷徙數據(騰訊遷徙

map car img all spa ima tps .sh compile 說明: 1.遷徙量是騰訊修改後的數值,無法確認真實性。 2.代碼運行期間,騰訊遷徙未設置IP屏蔽和瀏覽器檢測,因此下段代碼僅能保證發布近期有效。 3.代碼功能:爬取指定一天的四十個城市左右的遷徙

Net Core中數據庫事務隔離詳解——Dapper和Mysql

事務 ring 增刪改 tostring 測試 stc efault 多個 log Net Core中數據庫事務隔離詳解——以Dapper和Mysql為例 事務隔離級別 準備工作 Read uncommitted 讀未提交 Read committed 讀取提交內