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