jfinal-4.9 整合ecache自动缓存controller数据

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查看数据

image.png

_render_key的值显示这个样子是json序列化的问题,并与影响数据的数据展示

评论区

李通

2020-08-18 10:36

_render_key的值显示这个样子是json序列化的问题,并不影响数据的数据展示

zzutligang

2020-08-18 15:04

收藏,有时间验证一下

jfinal爱好者22

2020-08-26 10:20

不错,赞

热门分享

扫码入社