1. 程式人生 > >自定義key的CacheConfig原始碼剖析

自定義key的CacheConfig原始碼剖析

Spring cache的原始碼版本:spring-context-5.0.9.RELEASE.jar

專案demo程式碼:點我跳轉

先講自定義可以幹嘛,再講解原始碼:
通過自定義cache config,可以用來設定自定義的過期時間,自定義的序列化方式,自定義字首等等。@Cacheable 註解不能設定過期時間,這點是由於cache本身是抽象,各種實現過期時間的一些具體快取框架可能有差異,不過我覺得這是一個非常不爽的點。
所以我們來閱讀原始碼吧。

Cache啟動初始化

AbstractCacheManager類中有一個cacheMap變數儲存所有的快取實現,在專案初始化時,由於類中實現了InitializingBean

介面,所有會初始化快取,程式碼:

	public abstract class AbstractCacheManager implements CacheManager, InitializingBean {

	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

	private volatile Set<String> cacheNames = Collections.emptySet();

	@Override
	public void
afterPropertiesSet() { initializeCaches(); } /** * Initialize the static configuration of caches. * <p>Triggered on startup through {@link #afterPropertiesSet()}; * can also be called to re-initialize at runtime. * @since 4.2.2 * @see #loadCaches() */ public void initializeCaches
() { // 1⃣️重點在loadCaches方法 Collection<? extends Cache> caches = loadCaches(); synchronized (this.cacheMap) { this.cacheNames = Collections.emptySet(); this.cacheMap.clear(); Set<String> cacheNames = new LinkedHashSet<>(caches.size()); for (Cache cache : caches) { String name = cache.getName(); this.cacheMap.put(name, decorateCache(cache)); cacheNames.add(name); } this.cacheNames = Collections.unmodifiableSet(cacheNames); } } }

由於loadCaches方法是抽象的,我們實現使用的redis實現,所有直接檢視org.springframework.data.redis.cache.RedisCacheManager類的實現,閱讀原始碼發現:

public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
...
	private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
...
	@Override
	protected Collection<RedisCache> loadCaches() {
	    //1⃣️可以看到實際上就是取initialCacheConfiguration變數的值
		List<RedisCache> caches = new LinkedList<>();
		for (Map.Entry<String, RedisCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
            //2⃣️初始化cache
			caches.add(createRedisCache(entry.getKey(), entry.getValue()));
		}
		return caches;
	}
	protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
		return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
	}
...    
}    

通過注入自定義的cacheConfig能夠使不同的key擁有不同的cache配置,達到自定義的效果。

Cache被呼叫

回到上面的正題,在cacheManager初始化完成後,當有請求來到@Cacheable註解處的方法時,會通過aop代理的形式做invoke,頂層是在CacheAspectSupport的execute方法進行代理,

中間一個步驟省略,它最後會直接通過CacheManager去獲取cache,方法為:

public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
...
    @Override
	@Nullable
	public 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 = getMissingCache(name);
					if (cache != null) {
						cache = decorateCache(cache);
						this.cacheMap.put(name, cache);
						updateCacheNames(name);
					}
				}
				return cache;
			}
		}
	}
...
}

我們檢視下RedisCache內部呼叫生成快取的方法來看一下。

public class RedisCache extends AbstractValueAdaptingCache {
    @Override
	public void put(Object key, @Nullable Object value) {
		Object cacheValue = preProcessCacheValue(value);
...
    	//1⃣️ 過期時間是通過cacheConfig配置進行獲取的。
		cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
	}
    protected byte[] serializeCacheValue(Object value) {

		if (isAllowNullValues() && value instanceof NullValue) {
			return BINARY_NULL_VALUE;
		}
        //2⃣️ value的序列化方式也是通過cacheConfig配置來初始化的
		return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value));
	}
}

自定義CacheConfig的配置方法

 	@Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(connectionFactory);
        builder.withInitialCacheConfigurations(customCacheConfig());
        return builder.build();
    }

    private Map<String, RedisCacheConfiguration> customCacheConfig() {
        Map<String, RedisCacheConfiguration> map = new HashMap<>();
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(1)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))

PS: 感覺使用Spring cache還是略麻煩,不如自己實現一個基於aop的cache吧。