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