软件简介
Caffeine 是基于Java 8的高性能,接近最佳的缓存库。
本来也没用过,JF内置的EhCache对于我们业务是绰绰有余。
前几天有社友反馈说demo,就查了一下资料,网友说是这玩意性能还非常高,比EhCache还高不少。
今天休息,想着敲点代码放松一下,就来模仿模仿。废话不多说,直接 上 石马 ~
导包加依赖 pom.xml
<!-- caffeine 缓存 --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.0.5</version> </dependency>
3.0及以上版本,Java jdk最低版本是11,如果项目使用低版本jdk,请尝试降低caffeine版本, 如jdk8用2.9.3。
先来个CaffeineKit 模仿复制JF的CacheKit代码
package com.jfinal.plugin.caffeine;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.jfinal.log.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
/**
* Caffeine 缓存
* @author 杜福忠
*/
public class CaffeineKit {
private static final Log log = Log.getLog(CaffeineKit.class);
private static final ConcurrentHashMap<String, Cache> CACHES = new ConcurrentHashMap();
public static Cache putCache(String cacheName, Cache cache){
return CACHES.put(cacheName, cache);
}
public static Cache getCache(String cacheName){
return CACHES.get(cacheName);
}
public static Cache removeCache(String cacheName){
return CACHES.remove(cacheName);
}
static Cache getOrAddCache(String cacheName) {
Cache cache = CACHES.get(cacheName);
if (cache == null) {
synchronized(CaffeineKit.class) {
cache = CACHES.get(cacheName);
if (cache == null) {
log.warn("Could not find cache config [" + cacheName + "], using default.");
cache = Caffeine.newBuilder().build();
CACHES.put(cacheName, cache);
log.debug("Cache [" + cacheName + "] started.");
}
}
}
return cache;
}
public static void put(String cacheName, Object key, Object value) {
if(value != null){
getOrAddCache(cacheName).put(key, value);
} else {
remove(cacheName, key);
}
}
@SuppressWarnings("unchecked")
public static <T> T get(String cacheName, Object key) {
return (T) getOrAddCache(cacheName).getIfPresent(key);
}
@SuppressWarnings("rawtypes")
public static List getKeys(String cacheName) {
return new ArrayList(asMap(cacheName).keySet());
}
public static ConcurrentMap asMap(String cacheName) {
return getOrAddCache(cacheName).asMap();
}
public static void remove(String cacheName, Object key) {
getOrAddCache(cacheName).invalidate(key);
}
public static void removeAll(String cacheName) {
getOrAddCache(cacheName).invalidateAll();
}
@SuppressWarnings("unchecked")
public static <T> T get(String cacheName, Object key, Function mappingFunction) {
return (T)getOrAddCache(cacheName).get(key, mappingFunction);
}
@SuppressWarnings("unchecked")
public static <T> T get(String cacheName, Object key, Class<? extends Function> dataLoaderClass) {
try {
Function dataLoader = dataLoaderClass.getDeclaredConstructor().newInstance();
return (T)getOrAddCache(cacheName).get(key, dataLoader);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}OK,测试测试
package com.jfinal.plugin.caffeine;
import com.jfinal.kit.Ret;
import java.util.List;
public class TestCaffeine {
static String cacheName = "man";
static String key = "a";
static Object v;
public static void main(String[] args) {
put(key);
remove(key);
put(key);
put("b");
put("c");
getKeys();
removeAll();
getKeys();
}
private static void getKeys() {
System.out.println("getKeys------");
List list = CaffeineKit.getKeys(cacheName);
System.out.println(list);
}
private static void removeAll() {
System.out.println("removeAll------");
CaffeineKit.removeAll(cacheName);
v = CaffeineKit.get(cacheName, key);
System.out.println(v);
}
private static void remove(String key) {
System.out.println("remove------" + key);
CaffeineKit.remove(cacheName, key);
v = CaffeineKit.get(cacheName, key);
System.out.println(v);
}
private static void put(String key) {
System.out.println("put------" + key);
CaffeineKit.put(cacheName, key, Ret.ok("老铁没毛病!"));
v = CaffeineKit.get(cacheName, key);
System.out.println(v);
}
}测试结果:
put------a
2021-12-05 23:06:57
[WARN]-[Thread: main]-[com.jfinal.plugin.caffeine.CaffeineKit.getOrAddCache()]: Could not find cache config [man], using default.
2021-12-05 23:06:57
[DEBUG]-[Thread: main]-[com.jfinal.plugin.caffeine.CaffeineKit.getOrAddCache()]: Cache [man] started.
{msg=老铁没毛病!, state=ok}
remove------a
null
put------a
{msg=老铁没毛病!, state=ok}
put------b
{msg=老铁没毛病!, state=ok}
put------c
{msg=老铁没毛病!, state=ok}
getKeys------
[a, b, c]
removeAll------
null
getKeys------
[]
进程已结束,退出代码为 0上面用的是默认配置,也可以在JF启动的时候,手动设置:
CaffeineKit.putCache(cacheName, Caffeine.newBuilder().maximumSize(10_000).build());
那用很简单,网上例子一大把,咋和JF配合使用了?得和内置的EhCache一样方便才行啊。
那继续复制JF里面的源码,改一下:
先说Db.findByCache和Model里面的ByCache等系列方法咋设置吧。
建一个CaffeineCache即可
package com.jfinal.plugin.caffeine;
import com.jfinal.plugin.activerecord.cache.ICache;
/**
* Caffeine 缓存
*
* ActiveRecordPlugin arp = new ActiveRecordPlugin(...);
* arp.setCache(new CaffeineCache());
*
* @author 杜福忠
*/
public class CaffeineCache implements ICache {
@Override
public <T> T get(String cacheName, Object key) {
return CaffeineKit.get(cacheName, key);
}
@Override
public void put(String cacheName, Object key, Object value) {
CaffeineKit.put(cacheName, key, value);
}
@Override
public void remove(String cacheName, Object key) {
CaffeineKit.remove(cacheName, key);
}
@Override
public void removeAll(String cacheName) {
CaffeineKit.removeAll(cacheName);
}
}用法注释中写了,ActiveRecordPlugin支持配置的。
ActiveRecordPlugin arp = new ActiveRecordPlugin(...); arp.setCache(new CaffeineCache());
测试:
/**
* 获取缓存数据
*/
public Ret findByCache() {
List<Record> list = Db.findByCache("a", "findByCache", "SELECT 1");
return Ret.ok(list.toString());
}断点第一次进去了
第二次就走缓存了

好没问题。
再说下一个,CacheInterceptor 和 EvictInterceptor 代码复制过来吧23333CV大F
用法文档中有说明:
https://jfinal.com/doc/7-4
package com.jfinal.plugin.caffeine;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.http.HttpServletRequest;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.core.Controller;
import com.jfinal.plugin.ehcache.CacheName;
import com.jfinal.plugin.ehcache.RenderInfo;
import com.jfinal.render.Render;
/**
* CacheInterceptor.
*/
public class CacheCaffeineInterceptor implements Interceptor {
private static final String renderKey = "_renderKey";
private static ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<String, ReentrantLock>(512);
private ReentrantLock getLock(String key) {
ReentrantLock lock = lockMap.get(key);
if (lock != null) {
return lock;
}
lock = new ReentrantLock();
ReentrantLock previousLock = lockMap.putIfAbsent(key, lock);
return previousLock == null ? lock : previousLock;
}
final public void intercept(Invocation inv) {
Controller controller = inv.getController();
String cacheName = buildCacheName(inv, controller);
String cacheKey = buildCacheKey(inv, controller);
Map<String, Object> cacheData = CaffeineKit.get(cacheName, cacheKey);
if (cacheData == null) {
Lock lock = getLock(cacheName);
lock.lock(); // prevent cache snowslide
try {
cacheData = CaffeineKit.get(cacheName, cacheKey);
if (cacheData == null) {
inv.invoke();
cacheAction(cacheName, cacheKey, controller);
return ;
}
}
finally {
lock.unlock();
}
}
useCacheDataAndRender(cacheData, controller);
}
// TODO 考虑与 EvictInterceptor 一样强制使用 @CacheName
protected String buildCacheName(Invocation inv, Controller controller) {
CacheName cacheName = inv.getMethod().getAnnotation(CacheName.class);
if (cacheName != null)
return cacheName.value();
cacheName = controller.getClass().getAnnotation(CacheName.class);
return (cacheName != null) ? cacheName.value() : inv.getActionKey();
}
protected String buildCacheKey(Invocation inv, Controller controller) {
StringBuilder sb = new StringBuilder(inv.getActionKey());
String urlPara = controller.getPara();
if (urlPara != null)
sb.append('/').append(urlPara);
String queryString = controller.getRequest().getQueryString();
if (queryString != null)
sb.append('?').append(queryString);
return sb.toString();
}
/**
* 通过继承 CacheInterceptor 并覆盖此方法支持更多类型的 Render
*/
protected RenderInfo createRenderInfo(Render render) {
return new RenderInfo(render);
}
protected void cacheAction(String cacheName, String cacheKey, Controller controller) {
HttpServletRequest request = controller.getRequest();
Map<String, Object> cacheData = new HashMap<String, Object>();
for (Enumeration<String> names=request.getAttributeNames(); names.hasMoreElements();) {
String name = names.nextElement();
cacheData.put(name, request.getAttribute(name));
}
Render render = controller.getRender();
if (render != null) {
cacheData.put(renderKey, createRenderInfo(render)); // cache RenderInfo
}
CaffeineKit.put(cacheName, cacheKey, cacheData);
}
protected void useCacheDataAndRender(Map<String, Object> cacheData, Controller controller) {
HttpServletRequest request = controller.getRequest();
Set<Entry<String, Object>> set = cacheData.entrySet();
for (Iterator<Entry<String, Object>> it=set.iterator(); it.hasNext();) {
Entry<String, Object> entry = it.next();
request.setAttribute(entry.getKey(), entry.getValue());
}
request.removeAttribute(renderKey);
RenderInfo renderInfo = (RenderInfo)cacheData.get(renderKey);
if (renderInfo != null) {
controller.render(renderInfo.createRender()); // set render from cacheData
}
}
}package com.jfinal.plugin.caffeine;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.plugin.ehcache.CacheName;
/**
* EvictInterceptor.
*/
public class EvictCaffeineInterceptor implements Interceptor {
public void intercept(Invocation inv) {
inv.invoke();
// @CacheName 注解中的多个 cacheName 可用逗号分隔
String[] cacheNames = getCacheName(inv).split(",");
if (cacheNames.length == 1) {
CaffeineKit.removeAll(cacheNames[0].trim());
} else {
for (String cn : cacheNames) {
CaffeineKit.removeAll(cn.trim());
}
}
}
/**
* 获取 @CacheName 注解配置的 cacheName,注解可配置在方法和类之上
*/
protected String getCacheName(Invocation inv) {
CacheName cacheName = inv.getMethod().getAnnotation(CacheName.class);
if (cacheName != null) {
return cacheName.value();
}
cacheName = inv.getController().getClass().getAnnotation(CacheName.class);
if (cacheName == null) {
throw new RuntimeException("EvictInterceptor need CacheName annotation in controller.");
}
return cacheName.value();
}
}OK,改一行代码即可,再测试测试
/**
* 清除缓存
*/
@CacheName("a")
@Before(EvictCaffeineInterceptor.class)
public void clearCache() {
renderJson(srv.clearCache());
}
/**
* 传递数据
*/
@CacheName("a")
@Before(CacheCaffeineInterceptor.class)
public void passData() {
renderJson(srv.passData(get("k1"), get("k2")));
}测试结果:
可以看到第一次访问是进入了Action

第二次访问是直接返回结果了,确定使用到缓存了
再清除缓存试试

看着是清除了,再访问一下刚取值的方法,看是否重新进入了Action
OK,确实清除了。
那么 EhCachePlugin这个插件的功能,都拿过来了。

Caffeine比上面少了两个东西,一个是Plugin这个插件接口,因为它不需要加载ehcache.xml所以不需内置,
那么想在JF启动时,配置自己的CaffeineKit.putCache(...) 写在什么位置好了?
我觉得最方便的位置是放在JFinalConfig子类的
public void configConstant(Constants me) {当然也看自己的业务。
这里,如果putCache的代码比较多,可以单独拆出一个方法,或者类,这样转调即可。
放图:
有用得到的话,点个赞呗~