原本想基於Lettuce,自己寫一個Redis的Cache,自定義Cache難度不高,但是在編碼過程中,發現get(Object key, Class<T> aClass)函數從未被調用過,導致計划遲遲未完成。
自定義Cache的基本代碼
package cn.seaboot.plugin.redis; import cn.seaboot.admin.consts.SystemConst; import cn.seaboot.common.core.Converter; import cn.seaboot.common.exception.BizException; import org.springframework.cache.Cache; import org.springframework.cache.support.SimpleValueWrapper; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; /** * */ public class RedisCache implements Cache { Map<Object, Object> map = new HashMap<>(); /** * 簡單直白,就是獲取Cache的名字 */ @Override public String getName() { return SystemConst.CACHE_DEF; } /** * 獲取底層的緩存實現對象 */ @Override public Object getNativeCache() { return SystemConst.CACHE_DEF; } /** * 根據鍵獲取值,把值包裝在ValueWrapper里面,如果有必要可以附加額外信息 */ @Override public ValueWrapper get(Object key) { System.out.println("ValueWrapper"); throw new BizException("test"); // return map.containsKey(key)?new SimpleValueWrapper(map.get(key)):null; } /** * 代碼封裝中希望被調用的代碼 */ @Override public <T> T get(Object key, Class<T> aClass) { try { System.out.println("get(Object o, Class<T> aClass)"); return map.containsKey(key)?Converter.convert(map.get(key), aClass):null; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 從緩存中獲取 key 對應的值,如果緩存沒有命中,則添加緩存, * 此時可異步地從 valueLoader 中獲取對應的值(4.3版本新增) * 與緩存標簽中的sync屬性有關 */ @Override public <T> T get(Object key, Callable<T> valueLoader) { try { System.out.println("get"); return valueLoader.call(); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 存放鍵值對 */ @Override public void put(Object key, Object value) { System.out.println("put(Object key, Object value)"); map.put(key, value); } /** * 如果鍵對應的值不存在,則添加鍵值對 */ @Override public ValueWrapper putIfAbsent(Object key, Object value) { System.out.println("putIfAbsent"); map.put(key, value); return new SimpleValueWrapper(value); } /** * 移除鍵對應鍵值對 */ @Override public void evict(Object key) { System.out.println("evict"); map.remove(key); } /** * 清空緩存 */ @Override public void clear() { System.out.println("clear"); map.clear(); } }
問題
數據緩存需要經過序列化,轉為String,或者byte[];在取值的時候,需要數據還原,JSON是我們最常用的,它的序列化依賴Class對象:
JSON.parseObject(String json, Class<T> clazz)
因此,Cache接口中,get(Object key, Class<T> aClass)是我們最希望被調用的函數,而實際使用中,此函數卻從未觸發過,這是為何 ?
(后面提到的get函數,均指此函數,重點分析此函數不被調用的原因)
@Override public <T> T get(Object key, Class<T> aClass) { try { System.out.println("get(Object o, Class<T> aClass)"); return map.containsKey(key)?Converter.convert(map.get(key), aClass):null; } catch (Exception e) { e.printStackTrace(); } return null; }
分析
打印異常棧,分析源碼,核心異常如下:
cn.seaboot.admin.core.ControllerExceptionHandler.exceptionHandler(javax.servlet.http.HttpServletRequest,javax.servlet.http.
HttpServletResponse,cn.seaboot.common.exception.BizException) throws java.io.IOException cn.seaboot.common.exception.BizException: test at cn.seaboot.plugin.redis.RedisCache.get(RedisCache.java:43) at org.springframework.cache.interceptor.AbstractCacheInvoker.doGet(AbstractCacheInvoker.java:73) at org.springframework.cache.interceptor.CacheAspectSupport.findInCaches(CacheAspectSupport.java:389) at org.springframework.cache.interceptor.CacheAspectSupport.findCachedItem(CacheAspectSupport.java:350) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:239) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:205) at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) at cn.seaboot.admin.service.core.DebugService$$EnhancerBySpringCGLIB$$e478a24.testCache(<generated>) at cn.seaboot.admin.ctrl.page.DebugCtrl.data(DebugCtrl.java:97)
1、無關緊要的部分:
DebugCtrl -> Service代理 -> CglibAopProxy -> ReflectiveMethodInvocation
DebugCtrl、Service是自己寫的代碼。
CglibAopProxy、ReflectiveMethodInvocation屬於Aop包,歸屬於底層源碼,統領系統的代理切面,緩存攔截只屬於其中一部分。
2、核心部分,問題必定位於這些代碼中:
- CacheInterceptor 緩存攔截
- CacheAspectSupport 緩存切面
- AbstractCacheInvoker Cache處理對象,負責調用Cache實現類
- RedisCache Cache接口實現類
CacheInterceptor
只是負責調用起CacheAspectSupport,本身代碼較少,無分析價值
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable { public CacheInterceptor() { } @Nullable public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); CacheOperationInvoker aopAllianceInvoker = () -> { try { return invocation.proceed(); } catch (Throwable var2) { throw new ThrowableWrapper(var2); } }; try { //只是負責調用起CacheAspectSupport,本身代碼較少 return this.execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments()); } catch (ThrowableWrapper var5) { throw var5.getOriginal(); } } }
AbstractCacheInvoker
Cache處理器,對Cache包裹了一層代碼,負責調用起Cache的相關函數,從這里可以看出一部分問題了,代碼自始至終沒調用過get函數
public abstract class AbstractCacheInvoker { protected SingletonSupplier<CacheErrorHandler> errorHandler; protected AbstractCacheInvoker() { this.errorHandler = SingletonSupplier.of(SimpleCacheErrorHandler::new); } protected AbstractCacheInvoker(CacheErrorHandler errorHandler) { this.errorHandler = SingletonSupplier.of(errorHandler); } public void setErrorHandler(CacheErrorHandler errorHandler) { this.errorHandler = SingletonSupplier.of(errorHandler); } public CacheErrorHandler getErrorHandler() { return (CacheErrorHandler)this.errorHandler.obtain(); } @Nullable protected ValueWrapper doGet(Cache cache, Object key) { try { return cache.get(key); } catch (RuntimeException var4) { this.getErrorHandler().handleCacheGetError(var4, cache, key); return null; } } protected void doPut(Cache cache, Object key, @Nullable Object result) { try { cache.put(key, result); } catch (RuntimeException var5) { this.getErrorHandler().handleCachePutError(var5, cache, key, result); } } protected void doEvict(Cache cache, Object key) { try { cache.evict(key); } catch (RuntimeException var4) { this.getErrorHandler().handleCacheEvictError(var4, cache, key); } } protected void doClear(Cache cache) { try { cache.clear(); } catch (RuntimeException var3) { this.getErrorHandler().handleCacheClearError(var3, cache); } } }
CacheAspectSupport
代碼有一千多行,重點看execute,很明顯,get函數確實沒被調用過
@Nullable private Object execute(CacheOperationInvoker invoker, Method method, CacheAspectSupport.CacheOperationContexts contexts) { if (contexts.isSynchronized()) { CacheAspectSupport.CacheOperationContext context = (CacheAspectSupport.CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next(); if (this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) { Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT); Cache cache = (Cache)context.getCaches().iterator().next(); try { //同步調用,調用Cache有Callback的get函數 return this.wrapCacheValue(method, cache.get(key, () -> { return this.unwrapReturnValue(this.invokeOperation(invoker)); })); } catch (ValueRetrievalException var10) { throw (ThrowableWrapper)var10.getCause(); } } else { return this.invokeOperation(invoker); } } else { this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT); //異步調用,調用get函數,返回ValueWrapper ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class)); List<CacheAspectSupport.CachePutRequest> cachePutRequests = new LinkedList(); if (cacheHit == null) { this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests); } Object cacheValue; Object returnValue; if (cacheHit != null && !this.hasCachePut(contexts)) { //從緩存命中到Value cacheValue = cacheHit.get(); //Value格式化 returnValue = this.wrapCacheValue(method, cacheValue); } else { //從緩存未命中到Value returnValue = this.invokeOperation(invoker); cacheValue = this.unwrapReturnValue(returnValue); } //put函數調用 this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); Iterator var8 = cachePutRequests.iterator(); while(var8.hasNext()) { //put函數調用 CacheAspectSupport.CachePutRequest cachePutRequest = (CacheAspectSupport.CachePutRequest)var8.next(); cachePutRequest.apply(cacheValue); } //evict函數調用 this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); return returnValue; } }
其中最可疑的wrapCacheValue,作用是包裝緩存值,合理的代碼邏輯,會對返回值進行二次封裝,然而,此處代碼僅僅只是用Optional進行了處理,並未做更有效的處理,應該是Java8下的代碼調整。
@Nullable private Object wrapCacheValue(Method method, @Nullable Object cacheValue) { return method.getReturnType() != Optional.class || cacheValue != null && cacheValue.getClass() == Optional.class ? cacheValue : Optional.ofNullable(cacheValue); }
推測
JDK自帶的對象序列化技術,在對象轉為byte[]之后,內容中已經包含了全類名,byte[]轉為對象,可以自動轉型,不需要Class作為參數,。
Spring的Cache十分符合JDK的用法,傳統的緩存EhCache,設計上就采用了原生的序列化。
JSON是Douglas Crockford在2001年開始推廣使用的數據格式,在2005年-2006年正式成為主流的數據格式,
而Spring 框架最開始的部分是由Rod Johnson於2000年為倫敦金融界提供獨立咨詢業務時寫出來的。
兩者產生的時間差異,可能是這一現象的原因:
Spring重點對JDK原生代碼做了支持,因為JDK不需要Class作為參數,隨着第三方序列化的產生,盡管接口上已經設計好,但是並未改變原先的做法。
序列化測試代碼:
import java.io.*; /** * @author Mr.css * @date 2019/12/24 14:38 */ class A implements Serializable { } public class Test { public static void main(String[] args) throws IOException, ClassNotFoundException { // A a = new A(); // FileOutputStream is = new FileOutputStream("C:\\Users\\postm\\Desktop\\test.txt"); // try (ObjectOutputStream oos = new ObjectOutputStream(is)) { // oos.writeObject(a); // } FileInputStream is = new FileInputStream("C:\\Users\\postm\\Desktop\\test.txt"); ObjectInputStream ois = new ObjectInputStream(is); A a = (A)ois.readObject(); } }