Java geek
Related reading:
JetCache source code (2) Top view JetCache source code (3) Cache class structure and code parsing 1 JAVA basics (1) A simple, thorough understanding of inner classes and static inner classes JAVA Foundation (2) Memory optimization – Using JAVA references for caching JAVA foundation (3) Hot loading JAVA foundation (4) Enumeration (enum) and constant definition, factory class use contrast JAVA foundation (5) Functional interface – reuse, JAVA programming ideas (1) Increase extensibility through dependency Injection (2) How to program for interface (3) Remove awkward if, self-registration strategy mode elegant meet the open and close principle (4) JAVA programming ideas (Builder mode) classical paradigm and factory mode how to choose? HikariPool source code (two) design ideas for reference in the workplace (a) IT factory survival rules
1. Class structure
Abstract: AbstractEmbeddedCache contains two AbstractEmbeddedCache implementations, LRU cache and Caffeine cache
As always, the template pattern is used, but instead of implementing the template method directly in the subclass, the subclass creates the InnerMap instance, and then calls the InnerMap method at the call point of the template method: Keep need to subclass implementation of template method cohesion into a class, and relatively independent (directly in the subclass implementation also cohesive, but not independent), after the class independent, later if you want to expand, can be finished through independent factory class creation process, the factory class can also through dependency injection, greatly improving the flexibility and extensibility.
2. Core code analysis
2.1. AbstractEmbeddedCache. Java
AbstractEmbeddedCache implements some common methods to avoid repeated implementation by subclasses, and uses a template pattern to allow subclasses to implement the change-point part.
public abstract class AbstractEmbeddedCache<K.V> extends AbstractCache<K.V> {
// has its own configuration class
protected EmbeddedCacheConfig<K, V> config;
// Holds the InnerMap interface to which template methods are delegated
protected InnerMap innerMap;
// The subclass creates the InnerMap, making the template methods to be called cohesive and independent
protected abstract InnerMap createAreaCache(a);
public AbstractEmbeddedCache(EmbeddedCacheConfig<K, V> config) {
this.config = config;
// Template method pattern, implemented by subclasses
innerMap = createAreaCache();
}
@Override
public CacheConfig<K, V> config(a) {
return config;
}
// Public method
public Object buildKey(K key) {
Object newKey = key;
Function<K, Object> keyConvertor = config.getKeyConvertor();
if(keyConvertor ! =null) {
newKey = keyConvertor.apply(key);
}
return newKey;
}
@Override
protected CacheGetResult<V> do_GET(K key) {
Object newKey = buildKey(key);
// Delegate to innerMap. You can also define a template method, which is implemented by subclasses, but innerMap brings all the template methods to be implemented together and is more independent
CacheValueHolder<V> holder = (CacheValueHolder<V>) innerMap.getValue(newKey);
return parseHolderResult(holder);
}
protected CacheGetResult<V> parseHolderResult(CacheValueHolder<V> holder) {
long now = System.currentTimeMillis();
if (holder == null) {
return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;
} else if (now >= holder.getExpireTime()) {
return CacheGetResult.EXPIRED_WITHOUT_MSG;
} else {
synchronized (holder) {
long accessTime = holder.getAccessTime();
if (config.isExpireAfterAccess()) {
long expireAfterAccess = config.getExpireAfterAccessInMillis();
if (now >= accessTime + expireAfterAccess) {
return CacheGetResult.EXPIRED_WITHOUT_MSG;
}
}
holder.setAccessTime(now);
}
return new CacheGetResult(CacheResultCode.SUCCESS, null, holder); }}@Override
protected MultiGetResult<K, V> do_GET_ALL(Set<? extends K> keys) {
ArrayList<K> keyList = new ArrayList<K>(keys.size());
ArrayList<Object> newKeyList = new ArrayList<Object>(keys.size());
keys.stream().forEach((k) -> {
Object newKey = buildKey(k);
keyList.add(k);
newKeyList.add(newKey);
});
// Still delegate to innerMap
Map<Object, CacheValueHolder<V>> innerResultMap = innerMap.getAllValues(newKeyList);
Map<K, CacheGetResult<V>> resultMap = new HashMap<>();
for (int i = 0; i < keyList.size(); i++) {
K key = keyList.get(i);
Object newKey = newKeyList.get(i);
CacheValueHolder<V> holder = innerResultMap.get(newKey);
resultMap.put(key, parseHolderResult(holder));
}
MultiGetResult<K, V> result = new MultiGetResult<>(CacheResultCode.SUCCESS, null, resultMap);
return result;
}
// The last few methods are also common code + template methods that delegate to the innerMap call
Copy the code
2.2. LinkedHashMapCache. Java
Key points of 2.2.1.
// Define a member variable
private LRUMap lruMap;
@Override
protected InnerMap createAreaCache(a) {
// Assign to the private member first, then return.
lruMap = new LRUMap(config.getLimit(), this);
return lruMap;
}
public void cleanExpiredEntry(a) {
// This does not require strong rotation
lruMap.cleanExpiredEntry();
}
Copy the code
2.2.2. Code
package com.alicp.jetcache.embedded;
import com.alicp.jetcache.CacheResultCode;
import com.alicp.jetcache.CacheValueHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/ * * *@author <a href="mailto:[email protected]">huangli</a>
*/
public class LinkedHashMapCache<K.V> extends AbstractEmbeddedCache<K.V> {
private static Logger logger = LoggerFactory.getLogger(LinkedHashMapCache.class);
public LinkedHashMapCache(EmbeddedCacheConfig<K, V> config) {
super(config);
addToCleaner();
}
protected void addToCleaner(a) {
Cleaner.add(this);
}
@Override
protected InnerMap createAreaCache(a) {
// Define a private LRUMap member, assign it to the private member, and return it, instead of forcing it in the cleanExpiredEntry method below
return new LRUMap(config.getLimit(), this);
}
@Override
public <T> T unwrap(Class<T> clazz) {
if (clazz.equals(LinkedHashMap.class)) {
return (T) innerMap;
}
throw new IllegalArgumentException(clazz.getName());
}
public void cleanExpiredEntry(a) {
// When programming for interfaces, it feels awkward to be forced to use strongcasts because there are more methods in subclasses than in interfaces
// To avoid overcasting, you can define a private LRUMap member and copy it to it while creating the InnerMap, which is called by LRUMap's private person
((LRUMap) innerMap).cleanExpiredEntry();
}
/ / inner classes
final class LRUMap extends LinkedHashMap implements InnerMap {
private final int max;
// Can be defined directly in LinkedHashMapCache and referenced directly, without passing in the constructor
private Object lock;
LinkedHashMapCache; // This lock is an instance of LinkedHashMapCache.
// We want to lock based on the LinkedHashMapCache instance, although the actual locking is only used in the LRUMap instance method. To avoid further extension,
// Based on the principle of minimal visibility, it is also possible to define a lock directly in the LinkedHashMapCache, without passing it in through the constructor.
public LRUMap(int max, Object lock) {
super((int) (max * 1.4 f), 0.75 f.true);
this.max = max;
this.lock = lock;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > max;
}
// Clear the stale cache
void cleanExpiredEntry(a) {
synchronized (lock) {
for (Iterator it = entrySet().iterator(); it.hasNext();) {
Map.Entry en = (Map.Entry) it.next();
Object value = en.getValue();
if(value ! =null && value instanceof CacheValueHolder) {
CacheValueHolder h = (CacheValueHolder) value;
logger.info("CacheValueHolder: " + h.toString());
if(System.currentTimeMillis() >= h.getExpireTime()) { it.remove(); }}else {
// assert false
if (value == null) {
logger.error("key " + en.getKey() + " is null");
} else {
logger.error("value of key " + en.getKey() + " is not a CacheValueHolder. type=" + value.getClass());
}
}
}
}
}
@Override
public Object getValue(Object key) {
synchronized (lock) {
returnget(key); }}@Override
public Map getAllValues(Collection keys) {
Map values = new HashMap();
synchronized (lock) {
for (Object key : keys) {
Object v = get(key);
if(v ! =null) { values.put(key, v); }}}return values;
}
@Override
public void putValue(Object key, Object value) {
synchronized(lock) { put(key, value); }}@Override
public void putAllValues(Map map) {
synchronized (lock) {
Set<Map.Entry> set = map.entrySet();
for(Map.Entry en : set) { put(en.getKey(), en.getValue()); }}}@Override
public boolean removeValue(Object key) {
synchronized (lock) {
returnremove(key) ! =null; }}@Override
public void removeAllValues(Collection keys) {
synchronized (lock) {
for(Object k : keys) { remove(k); }}}@Override
@SuppressWarnings("unchecked")
public boolean putIfAbsentValue(Object key, Object value) {
synchronized (lock) {
CacheValueHolder h = (CacheValueHolder) get(key);
if (h == null || parseHolderResult(h).getResultCode() == CacheResultCode.EXPIRED) {
put(key, value);
return true;
} else {
return false;
}
}
}
}
}
Copy the code
2.3. CaffeineCache. Java
Key points of 2.3.1.
2.3.2. Code
package com.alicp.jetcache.embedded;
import com.alicp.jetcache.CacheValueHolder;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Created on 2016/10/25.
*
* @author <a href="mailto:[email protected]">huangli</a>
*/
public class CaffeineCache<K.V> extends AbstractEmbeddedCache<K.V> {
// Holds caffeine's cache instance to which method calls are ultimately delegated
private com.github.benmanes.caffeine.cache.Cache cache;
public CaffeineCache(EmbeddedCacheConfig<K, V> config) {
super(config);
}
@Override
public <T> T unwrap(Class<T> clazz) {
if (clazz.equals(com.github.benmanes.caffeine.cache.Cache.class)) {
return (T) cache;
}
throw new IllegalArgumentException(clazz.getName());
}
@Override
@SuppressWarnings("unchecked")
protected InnerMap createAreaCache(a) {
// build a model
Caffeine<Object, Object> builder = Caffeine.newBuilder();
builder.maximumSize(config.getLimit());
final boolean isExpireAfterAccess = config.isExpireAfterAccess();
final long expireAfterAccess = config.getExpireAfterAccessInMillis();
// Override the default by dependency injection
builder.expireAfter(new Expiry<Object, CacheValueHolder>() {
private long getRestTimeInNanos(CacheValueHolder value) {
long now = System.currentTimeMillis();
long ttl = value.getExpireTime() - now;
if(isExpireAfterAccess){
ttl = Math.min(ttl, expireAfterAccess);
}
return TimeUnit.MILLISECONDS.toNanos(ttl);
}
@Override
public long expireAfterCreate(Object key, CacheValueHolder value, long currentTime) {
return getRestTimeInNanos(value);
}
@Override
public long expireAfterUpdate(Object key, CacheValueHolder value,
long currentTime, long currentDuration) {
return currentDuration;
}
@Override
public long expireAfterRead(Object key, CacheValueHolder value,
long currentTime, long currentDuration) {
returngetRestTimeInNanos(value); }}); cache = builder.build();// Anonymous inner classes are used because the invocation relationship is relatively simple
return new InnerMap() {
@Override
public Object getValue(Object key) {
return cache.getIfPresent(key);
}
@Override
public Map getAllValues(Collection keys) {
return cache.getAllPresent(keys);
}
@Override
public void putValue(Object key, Object value) {
cache.put(key, value);
}
@Override
public void putAllValues(Map map) {
cache.putAll(map);
}
@Override
public boolean removeValue(Object key) {
returncache.asMap().remove(key) ! =null;
}
@Override
public void removeAllValues(Collection keys) {
cache.invalidateAll(keys);
}
@Override
public boolean putIfAbsentValue(Object key, Object value) {
return cache.asMap().putIfAbsent(key, value) == null; }}; }}Copy the code
3. Summary
1. When using the template method design pattern, all the template methods to be implemented can be clustered into a class, that is, the cohesive and relatively independent, coupled with injectable factory classes, which can further improve scalability.
2. Dependency injection is ubiquitous and a necessary pattern to provide high extensibility.
3. Interface oriented programming, but also to avoid strong transfer, the implementation is worth thinking about.
4. Builder mode compared to factory mode to create instances, is also very common, is a must design mode.
end.