软件简介
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的代码比较多,可以单独拆出一个方法,或者类,这样转调即可。
放图:
有用得到的话,点个赞呗~