一个请求级别的缓存实现

云程序员 2021年02月01日 15次浏览

需求

我们经常会遇到一种情况,就是本地多个方法会重复用到一个计算后的对象。
这种情况,常用的方式有四种

  1. 直接把对象在方法中传递

  2. 类中添加成员变量,在各个方法中引用

  3. 使用ThreadLocal来进行存取

  4. 缓存

但四种方法都存在一点问题

  1. 对象在方法中传递,这个代码的重复率实在了。

  2. 在spring的单例中,成员变量在service中应该是不变的,要设计一个很好的类和方法,传service进去再处理,也不是一件简单的事情

  3. ThreadLocal相对比较直观,处理完成后就set,需要用就get,但一个ThreadLocal很多时候显式的get和set方法进行单个对象的存取,这样代码的重复率特别高

  4. 普通缓存结构就是key-value的模式,多线程甚至多进程贡献

有没有办法设计一个简单易用的方法呢?

需求实现

着手思考

既然是请求级别的缓存,自然就不存在多线程甚至多进程的问题,自然就排除了redis等组件,我们还是选用了ThreadLocal中保存一个Map的方式,其实多线程的问题也不会出现,使用HashMap也就可以了,代码中是ConcurrentHashMap(只为后面引出一个现成的缓存类)

代码过程

我们先定义最一个简单的缓存类,有get和put方法,分别代表保存缓存和取出缓存

public class DemoCache {
    ThreadLocal<ConcurrentHashMap> threadLocal = new ThreadLocal<ConcurrentHashMap>();
   
    public void put(String key ,Object value){
        threadLocal.get().put(key,value);
    }

    public Object get(String key){
        return threadLocal.get().get(key);
    }
    //... more function
}

再编写一个业务类

public class CacheTest {
    DemoCache demoCache = new DemoCache();
    public Object getSomething(){
        //继续其他逻辑
        Object o = demoCache.get("something");
        if(o==null){
             // 业务实际获取数据
            o = 1;
            demoCache.put("something",o);
        }
        return o;
    }
    
    public Object getSomething2(){
        //继续其他逻辑
        Object o = demoCache.get("something2");
        if(o==null){
           // 业务实际获取数据
            o = 2;
            demoCache.put("something2",o);
        }
        return o;
    }
}

这个例子看起来每个方法都要写一遍获取,操作,保存。我们可以使用代理的方式
可以通过两种方式进行小优化

  1. 我们可以每次都传入一个类似回调方法来进行获取对象。
public class CacheTest {
    public Object getSomething(CallBack c){
            //继续其他逻辑
            Object o = demoCache.get("something2");
            if(o==null){
               // 业务实际获取数据
                o = c.callback();
                demoCache.put("something2",o);
            }
            return o;
        }
}
  1. 使用静态代理的方式
public class CacheTest {
    private CallBack c;
    
    CacheTest(CallBack c){
        this.c = c;
    }
    
    public Object getSomething(){
            //继续其他逻辑
            Object o = demoCache.get("something2");
            if(o==null){
               // 业务实际获取数据
                o = c.callback();
                demoCache.put("something2",o);
            }
    }
}

虽然代码有所优化,但当我们需要代理的对象增多时,就要为每一个代理对象都提供一个代理类,这样系统中的类个数就会很多;就需要编译很多次,很浪费系统资源,我们可以使用动态代理的方式进行替代,而Spring中的AOP也进行了动态代理的实现。

这样代码就变成了

public class CacheTest {
    @CacheAnnotation
    public Object getSomething(){
        Integer o = 1;
        return o;
    }
}
@Aspect
public class AnnotationTestAspect {
    DemoCache demoCache = new DemoCache();
	@Around("@annotation(CacheAnnotation)")
	public Object around(ProceedingJoinPoint pjp) throws Throwable {
		Object result = demoCache.get("someKey");
		if(result == null) {
			result = pjp.proceed();
			demoCache.put("someKey",result);
		}
		System.out.println("this is after around advice");
		return result;
	}

}

我们持续添加为缓存类添加可能需要的方法

public class DemoCache {
    ThreadLocal<ConcurrentHashMap> threadLocal = new ThreadLocal<ConcurrentHashMap>();

    public void put(String key ,Object value){
        threadLocal.get().put(key,value);
    }

    public Object get(String key){
        return threadLocal.get().get(key);
    }

    public Object evict(String key){
        return threadLocal.get().remove(key);
    }

    //... more function
}

是不是觉不觉得这个类有点熟悉?没错,这个其实就是Spring的cache类。直接使用spring的cache接口去实现,就不需要在这里想需要什么方法了。

于是,cache类大概就演变成了这样

public  class ThreadLocalMapCache implements Cache {

	private final String name;
	private final boolean allowNullValues;

	ThreadLocal<ConcurrentHashMap<Object,Object>> threadLocal = new ThreadLocal<>();

	@Nullable
	private final SerializationDelegate serialization;

	public ThreadLocalMapCache(String name) {
		this(name, true);
	}

	public ThreadLocalMapCache(String name, boolean allowNullValues) {
		this(name, allowNullValues, null);
	}

	protected ThreadLocalMapCache(String name, boolean allowNullValues, @Nullable SerializationDelegate serialization) {
		this.allowNullValues = allowNullValues;
		Assert.notNull(name, "Name must not be null");
		this.name = name;
		this.serialization = serialization;
	}

	public final boolean isStoreByValue() {
		return (this.serialization != null);
	}

	@Override
	public final String getName() {
		return this.name;
	}

	@Override
	public final ConcurrentMap<Object, Object> getNativeCache() {
		return threadLocal.get();
	}

	public ValueWrapper get(Object key) {
		Object value = lookup(key);
		return toValueWrapper(value);
	}

	@Override
	@SuppressWarnings("unchecked")
	@Nullable
	public <T> T get(Object key, @Nullable Class<T> type) {
		Object value = fromStoreValue(lookup(key));
		if (value != null && type != null && !type.isInstance(value)) {
			throw new IllegalStateException(
					"Cached value is not of required type [" + type.getName() + "]: " + value);
		}
		return (T) value;
	}


	@Nullable
	protected Object lookup(Object key) {
		return threadLocal.get().get(key);
	}

	@SuppressWarnings("unchecked")

	@Nullable
	public <T> T get(Object key, Callable<T> valueLoader) {
		return (T) fromStoreValue(threadLocal.get().computeIfAbsent(key, k -> {
			try {
				return toStoreValue(valueLoader.call());
			} catch (Throwable ex) {
				throw new ValueRetrievalException(key, valueLoader, ex);
			}
		}));
	}
	@Nullable
	protected Object fromStoreValue(@Nullable Object storeValue) {
		if (this.allowNullValues && storeValue == NullValue.INSTANCE) {
			return null;
		}
		return storeValue;
	}


	@Override
	public void put(Object key, @Nullable Object value) {
		threadLocal.get().put(key, toStoreValue(value));
	}

	@Override
	@Nullable
	public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
		Object existing = threadLocal.get().putIfAbsent(key, toStoreValue(value));
		return toValueWrapper(existing);
	}

	protected Cache.ValueWrapper toValueWrapper(@Nullable Object storeValue) {
		return (storeValue != null ? new SimpleValueWrapper(fromStoreValue(storeValue)) : null);
	}
	@Override
	public void evict(Object key) {
		threadLocal.get().remove(key);
	}

	@Override
	public boolean evictIfPresent(Object key) {
		return (threadLocal.get().remove(key) != null);
	}

	@Override
	public void clear() {
		threadLocal.get().clear();
	}

	@Override
	public boolean invalidate() {
		boolean notEmpty = !threadLocal.get().isEmpty();
		threadLocal.get().clear();
		return notEmpty;
	}


	protected Object toStoreValue(@Nullable Object userValue) {
		if (userValue == null) {
			if (this.allowNullValues) {
				return NullValue.INSTANCE;
			}
			throw new IllegalArgumentException(
					"Cache '" + getName() + "' is configured to not allow null values but null was provided");
		}
		return userValue;
	}
}



这个时候,cache类就已经接近完工了,但仔细想想,我们真的需要自己再实现一遍HashMap缓存的存取吗?回头看看Cache的其他实现,其中ConcurrentMapCache的实现就是我们要的答案。所以我们使用ConcurrentMapCache来代替ConcurrentMap

public  class ThreadLocalMapCache implements Cache {

	private final String name;

     public static final ThreadLocal<ConcurrentMapCache> threadLocal = new ThreadLocal<>();



	@Nullable
	private final boolean allowNullValues;

	public ThreadLocalMapCache(String name) {
		this(name, true);
	}

	public ThreadLocalMapCache(String name, boolean allowNullValues) {
		this.name = name;
		this. allowNullValues =  allowNullValues;
	}





	@Override
	public final String getName() {
		return this.name;
	}

	@Override
	public final ConcurrentMapCache getNativeCache() {
		if(threadLocal.get() == null){
			threadLocal.set(new  ConcurrentMapCache(Thread.currentThread().getName(),allowNullValues));
		}
		return threadLocal.get();
	}

	@Override
	public ValueWrapper get(Object key) {
		return getNativeCache().get(key);
	}

	@Override
	public <T> T get(Object key, Class<T> type) {
		return getNativeCache().get(key,type);
	}


	@SuppressWarnings("unchecked")
	@Override
	@Nullable
	public <T> T get(Object key, Callable<T> valueLoader) {
		return getNativeCache().get(key, valueLoader);
	}

	@Override
	public void put(Object key, @Nullable Object value) {
		getNativeCache().put(key, value);
	}

	@Override
	@Nullable
	public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
		return getNativeCache().putIfAbsent(key,value);
	}

	@Override
	public void evict(Object key) {
		getNativeCache().evict(key);
	}

	@Override
	public boolean evictIfPresent(Object key) {
		return getNativeCache().evictIfPresent(key);
	}

	@Override
	public void clear() {
		getNativeCache().clear();
	}

	@Override
	public boolean invalidate() {
		return getNativeCache().invalidate();
	}


}

按spring的使用规则,我们添加一个cache管理类

@Component(ThreadLocalCacheManager.NAME)
public class ThreadLocalCacheManager extends AbstractCacheManager{

	public static final String NAME = "threadLocalCacheManage";

	@Override
	protected Collection<? extends Cache> loadCaches() {
		return new LinkedList<>();
	}

	@Override
	protected Cache getMissingCache(String cacheName) {
		ThreadLocalMapCache cache = new ThreadLocalMapCache(cacheName, true);
		return cache;
	}

}

目前为止,我们已经完成了线程级别的缓存,要完成请求级别的缓存,我们需要在出入口处清除缓存

@RestControllerAdvice
public class RequestScopeCacheInterceptor implements HandlerInterceptor {



	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		return true;
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ConcurrentMapCache cache = ThreadLocalMapCache.threadLocal.get();
        if (cache != null) {
            cache.clear();
        }
	}


}

这个请求级别缓存类已经完成开发,是将整个对象作为一个ThreadLocalMapCache作为一个cache提供给缓存管理器管理。

再回看一下代码,发觉我们完成的并不像一个Cache(缓存类)的功能,更像一个CacheManager缓存管理器的功能,所以我们再将多个缓存的管理放到管理器去。

如下面的代码,不过这样会有一个问题就是getCache一直为空(配置的是固定字符,存取的时候为线程Id),最终会调用getMissingcache来进行获取

@Component(ThreadLocalCacheManager2.NAME)
@Primary
public class ThreadLocalCacheManager2 extends AbstractCacheManager{

	public static final ThreadLocal<ConcurrentMapCache> threadLocal = new ThreadLocal<>();

	public static final String NAME = "threadLocalCacheManage2";

	@Override
	protected Collection<? extends Cache> loadCaches() {
		return new LinkedList<>();
	}

	@Override
	protected Cache getMissingCache(String cacheName) {
		ConcurrentMapCache cache = threadLocal.get();
		if(cache == null){
			cache = new ConcurrentMapCache(Thread.currentThread().getName(), true);
			threadLocal.set(cache);
		}
		return cache;
	}

}

总结

瞎折腾