上篇咱们介绍了Spring之Caffeine缓存的简单了解,这里仅仅是讲述了一个小小的应用场景及简化开发,关于更多其他应用接下来有时间再聊(比如:结合Spring-Data-Redis做多层缓存结构等等),今天咱们学习基于Google Guava来开发一款自己的轻量级内存缓存,像Caffeine一样最终交由Spring来管理咱们自己的缓存.
老习惯开发语言Kotlin硬刚:
1.缓存管理接口
/**
* 可维护缓存接口
*/
interface MaintainableCache<T> {
/**
* 添加缓存对象
*/
fun add(obj: T)
/**
* 删除缓存对象
*/
fun delete(obj: T)
/**
* 清空所有缓存
*/
fun clear()
}
/**
* 创建内存缓存接口缓存
*/
interface InternalCacheable<T> {
/**
* 创建唯一值缓存
*/
fun <K> uniqueCache(keyBuilder: Function<T,K>) : InternalCache<K,T>
/**
* 创建多值缓存
*/
fun <K> multipleCache(keyBuilder: Function<T,K>) : InternalCache<K, Collection<T>>
}
/**
* 根据key值查询数据
*/
interface InternalCache<K,V> {
/**
* 根据key获取对应值
*/
fun get(key: K): V?
/**
* 判断key是否存在
*/
fun ifPresent(key: K): Boolean
}
2.接口实现,唯一键值实现Map结构仅保证key唯一,value可能重复,此处使用了Google的HashBiMap来保证键值都唯一,当然实际应用中key值一般均为ID或代表唯一对象的标识,只要存储的value对象中含有唯一标识,可认为同一对象,当新值put进来时会替换之前的数据,以做到更新缓存的目的,无法辨别存储对象是否含有key同值的唯一标识时,此时,HashBiMap就起到了更新缓存的作用了.
/**
* 唯一键,值缓存实现
* 使用google guava HashBiMap 保证K,V存储唯一
*/
class UniqueCacheable<K,V>(var keyBuilder: Function<V,K>):InternalCache<K,V>,MaintainableCache<V> {
private var data = HashBiMap.create<K,V>()
companion object {
fun <K,V> create(keyBuilder: Function<V,K>) = UniqueCacheable(keyBuilder)
}
override fun get(key: K): V? = data[key]
override fun ifPresent(key: K)= data.containsKey(key)
override fun add(obj: V) {
val key = keyBuilder.apply(obj)
if (key.isNull()) return
data[key] = obj
}
override fun delete(obj: V) {
val key = keyBuilder.apply(obj)
if (key.isNull()) return
data.remove(key,obj)
}
override fun clear() {
data.clear()
}
}
/**
* 可维护缓存实现
*/
class MaintainableCacheable<T>:InternalCacheable<T>,MaintainableCache<T> {
private val list = mutableListOf<MaintainableCache<T>>()
override fun <K> uniqueCache(keyBuilder: Function<T, K>) = UniqueCacheable.create(keyBuilder).apply { list.add(this) }
override fun add(obj: T) = list.forEach { it.add(obj) }
override fun delete(obj: T) = list.forEach { it.delete(obj) }
override fun clear() = list.forEach { it.clear() }
}
3.Map实现缓存,这里的Map继承了Google的ForwardingMap,作为唯一键值实现,多值也可以继承ForwardingMultimap,Java类无多继承,所以本文只继承了ForwardingMap,而多值可以具体另外实现,有兴趣同学可以尝试下一键多值.此处仅仅是以map为结构的缓存策略,如有其他(如:List,Set)也可以具体实现,需要说明的是除map结构外,其他结构类型在add数据之前应先remove之前数据,否则无法保证键值唯一.
class CacheableMap<K,V>(var data: Map<K,V>): ForwardingMap<K,V>(),InternalCacheable<V> {
private val maintainableCacheable = MaintainableCacheable<V>()
companion object {
fun <K,V> create() = create(mutableMapOf<K,V>())
fun <K,V> create(data: Map<K,V>) = CacheableMap(data)
}
override fun delegate() = data
override fun <K> uniqueCache(keyBuilder: Function<V, K>) = maintainableCacheable.uniqueCache(keyBuilder)
//TODO implement multipleCache interface
override fun clear() {
super.clear()
maintainableCacheable.clear()
}
override fun put(key: K, value: V) =
super.put(key, value).apply {
// List/Set remove old value
maintainableCacheable.add(value)
}
override fun remove(key: K) = super.remove(key).apply {
if (this.isNotNull()) maintainableCacheable.delete(this!!)
}
}
4.使用示例中,对象a可以认为是old data,b为new data当创建了uniqueCache和multipleCache查看输出的不同,有兴趣的同学,可以另创建不含有id,名称相同的dto,看是否能存储到内存缓存中.
fun main(args: Array<String>) {
val caches = CacheableMap.create<Long,A>()
val a = A(1,"张三")
val b = A(1,"李四")
val unique = caches.uniqueCache(Function(A::id))
var multi = caches.multipleCache(Function(A::id))
caches[a.id] = a
caches[a.id] = b
println("unique cache is ${unique.get(a.id)}")
println("multi cache is ${multi.get(a.id)}")
}
class A(
var id: Long = 0,
var a: String=""
):Serializable{
override fun toString(): String {
return "A(id=$id, a='$a')"
}
}
5.测试结果:
unique cache is A(id=1, a='李四')
multi cache is [A(id=1, a='张三'), A(id=1, a='李四')]
6.测试结果分析uniqueCache:key相同时,新数据会覆盖老数据;multipleCache会放入集合之中.