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序列化的问题,并与影响数据的数据展示