一、背景
在平时开发过程中,通常我们会选择使用 Guava 的 Cache 类用作本地缓存。
但有些时候,我们不需要过期控制,不想引入 Guava 包,我们也会选择简单粗暴的使用 Map 作缓存。
但是,在某些业务场景下,需要在 Map 的属性发生变更时,做一些特殊处理。
比如 Map 发生不变化时,要基于 Map 的值生成一份不可变 List。
二、方法
尝试使用 Guava
的 Cache
类,发现 CacheBuilder
构造 Cache
时,只提供了 RemovalListener
用于移除或替换元素的监听,并没有提供新增元素、清空map 等事件的监听。
因此,借鉴 SynchronizedList
的思想,即 SynchronizedList
作为包装器内部持有 list 的引用,这种思路,我们可以对 Map 进行改造。
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return list.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return list.hashCode();}
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
public int indexOf(Object o) {
synchronized (mutex) {return list.indexOf(o);}
}
// 省略其他
}
参考代码如下:
构造一个 ModifyHookMap
类,可以通过 Builder 传入底层使用的 Map 和需要感知的回调函数。
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Pair<K, V> {
private K key;
private V value;
}
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Triple<F, S, T> {
private F first;
private S second;
private T third;
}
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
public class ModifyHookMap<K, V> implements Map<K, V> {
private final Map<K, V> map;
private final Consumer<ModifyHookMap<K, V>> onModified;
private final Consumer<Triple<K, V, V>> onPut;
private final Consumer<Pair<K, V>> onRemove;
private final Consumer<Map<? extends K, ? extends V>> onPutAll;
private final Consumer<Void> onClear;
private ModifyHookMap(Builder<K, V> builder) {
map = builder.map;
Consumer<Map<K, V>> onInit = builder.onInit;
onModified = builder.onModified;
onPut = builder.onPut;
onRemove = builder.onRemove;
onPutAll = builder.onPutAll;
onClear = builder.onClear;
if (onInit != null) {
onInit.accept(map);
}
if (onModified != null) {
onModified.accept(this);
}
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return map.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
@Override
public V get(Object key) {
return map.get(key);
}
@Override
public V put(K key, V value) {
V old = map.put(key, value);
if (onPut != null) {
onPut.accept(new Triple<>(key, value, old));
}
if (onModified != null) {
onModified.accept(this);
}
return old;
}
@Override
public V remove(Object key) {
V remove = map.remove(key);
if (onRemove != null) {
onRemove.accept(new Pair<>((K) key, remove));
}
if (onModified != null) {
onModified.accept(this);
}
return remove;
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
map.putAll(m);
if (onPutAll != null) {
onPutAll.accept(m);
}
if (onModified != null) {
onModified.accept(this);
}
}
@Override
public void clear() {
map.clear();
if (onClear != null) {
onClear.accept(null);
}
if (onModified != null) {
onModified.accept(this);
}
}
@Override
public Set<K> keySet() {
return map.keySet();
}
@Override
public Collection<V> values() {
return map.values();
}
@Override
public Set<Entry<K, V>> entrySet() {
return map.entrySet();
}
public static final class Builder<K, V> {
private Map<K, V> map;
private Consumer<Map<K, V>> onInit;
private Consumer<ModifyHookMap<K, V>> onModified;
private Consumer<Triple<K, V, V>> onPut;
private Consumer<Pair<K, V>> onRemove;
private Consumer<Map<? extends K, ? extends V>> onPutAll;
private Consumer<Void> onClear;
public Builder() {
}
public Builder<K, V> map(Map<K, V> val) {
map = val;
return this;
}
public Builder<K, V> onInit(Consumer<Map<K, V>> val) {
onInit = val;
return this;
}
public Builder<K, V> onModified(Consumer<ModifyHookMap<K, V>> val) {
onModified = val;
return this;
}
public Builder<K, V> onPut(Consumer<Triple<K, V, V>> val) {
onPut = val;
return this;
}
public Builder<K, V> onRemove(Consumer<Pair<K, V>> val) {
onRemove = val;
return this;
}
public Builder<K, V> onPutAll(Consumer<Map<? extends K, ? extends V>> val) {
onPutAll = val;
return this;
}
public Builder<K, V> onClear(Consumer<Void> val) {
onClear = val;
return this;
}
public ModifyHookMap<K, V> build() {
if (map == null) {
throw new IllegalArgumentException("map 不能为空");
}
return new ModifyHookMap<K, V>(this);
}
}
}
测试代码如下(时间仓促,这里不使用 JUnit 单测,简单使用 main方法):
import com.alibaba.fastjson.JSON;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Demo {
private static List<Integer> cache = new ArrayList<Integer>();
private static ModifyHookMap<String, Integer> mapWrapper = new ModifyHookMap
.Builder<String, Integer>()
.map(new HashMap<>())
.onInit((Map<String, Integer> map) -> {
System.out.println("执行创建, map:" + JSON.toJSONString(map));
})
.onClear((v) -> {
System.out.println("执行 clear");
})
.onRemove((Pair<String, Integer> pair) -> {
System.out.println("执行 remove,k:" + pair.getKey() + ";v" + pair.getValue());
})
.onPut((Triple<String, Integer, Integer> triple) -> {
System.out.println("执行 put,k:" + triple.getFirst() + ";v new:" + triple.getSecond() + ",v old:" + triple.getThird());
})
.onPutAll((Map<? extends String, ? extends Integer> map) -> {
System.out.println("执行 putAll:" + JSON.toJSONString(map));
})
.onModified((modifyInvokeMap -> {
cache = ImmutableList.copyOf(modifyInvokeMap.values());
System.out.println("执行 修改操作" + JSON.toJSONString(cache));
}))
.build();
public static void main(String[] args) {
mapWrapper.put("1", 1);
mapWrapper.put("1", 3);
Map<String, Integer> otherMap = new HashMap<>();
otherMap.put("2", 2);
mapWrapper.putAll(otherMap);
mapWrapper.remove("2");
mapWrapper.clear();
}
}
输出:
执行创建, map:{}
执行 修改操作[]
执行 put,k:1;v new:1,v old:null
执行 修改操作[1]
执行 put,k:1;v new:3,v old:1
执行 修改操作[3]
执行 putAll:{“2”:2}
执行 修改操作[3,2]
执行 remove,k:2;v2
执行 修改操作[3]
执行 clear
执行 修改操作[]
需要注意的是,此时 Consumer
里面如果传入的 key
或者 value
进行修改会影响到底层 Map
的数据。
因此,只建议在 Consumer
里执行读操作,不要执行修改操作。
三、总结
本文通过借鉴 SynchronizedList
实现了自己的带回调的 Map
。
想表达的是,开源代码不是万能的,当开源代码无法满足时,有时候需要我们自己编写特定逻辑来满足业务需求。
此外,学习的价值在于 学以致用,我们要尝试将所需知识灵活运用来解决业务问题,才是能真正发挥读源码的价值。
如果你有更好的处理方式,欢迎评论和我讨论。