2023年11月29日发(作者:)

SpringBoot(九)Cache接⼝get函数⽆效问题

原本想基于Lettuce,⾃⼰写⼀个Redis的Cache,⾃定义Cache难度不⾼,但是在编码过程中,发现get(Object key, Class aClass)

函数从未被调⽤过,导致计划迟迟未完成。

⾃定义Cache的基本代码

package ;

import Const;

import ter;

import eption;

import ;

import ValueWrapper;

import p;

import ;

import le;

/**

*

*/

public class RedisCache implements Cache {

Map map = new HashMap<>();

/**

* 简单直⽩,就是获取Cache的名字

*/

@Override

public String getName() {

return _DEF;

}

/**

* 获取底层的缓存实现对象

*/

@Override

public Object getNativeCache() {

return _DEF;

}

/**

* 根据键获取值,把值包装在ValueWrapper⾥⾯,如果有必要可以附加额外信息

*/

@Override

public ValueWrapper get(Object key) {

n("ValueWrapper");

throw new BizException("test");

// return nsKey(key)?new SimpleValueWrapper((key)):null;

}

/**

* 从缓存中获取 key 对应的值,如果缓存没有命中,则添加缓存,

* 此时可异步地从 valueLoader 中获取对应的值(4.3版本新增)

* 与缓存标签中的sync属性有关

*/

@Override

public T get(Object key, Callable valueLoader) {

try {

n("get");

return ();

} catch (Exception e) {

tackTrace();

}

return null;

}

/**

* 存放键值对

*/

@Override

public void put(Object key, Object value) {

n("put(Object key, Object value)");

(key, value);

}

/**

因此,Cache接⼝中,get(Object key, Class aClass)是我们最希望被调⽤的函数,⽽实际使⽤中,此函数却从未触发过,这是为何

(后⾯提到的get函数,均指此函数,重点分析此函数不被调⽤的原因)

@Override

public T get(Object key, Class aClass) {

try {

n("get(Object o, Class aClass)");

return nsKey(key)?t((key), aClass):null;

} catch (Exception e) {

tackTrace();

}

return null;

}

分析

打印异常栈,分析源码,核⼼异常如下:

ionHandler(rvletRequest,.

HttpServletResponse,eption) throws ption

eption: test

at (:43)

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {

public CacheInterceptor() {

}

@Nullable

public Object invoke(MethodInvocation invocation) throws Throwable {

Method method = hod();

CacheOperationInvoker aopAllianceInvoker = () -> {

try {

return d();

} catch (Throwable var2) {

throw new ThrowableWrapper(var2);

}

};

try {

//只是负责调⽤起CacheAspectSupport,本⾝代码较少

return this.execute(aopAllianceInvoker, s(), method, uments());

} catch (ThrowableWrapper var5) {

throw ginal();

}

}

}

AbstractCacheInvoker

Cache处理器,对Cache包裹了⼀层代码,负责调⽤起Cache的相关函数,从这⾥可以看出⼀部分问题了,代码⾃始⾄终没调⽤过get函数

public abstract class AbstractCacheInvoker {

protected SingletonSupplier errorHandler;

protected AbstractCacheInvoker() {

this.errorHandler = (SimpleCacheErrorHandler::new);

}

protected AbstractCacheInvoker(CacheErrorHandler errorHandler) {

this.errorHandler = (errorHandler);

}

public void setErrorHandler(CacheErrorHandler errorHandler) {

this.errorHandler = (errorHandler);

}

public CacheErrorHandler getErrorHandler() {

return (CacheErrorHandler)this.();

}

@Nullable

protected ValueWrapper doGet(Cache cache, Object key) {

try {

return (key);

} catch (RuntimeException var4) {

this.getErrorHandler().handleCacheGetError(var4, cache, key);

return null;

}

}

protected void doPut(Cache cache, Object key, @Nullable Object result) {

try {

(key, result);

} catch (RuntimeException var5) {

this.getErrorHandler().handleCachePutError(var5, cache, key, result);

}

@Nullable

private Object execute(CacheOperationInvoker invoker, Method method, perationContexts contexts) {

if (hronized()) {

perationContext context =

(perationContext)(CacheableOperation.class).iterator().next();

if (this.isConditionPassing(context, _RESULT)) {

Object key = this.generateKey(context, _RESULT);

Cache cache = (Cache)hes().iterator().next();

try {

//同步调⽤,调⽤CacheCallbackget函数

return this.wrapCacheValue(method, (key, () -> {

return this.unwrapReturnValue(this.invokeOperation(invoker));

}));

} catch (ValueRetrievalException var10) {

throw (ThrowableWrapper)se();

}

} else {

return this.invokeOperation(invoker);

}

} else {

this.processCacheEvicts((CacheEvictOperation.class), true, _RESULT);

//异步调⽤,调⽤get函数,返回ValueWrapper

ValueWrapper cacheHit = this.findCachedItem((CacheableOperation.class));

List cachePutRequests = new LinkedList();

if (cacheHit == null) {

this.collectPutRequests((CacheableOperation.class), _RESULT, cachePutRequests);

}

Object cacheValue;

Object returnValue;

if (cacheHit != null && !this.hasCachePut(contexts)) {

//从缓存命中到Value

cacheValue = ();

//Value格式化

returnValue = this.wrapCacheValue(method, cacheValue);

} else {

//从缓存未命中到Value

returnValue = this.invokeOperation(invoker);

cacheValue = this.unwrapReturnValue(returnValue);

}

//put函数调⽤

this.collectPutRequests((CachePutOperation.class), cacheValue, cachePutRequests);

Iterator var8 = or();

while(t()) {

//put函数调⽤

utRequest cachePutRequest = (utRequest)();

(cacheValue);

}

//evict函数调⽤

this.processCacheEvicts((CacheEvictOperation.class), false, cacheValue);

return returnValue;

}

}

其中最可疑的wrapCacheValue,作⽤是包装缓存值,合理的代码逻辑,会对返回值进⾏⼆次封装,然⽽,此处代码仅仅只是⽤Optional

进⾏了处理,并未做更有效的处理,应该是Java8下的代码调整。

@Nullable

private Object wrapCacheValue(Method method, @Nullable Object cacheValue) {

return urnType() != Optional.class || cacheValue != null && ss() == Optional.class ? cacheValue :

able(cacheValue);

}

推测

JDK⾃带的对象序列化技术,在对象转为byte[]之后,内容中已经包含了全类名,byte[]转为对象,可以⾃动转型,不需要Class作为参

数,。

Spring的Cache⼗分符合JDK的⽤法,传统的缓存EhCache,设计上就采⽤了原⽣的序列化。

JSON是Douglas Crockford在2001年开始推⼴使⽤的数据格式,在2005年-2006年正式成为主流的数据格式,

⽽Spring 框架最开始的部分是由Rod Johnson于2000年为伦敦⾦融界提供独⽴咨询业务时写出来的。

两者产⽣的时间差异,可能是这⼀现象的原因:

Spring重点对JDK原⽣代码做了⽀持,因为JDK不需要Class作为参数,随着第三⽅序列化的产⽣,尽管接⼝上已经设计好,但是并未改变

原先的做法。

序列化测试代码:

import .*;

/**

* @author

* @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:");

// try (ObjectOutputStream oos = new ObjectOutputStream(is)) {

// bject(a);

// }