백엔드 작업 하면서 내부 변동성 없는 데이터를 DB를 통해 아주 자주~ 자주 호출 하는 구분이 생겼다.
캐싱 어노테이션을 이용해 보려 했지만 그낙 내 맘에 들지는 않았다.
일정 시간이 지나면 원하는 것만 캐시 내용이 제거 됬으면 하는 내 소망이였다. 그래서 만들었다.import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * 일정 시간 지나면 항목 제거 Map<P> * 항목을 추가하면 내부에 runnable이 생성되고 interval 시간에 삭제 처리가 이뤄 진다. * @param <K> * @param <V> * @see {@link RemoveEventListener} */ public class CacheIntervalMap<K, V> extends ConcurrentHashMap<K, V>{ private static final long serialVersionUID = -3116825266165827645L; /** 스케줄러 서비스 */ private final ScheduledExecutorService scheduleExe; /** 지속 시간 */ private final long interval; /** 지속 시간 단위 */ private final TimeUnit unit; /** 자동 삭제하기 전 문의하기 이벤트 리스너 */ private final RemoveEventListener<K, V> listener; /** 중간 취소 처리 위한 관리 객체 */ private final Map<K, Future<K>> callManage; /** * 일정 시간이 지나면 제거 * @param <K> * @param <V> */ private static class RemoverCashIntervalMap<K, V> implements Callable<K>{ private final CacheIntervalMap<K, V> owner; private final K key; public RemoverCashIntervalMap(CacheIntervalMap<K, V> owner, K key) { this.owner = owner; this.key = key; } @Override public K call() throws Exception { System.out.println("자동 삭제 " + this.key ); if( ! this.owner.containsKey(this.key) ) return this.key; // 일단 지우고 나서 리스너로 문의해 본다. final V value = this.owner.remove(this.key); if( this.owner.listener == null ) return this.key; if( ! this.owner.listener.isRemove(this.key, value) ) { // 이미 삭제된거 어쩔 수 없다. 다시 추가~! this.owner.put(this.key, value); } return this.key; } } /** * 삭제하기 * @param interval 일정시간 * @param unit 시간 단위 */ public CacheIntervalMap(long interval, TimeUnit unit) { this(interval, unit, null); } /** * 삭제하기 * @param interval 일정시간 * @param unit 시간 단위 * @param listener 삭제할지 말지 결정. */ public CacheIntervalMap(long interval, TimeUnit unit, RemoveEventListener<K, V> listener) { super(); this.interval = interval; this.unit = unit; this.listener = listener; this.scheduleExe = Executors.newSingleThreadScheduledExecutor(); this.callManage = new ConcurrentHashMap<K, Future<K>>(); } @Override public V remove(Object key) { this.callManage.remove( key ).cancel(false); return super.remove(key); } @Override public V put(K key, V value) { this.callManage.put(key, this.scheduleExe.schedule( new RemoverCashIntervalMap<K,V>(this, key), interval, unit) ); return super.put(key, value); } @Override public void putAll(Map<? extends K, ? extends V> m) { for( K key : m.keySet() ) { this.callManage.put(key, this.scheduleExe.schedule( new RemoverCashIntervalMap<K,V>(this, key), interval, unit) ); } super.putAll(m); } @Override public void clear() { for( Future<K> k : this.callManage.values() ) { k.cancel(true); } this.callManage.clear(); super.clear(); } } |
이벤트 클래스
/** * 삭제 되기 전 발생하는 이벤트 * @param <K> * @param <V> * @see {@link CacheIntervalMap} */ public interface RemoveEventListener<K, V> { /** * 삭제 되기전 문의 하기~ * @param key * @param value * @return true: 삭제해라. false:다음에 삭제할까 생각해 볼께. */ public boolean isRemove(K key, V value); } |
final Map<String, Integer> testExe = new CacheIntervalMap<String, Integer>(5, TimeUnit.SECONDS, new RemoveEventListener<String, Integer>() { @Override public boolean isRemove(String key, Integer value) { // BB는 삭제하지 않는다. return !"BB".equals(key); } }); testExe.put("AA", 100); testExe.put("BB", 200); testExe.put("CC", 300); |
결과
자동 삭제 AA 자동 삭제 BB 자동 삭제 CC 자동 삭제 BB 자동 삭제 BB 자동 삭제 BB |
출처 | 내 머리. |