SpringBoot2(九)Cache接口get函數無效問題


原本想基於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();
  }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM