1. 程式人生 > >Spring Boot Cache + redis 設定有效時間和自動重新整理快取,時間支援在配置檔案中配置

Spring Boot Cache + redis 設定有效時間和自動重新整理快取,時間支援在配置檔案中配置

分享一下我老師大神的人工智慧教程吧。零基礎,通俗易懂!風趣幽默!http://www.captainbed.net/

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

                                                                                                                                                           

問題描述

Spring  Cache提供的@Cacheable註解不支援配置過期時間,還有快取的自動重新整理。
我們可以通過配置CacheManneg來配置預設的過期時間和針對每個快取容器(value)單獨配置過期時間,但是總是感覺不太靈活。下面是一個示例:

@Beanpublic CacheManager cacheManager(RedisTemplate redisTemplate) {    RedisCacheManager cacheManager= new RedisCacheManager(redisTemplate);    cacheManager.setDefaultExpiration(60
);    Map<String,Long> expiresMap=new HashMap<>();    expiresMap.put("Product",5L);    cacheManager.setExpires(expiresMap);    return cacheManager;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我們想在註解上直接配置過期時間和自動重新整理時間,就像這樣:

@Cacheable(value = "people#120#90", key = "#person.id")public Person findOne(Person person) {    Person p = personRepository.findOne(person.getId());    System.out.println("為id、key為:" + p.getId() + "資料做了快取");    return p;}
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

value屬性上用#號隔開,第一個是原始的快取容器名稱,第二個是快取的有效時間,第三個是快取的自動重新整理時間,單位都是秒。

快取的有效時間和自動重新整理時間支援SpEl表示式,支援在配置檔案中配置,如:

@Cacheable(value = "people#${select.cache.timeout:1800}#${select.cache.refresh:600}", key = "#person.id", sync = true)//3public Person findOne(Person person) {    Person p = personRepository.findOne(person.getId());    System.out.println("為id、key為:" + p.getId() + "資料做了快取");    return p;}
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

解決思路

檢視原始碼你會發現快取最頂級的介面就是CacheManager和Cache介面。

CacheManager說明

CacheManager功能其實很簡單就是管理cache,介面只有兩個方法,根據容器名稱獲取一個Cache。還有就是返回所有的快取名稱。

public interface CacheManager {    /**     * 根據名稱獲取一個Cache(在實現類裡面是如果有這個Cache就返回,沒有就新建一個Cache放到Map容器中)     * @param name the cache identifier (must not be {@code null})     * @return the associated cache, or {@code null} if none found     */    Cache getCache(String name);    /**     * 返回一個快取名稱的集合     * @return the names of all caches known by the cache manager     */    Collection<String> getCacheNames();}
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Cache說明

Cache介面主要是操作快取的。get根據快取key從快取伺服器獲取快取中的值,put根據快取key將資料放到快取伺服器,evict根據key刪除快取中的資料。

public interface Cache {    ValueWrapper get(Object key);    void put(Object key, Object value);    void evict(Object key);    ...}
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

請求步驟

  1. 請求進來,在方法上面掃描@Cacheable註解,那麼會觸發org.springframework.cache.interceptor.CacheInterceptor快取的攔截器。
  2. 然後會呼叫CacheManager的getCache方法,獲取Cache,如果沒有(第一次訪問)就新建一Cache並返回。
  3. 根據獲取到的Cache去呼叫get方法獲取快取中的值。RedisCache這裡有個bug,原始碼是先判斷key是否存在,再去快取獲取值,在高併發下有bug。

程式碼分析

在最上面我們說了Spring Cache可以通過配置CacheManager來配置過期時間。那麼這個過期時間是在哪裡用的呢?設定預設的時間setDefaultExpiration,根據特定名稱設定有效時間setExpires,獲取一個快取名稱(value屬性)的有效時間computeExpiration,真正使用有效時間是在createCache方法裡面,而這個方法是在父類的getCache方法呼叫。通過RedisCacheManager原始碼我們看到:

// 設定預設的時間public void setDefaultExpiration(long defaultExpireTime) {    this.defaultExpiration = defaultExpireTime;}// 根據特定名稱設定有效時間public void setExpires(Map<String, Long> expires) {    this.expires = (expires != null ? new ConcurrentHashMap<String, Long>(expires) : null);}// 獲取一個key的有效時間protected long computeExpiration(String name) {    Long expiration = null;    if (expires != null) {        expiration = expires.get(name);    }    return (expiration != null ? expiration.longValue() : defaultExpiration);}@SuppressWarnings("unchecked")protected RedisCache createCache(String cacheName) {    // 呼叫了上面的方法獲取快取名稱的有效時間    long expiration = computeExpiration(cacheName);    // 建立了Cache物件,並使用了這個有效時間    return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,            cacheNullValues);}// 重寫父類的getMissingCache。去建立Cache@Overrideprotected Cache getMissingCache(String name) {    return this.dynamic ? createCache(name) : null;}
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

AbstractCacheManager父類原始碼:

// 根據名稱獲取Cache如果沒有呼叫getMissingCache方法,生成新的Cache,並將其放到Map容器中去。@Overridepublic Cache getCache(String name) {    Cache cache = this.cacheMap.get(name);    if (cache != null) {        return cache;    }    else {        // Fully synchronize now for missing cache creation...        synchronized (this.cacheMap) {            cache = this.cacheMap.get(name);            if (cache == null) {                // 如果沒找到Cache呼叫該方法,這個方法預設返回值NULL由子類自己實現。上面的就是子類自己實現的方法                cache = getMissingCache(name);                if (cache != null) {                    cache = decorateCache(cache);                    this.cacheMap.put(name, cache);                    updateCacheNames(name);                }            }            return cache;        }    }}
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

由此這個有效時間的設定關鍵就是在getCache方法上,這裡的name引數就是我們註解上的value屬性。所以在這裡解析這個特定格式的名稱我就可以拿到配置的過期時間和重新整理時間。getMissingCache方法裡面在新建快取的時候將這個過期時間設定進去,生成的Cache物件操作快取的時候就會帶上我們的配置的過期時間,然後過期就生效了。解析SpEL表示式獲取配置檔案中的時間也在也一步完成。

CustomizedRedisCacheManager原始碼:

package com.xiaolyuh.redis.cache;import com.xiaolyuh.redis.cache.helper.SpringContextHolder;import com.xiaolyuh.redis.utils.ReflectionUtils;import org.apache.commons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.support.DefaultListableBeanFactory;import org.springframework.cache.Cache;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.core.RedisOperations;import java.util.Collection;import java.util.concurrent.ConcurrentHashMap;/** * 自定義的redis快取管理器 * 支援方法上配置過期時間 * 支援熱載入快取:快取即將過期時主動重新整理快取 * * @author yuhao.wang */public class CustomizedRedisCacheManager extends RedisCacheManager {    private static final Logger logger = LoggerFactory.getLogger(CustomizedRedisCacheManager.class);    /**     * 父類cacheMap欄位     */    private static final String SUPER_FIELD_CACHEMAP = "cacheMap";    /**     * 父類dynamic欄位     */    private static final String SUPER_FIELD_DYNAMIC = "dynamic";    /**     * 父類cacheNullValues欄位     */    private static final String SUPER_FIELD_CACHENULLVALUES = "cacheNullValues";    /**     * 父類updateCacheNames方法     */    private static final String SUPER_METHOD_UPDATECACHENAMES = "updateCacheNames";    /**     * 快取引數的分隔符     * 陣列元素0=快取的名稱     * 陣列元素1=快取過期時間TTL     * 陣列元素2=快取在多少秒開始主動失效來強制重新整理     */    private static final String SEPARATOR = "#";    /**     * SpEL標示符     */    private static final String MARK = "$";    RedisCacheManager redisCacheManager = null;    @Autowired    DefaultListableBeanFactory beanFactory;    public CustomizedRedisCacheManager(RedisOperations redisOperations) {        super(redisOperations);    }    public CustomizedRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {        super(redisOperations, cacheNames);    }    public RedisCacheManager getInstance() {        if (redisCacheManager == null) {            redisCacheManager = SpringContextHolder.getBean(RedisCacheManager.class);        }        return redisCacheManager;    }    @Override    public Cache getCache(String name) {        String[] cacheParams = name.split(SEPARATOR);        String cacheName = cacheParams[0];        if (StringUtils.isBlank(cacheName)) {            return null;        }        // 有效時間,初始化獲取預設的有效時間        Long expirationSecondTime = getExpirationSecondTime(cacheName, cacheParams);        // 自動重新整理時間,預設是0        Long preloadSecondTime = getExpirationSecondTime(cacheParams);        // 通過反射獲取父類存放快取的容器物件        Object object = ReflectionUtils.getFieldValue(getInstance(), SUPER_FIELD_CACHEMAP);        if (object != null && object instanceof ConcurrentHashMap) {            ConcurrentHashMap<String, Cache> cacheMap = (ConcurrentHashMap<String, Cache>) object;            // 生成Cache物件,並將其儲存到父類的Cache容器中            return getCache(cacheName, expirationSecondTime, preloadSecondTime, cacheMap);        } else {            return super.getCache(cacheName);        }    }    /**     * 獲取過期時間     *     * @return     */    private long getExpirationSecondTime(String cacheName, String[] cacheParams) {        // 有效時間,初始化獲取預設的有效時間        Long expirationSecondTime = this.computeExpiration(cacheName);        // 設定key有效時間        if (cacheParams.length > 1) {            String expirationStr = cacheParams[1];            if (!StringUtils.isEmpty(expirationStr)) {                // 支援配置過期時間使用EL表示式讀取配置檔案時間                if (expirationStr.contains(MARK)) {                    expirationStr = beanFactory.resolveEmbeddedValue(expirationStr);                }                expirationSecondTime = Long.parseLong(expirationStr);            }        }        return expirationSecondTime;    }    /**     * 獲取自動重新整理時間     *     * @return     */    private long getExpirationSecondTime(String[] cacheParams) {        // 自動重新整理時間,預設是0        Long preloadSecondTime = 0L;        // 設定自動重新整理時間        if (cacheParams.length > 2) {            String preloadStr = cacheParams[2];            if (!StringUtils.isEmpty(preloadStr)) {                // 支援配置重新整理時間使用EL表示式讀取配置檔案時間                if (preloadStr.contains(MARK)) {                    preloadStr = beanFactory.resolveEmbeddedValue(preloadStr);                }                preloadSecondTime = Long.parseLong(preloadStr);            }        }        return preloadSecondTime;    }    /**     * 重寫父類的getCache方法,真假了三個引數     *     * @param cacheName            快取名稱     * @param expirationSecondTime 過期時間     * @param preloadSecondTime    自動重新整理時間     * @param cacheMap             通過反射獲取的父類的cacheMap物件     * @return Cache     */    public Cache getCache(String cacheName, long expirationSecondTime, long preloadSecondTime, ConcurrentHashMap<String, Cache> cacheMap) {        Cache cache = cacheMap.get(cacheName);        if (cache != null) {            return cache;        } else {            // Fully synchronize now for missing cache creation...            synchronized (cacheMap) {                cache = cacheMap.get(cacheName);                if (cache == null) {                    // 呼叫我們自己的getMissingCache方法建立自己的cache                    cache = getMissingCache(cacheName, expirationSecondTime, preloadSecondTime);                    if (cache != null) {                        cache = decorateCache(cache);                        cacheMap.put(cacheName, cache);                        // 反射去執行父類的updateCacheNames(cacheName)方法                        Class<?>[] parameterTypes = {String.class};                        Object[] parameters = {cacheName};                        ReflectionUtils.invokeMethod(getInstance(), SUPER_METHOD_UPDATECACHENAMES, parameterTypes, parameters);                    }                }                return cache;            }        }    }    /**     * 建立快取     *     * @param cacheName            快取名稱     * @param expirationSecondTime 過期時間     * @param preloadSecondTime    制動重新整理時間     * @return     */    public CustomizedRedisCache getMissingCache(String cacheName, long expirationSecondTime, long preloadSecondTime) {        logger.info("快取 cacheName:{},過期時間:{}, 自動重新整理時間:{}", cacheName, expirationSecondTime, preloadSecondTime);        Boolean dynamic = (Boolean) ReflectionUtils.getFieldValue(getInstance(), SUPER_FIELD_DYNAMIC);        Boolean cacheNullValues = (Boolean) ReflectionUtils.getFieldValue(getInstance(), SUPER_FIELD_CACHENULLVALUES);        return dynamic ? new CustomizedRedisCache(cacheName, (this.isUsePrefix() ? this.getCachePrefix().prefix(cacheName) : null),                this.getRedisOperations(), expirationSecondTime, preloadSecondTime, cacheNullValues) : null;    }}
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204

那自動重新整理時間呢?

在RedisCache的屬性裡面沒有重新整理時間,所以我們繼承該類重寫我們自己的Cache的時候要多加一個屬性preloadSecondTime來儲存這個重新整理時間。並在getMissingCache方法建立Cache物件的時候指定該值。

CustomizedRedisCache部分原始碼:

/** * 快取主動在失效前強制重新整理快取的時間 * 單位:秒 */private long preloadSecondTime = 0;// 重寫後的構造方法public CustomizedRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration, long preloadSecondTime) {    super(name, prefix, redisOperations, expiration);    this.redisOperations = redisOperations;    // 指定自動重新整理時間    this.preloadSecondTime = preloadSecondTime;    this.prefix = prefix;}// 重寫後的構造方法public CustomizedRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration, long preloadSecondTime, boolean allowNullValues) {    super(name, prefix, redisOperations, expiration, allowNullValues);    this.redisOperations = redisOperations;    // 指定自動重新整理時間    this.preloadSecondTime = preloadSecondTime;    this.prefix = prefix;}
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

那麼這個自動重新整理時間有了,怎麼來讓他自動重新整理呢?

在呼叫Cache的get方法的時候我們都會去快取服務查詢快取,這個時候我們在多查一個快取的有效時間,和我們配置的自動重新整理時間對比,如果快取的有效時間小於這個自動重新整理時間我們就去重新整理快取(這裡注意一點在高併發下我們最好只放一個請求去重新整理資料,儘量減少資料的壓力,所以在這個位置加一個分散式鎖)。所以我們重寫這個get方法。

CustomizedRedisCache部分原始碼:

/** * 重寫get方法,獲取到快取後再次取快取剩餘的時間,如果時間小余我們配置的重新整理時間就手動重新整理快取。 * 為了不影響get的效能,啟用後臺執行緒去完成快取的刷。 * 並且只放一個執行緒去重新整理資料。 * * @param key * @return */@Overridepublic ValueWrapper get(final Object key) {    RedisCacheKey cacheKey = getRedisCacheKey(key);    String cacheKeyStr = new String(cacheKey.getKeyBytes());    // 呼叫重寫後的get方法    ValueWrapper valueWrapper = this.get(cacheKey);    if (null != valueWrapper) {        // 重新整理快取資料        refreshCache(key, cacheKeyStr);    }    return valueWrapper;}/** * 重寫父類的get函式。 * 父類的get方法,是先使用exists判斷key是否存在,不存在返回null,存在再到redis快取中去取值。這樣會導致併發問題, * 假如有一個請求呼叫了exists函式判斷key存在,但是在下一時刻這個快取過期了,或者被刪掉了。 * 這時候再去快取中獲取值的時候返回的就是null了。 * 可以先獲取快取的值,再去判斷key是否存在。 * * @param cacheKey * @return */@Overridepublic RedisCacheElement get(final RedisCacheKey cacheKey) {    Assert.notNull(cacheKey, "CacheKey must not be null!");    // 根據key獲取快取值    RedisCacheElement redisCacheElement = new RedisCacheElement(cacheKey, fromStoreValue(lookup(cacheKey)));    // 判斷key是否存在    Boolean exists = (Boolean) redisOperations.execute(new RedisCallback<Boolean>() {        @Override        public Boolean doInRedis(RedisConnection connection) throws DataAccessException {            return connection.exists(cacheKey.getKeyBytes());        }    });    if (!exists.booleanValue()) {        return null;    }    return redisCacheElement;}/** * 重新整理快取資料 */private void refreshCache(Object key, String cacheKeyStr) {    Long ttl = this.redisOperations.getExpire(cacheKeyStr);    if (null != ttl && ttl <= CustomizedRedisCache.this.preloadSecondTime) {        // 儘量少的去開啟執行緒,因為執行緒池是有限的        ThreadTaskHelper.run(new Runnable() {            @Override            public void run() {                // 加一個分散式鎖,只放一個請求去重新整理快取                RedisLock redisLock = new RedisLock((RedisTemplate) redisOperations, cacheKeyStr + "_lock");                try {                    if (redisLock.lock()) {                        // 獲取鎖之後再判斷一下過期時間,看是否需要載入資料                        Long ttl = CustomizedRedisCache.this.redisOperations.getExpire(cacheKeyStr);                        if (null != ttl && ttl <= CustomizedRedisCache.this.preloadSecondTime) {                            // 通過獲取代理方法資訊重新載入快取資料                            CustomizedRedisCache.this.getCacheSupport().refreshCacheByKey(CustomizedRedisCache.super.getName(), key.toString());                        }                    }                } catch (Exception e) {                    logger.info(e.getMessage(), e);                } finally {                    redisLock.unlock();                }            }        });    }}
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

那麼自動重新整理肯定要掉用方法訪問資料庫,獲取值後去重新整理快取。這時我們又怎麼能去呼叫方法呢?

我們利用java的反射機制。所以我們要用一個容器來存放快取方法的方法資訊,包括物件,方法名稱,引數等等。我們建立了CachedInvocation類來存放這些資訊,再將這個類的物件維護到容器中。

CachedInvocation原始碼:

public final class CachedInvocation {    private Object key;    private final Object targetBean;    private final Method targetMethod;    private Object[] arguments;    public CachedInvocation(Object key, Object targetBean, Method targetMethod, Object[] arguments) {        this.key = key;        this.targetBean = targetBean;        this.targetMethod = targetMethod;        if (arguments != null && arguments.length != 0) {            this.arguments = Arrays.copyOf(arguments, arguments.length);        }    }    public Object[] getArguments() {        return arguments;    }    public Object getTargetBean() {        return targetBean;    }    public Method getTargetMethod() {        return targetMethod;    }    public Object getKey() {        return key;    }    /**     * 必須重寫equals和hashCode方法,否則放到set集合裡沒法去重 &n