jfinal-4.9 整合ecache自动缓存action数据
[产生背景]
1.每次controller都需要查询数据库,将数据渲染到页面 或者返回json
2.这些数据只要没有新增或者更新就没有变化,为什不能缓存呢
3.jfinal的自带的CacheInterceptor可以实现上面的功能,但是CacheInterceptor需要根据contrllerkey在ecache.xml中配置对应name的cache的标签
[我的目标]
1.自动缓存controller返回的数据
2.不要手工修改ecache.xml,因为controller太多了
[操作步骤]
1.添加依赖
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.11</version> </dependency>
2.添加ecache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <diskStore path="java.io.tmpdir/EhCache" /> <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU" /> </ehcache>
3.编写EhCacheInterceptor
分析com.jfinal.plugin.ehcache.CacheInterceptor在此基础之上编写com.litong.jfinal.interceptor.EhCacheInterceptor
package com.litong.jfinal.interceptor; 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.CacheKit; import com.jfinal.plugin.ehcache.CacheName; import com.jfinal.plugin.ehcache.RenderInfo; import com.jfinal.render.Render; import lombok.extern.slf4j.Slf4j; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; /** * EhCacheInterceptor. */ @Slf4j public class EhCacheInterceptor 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(); //获取cacheName 是类名的请求路径 String cacheName = buildCacheName(inv, controller); //获取cacheKey 是 方法名+请求参数 String cacheKey = buildCacheKey(inv, controller); //获取cacheManager,判断cacheName是否存在,如果不存在,新增 CacheManager cacheManager = CacheKit.getCacheManager(); Cache cache = cacheManager.getCache(cacheName); if (cache == null) { log.info("添加新的cacheName:{}",cacheName); // maxElementsInMemory,overflowToDisk,eternal,timeToLiveSecnds,timeToIdleSeconds cache = new Cache(cacheName, 10000, false, false, 259200, 1800); cacheManager.addCache(cache); } // 获取缓存数据,如果第一个获取,肯定是null Map<String, Object> cacheData = CacheKit.get(cacheName, cacheKey); if (cacheData == null) { //获取锁 Lock lock = getLock(cacheName); //加锁 lock.lock(); // prevent cache snowslide try { //双重检查,用于高并发时 cacheData = CacheKit.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); } /** * * @param cacheName * @param cacheKey * @param controller */ protected void cacheAction(String cacheName, String cacheKey, Controller controller) { HttpServletRequest request = controller.getRequest(); Map<String, Object> cacheData = new HashMap<String, Object>(); //获取request attributeNames 数据放入cacheData中 for (Enumeration<String> names = request.getAttributeNames(); names.hasMoreElements();) { String name = names.nextElement(); cacheData.put(name, request.getAttribute(name)); } //获取render 数据,存入cacheData中 Render render = controller.getRender(); if (render != null) { cacheData.put(renderKey, createRenderInfo(render)); // cache RenderInfo } //存入cache CacheKit.put(cacheName, cacheKey, cacheData); } protected void useCacheDataAndRender(Map<String, Object> cacheData, Controller controller) { HttpServletRequest request = controller.getRequest(); Set<Entry<String, Object>> set = cacheData.entrySet(); //将 request attributeNames 取出 从cache中取出,存入request中 for (Iterator<Entry<String, Object>> it = set.iterator(); it.hasNext();) { Entry<String, Object> entry = it.next(); request.setAttribute(entry.getKey(), entry.getValue()); } //删除reander数据 request.removeAttribute(renderKey); // 获取render数据 RenderInfo renderInfo = (RenderInfo) cacheData.get(renderKey); if (renderInfo != null) { //创建render并让controller返回reander controller.render(renderInfo.createRender()); // set render from cacheData } } }
在conntroller中添加拦截器
@Slf4j public class IndexController extends Controller { @Before(EhCacheInterceptor.class) public void index(Kv kv) { String string = HttpKit.post("http://yoshop.localhost.litongjava.com", kv, null); renderJson(JSON.parseObject(string)); return; } }
第一阶段完成
添加一个可以查看缓存内容的controller
package com.litong.jfinal.controler; import java.util.HashMap; import java.util.List; import java.util.Map; import com.jfinal.core.Controller; import com.jfinal.plugin.ehcache.CacheKit; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; /** * @author bill robot * @date 2020年8月18日_上午12:24:14 * @version 1.0 * @desc */ public class EhCacheController extends Controller { public void getCacheNames() { renderJson(CacheKit.getCacheManager().getCacheNames()); return; } public void getAllCacheValue() { CacheManager cacheManager = CacheKit.getCacheManager(); String[] cacheNames = cacheManager.getCacheNames(); Map<String, Map<String, Object>> retval = new HashMap<>(cacheNames.length); for (String name : cacheNames) { Map<String, Object> map = cacheToMap(cacheManager, name); retval.put(name, map); } renderJson(retval); } public void getCacheValueByCacheName(String cacheName) { CacheManager cacheManager = CacheKit.getCacheManager(); renderJson(cacheToMap(cacheManager, cacheName)); } public void getCacheValueByCacheNameAndCacheKey(String cacheName, String key) { Object object = CacheKit.get(cacheName, key); renderJson(object); } private Map<String, Object> cacheToMap(CacheManager cacheManager, String name) { Cache cache = cacheManager.getCache(name); List<String> keys = cache.getKeys(); Map<String, Object> map = new HashMap<>(keys.size()); for (String key : keys) { Element element = cache.get(key); Object value = element.getObjectValue(); map.put(key, value); } return map; } }
测试,先缓存一部分,数据,然后访问contrller查看数据
_render_key的值显示这个样子是json序列化的问题,并与影响数据的数据展示