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();
  }
}
原文地址:https://www.cnblogs.com/chenss15060100790/p/12094489.html