[JavaWeb]_[初级]_[jfinal集成ehcache缓存]

靳涵亮
2023-12-01

场景

  1. 开发网站的时候,有些页面在后台SQL查询时间比较慢(比如需要集成多个表的数据),如果页面内容不常变化,那么能否设置页面缓存,让用户访问的时候能直接显示已经查询过的数据,快速显示页面?

  2. Java生态里就有这一个产品ehcache, 可以做到缓存的功能. 那么它如何集成进JFinal框架里?

说明

  1. 集成ehcache步骤:

    1.1 在项目maven文件pom.xml里增加ehcache的依赖, 注意目前jfinal只支持2.x版本的ehcache.

    <dependency>
    	<groupId>net.sf.ehcache</groupId>
    	<artifactId>ehcache-core</artifactId>
    	<version>2.6.11</version>
    </dependency>
    

    1.2 在启动文件DemoConfig(它继承自JFinalConfig,用于全局配置)的configPlugin()方法里加载EhCachePlugin插件.

    me.add(new EhCachePlugin());
    

    1.3 新建ehcache.xml文件到项目结构目录src/main/resources下.

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true">
        <diskStore path="java.io.tmpdir"/>
        <defaultCache eternal="false"
                      maxElementsInMemory="100"
                      overflowToDisk="false"
                      diskPersistent="false"
                      timeToIdleSeconds="600"
                      timeToLiveSeconds="0"
                      memoryStoreEvictionPolicy="LRU" />
        <cache name="local"
               eternal="false"
               maxElementsInMemory="500"
               overflowToDisk="false"
               diskPersistent="false"
               timeToIdleSeconds="1800"
               timeToLiveSeconds="0"
               memoryStoreEvictionPolicy="LRU" />
    </ehcache>
    
    • name: 缓存名称,可以建多个缓存名,注意这个是存储你自己的缓存数据的容器名称,-所有你需要缓存的Java对象都存在这个容器里,也是从这个容器里取出存储对象. 可以任意名称.
    • eternal: 如果为true就是永不过期,timeToIdleSecondstimeToLiveSeconds就会失效.
    • maxElementsInMemory:最大的存储对象数,比如需要存储某个页面,url作为key,页面内容作为value. 那么这里设置的就是key的数量。
    • overflowToDisk: 是否在缓存过大时,内存容不下时存储到磁盘。
    • diskPersistentServer重启时将缓存序列化到本地,后再加载,保证缓存在重启后依然有效。
    • timeToIdleSeconds: 超过设置的秒数后没有访问这个key,那么这个key就会失效,会被移除缓存.
    • timeToLiveSeconds: 超过这个设置的秒数就会失效,无论是否有访问. 如果为0时就永不失效,失效控制交给timeToIdleSeconds和其他因素,比如内存不够。
    • memoryStoreEvictionPolicy: 内存不够或最大元素达到后的失效策略,这里配置使用LRU, 最近最少使用的key先移除.

    1.4 使用jfinal自带的CacheKit工具类来操作缓存. 主要是使用它以下的3个方法,里面封装了对ehcache的调用。 注意,ehcache的方法是线程安全的, 所以不用担心多线程访问的问题.

    • get(): 获取缓存
    • put(): 添加缓存
    • remove(): 移除缓存

    1.5 在jfinal官方文档[4]里,推荐使用的是缓存jfinal enjoy模板使用的Java变量,而不是缓存整个html页面。应该是整个页面需要的内存会更多的原因,而enjoy模板本身也有编译后的缓存,所以这种做法性能也是比较高的.

    public void list() {
        List<Blog> blogList = CacheKit.get("local", "blogList");
        if (blogList == null) {
           blogList = Blog.dao.find("select * from blog");
           CacheKit.put("local", "blogList", blogList);
        }
        setAttr("blogList", blogList);
        render("blog.html");
    }
    

    1.6 在缓存的时候就,通过从ehcache里取出来放到Controller.request的属性里, 可以调用Controller.setAttrrequest.setAttribute设置request属性,在渲染enjoy模板时就会拿到这些变量值. 无缓存的做法是,第一次查询数据库后得到的数据,会放到request的属性里,之后调用Controllerrender方法进行渲染模板。这时候如果不想缓存整个页面,可以通过读取request的属性,把所有的属性值都封装到一个Map里,之后再把这个Map存到CacheKit里即可. 我这里封装了一个工具类CacheUtilsapplyViewCache用于做这些工作,它的Supplier返回值是一个Pair<Boolean,String>, 如果ktrue时,说明是正常返回, v就是这个enjoy模板文件的路径. 注意当想让缓存失效时,调用CacheKitremove()方法移除缓存,那么调用就会重新查询数据库。

例子

调用

boolean result = CacheUtils.applyViewCache(kLocalCacheName,subUrlPath,oc,
                ()->{
                    ...
                    return new Pair<>(true,"/public/_home.enjoy");
                });

Pair

public class Pair<K,V>{

    public K k;
    public V v;

    public Pair(K k, V v){
        this.k = k;
        this.v = v;
    }
}

CacheUtils


import com.alibaba.fastjson.JSONObject;
import com.jfinal.core.Controller;
import com.jfinal.plugin.ehcache.CacheKit;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.function.Supplier;

public class CacheUtils {

    public static final String kLocalCacheName = "local";
    private static final String kViewPath = "CACHE_VIEW_PATH";
    public static final String kCacheRootKey = "/";

    public static JSONObject applyJsonCache(String cacheName, String cacheKey,
                                            Supplier<Pair<Boolean,String>> func){
        JSONObject cache = getJsonCache(cacheName, cacheKey);
        if(cache == null){
            Pair<Boolean, String> pair = func.get();
            if(pair.k && !isEmpty(pair.v)) {
                cache = (JSONObject)JSONObject.parse(pair.v);
                if(cache != null)
                    CacheKit.put(cacheName, cacheKey, cache);
            }
        }
        return cache;
    }

    public static boolean applyViewCache(String cacheName, String cacheKey,Controller oc,
                                     Supplier<Pair<Boolean,String>> func){
        Map<String, Object> cache = getCache(cacheName, cacheKey);
        boolean result;
        if(cache == null){
            Pair<Boolean, String> pair = func.get();
            if(pair.k && !isEmpty(pair.v)) {
                cache = cacheAttribute(cacheName, cacheKey, oc);
                cache.put(kViewPath,pair.v);
                oc.render(pair.v);
            }
            result = pair.k;
        }else{
            useCacheAttribute(cache,oc);
            oc.render((String)cache.get(kViewPath));
            result = true;
        }
        return result;
    }

    public static JSONObject getJsonCache(String cacheName, String cacheKey) {
        return CacheKit.get(cacheName,cacheKey);
    }

    public static Map<String, Object> getCache(String cacheName, String cacheKey) {
        return CacheKit.get(cacheName,cacheKey);
    }

    public static Map<String, Object> cacheAttribute(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));
        }
        CacheKit.put(cacheName, cacheKey, cacheData);
        return cacheData;
    }

    public static void useCacheAttribute(Map<String, Object> cacheData, Controller controller) {
        HttpServletRequest request = controller.getRequest();
        Set<Map.Entry<String, Object>> set = cacheData.entrySet();
        for (Iterator<Map.Entry<String, Object>> it = set.iterator(); it.hasNext();) {
            Map.Entry<String, Object> entry = it.next();
            request.setAttribute(entry.getKey(), entry.getValue());
        }
    }
}

参考

  1. 如果有人问你 JFinal 如何集成 EhCache,把这篇文章甩给他

  2. Ehcache timeToIdleSeconds和 timeToLiveSeconds区别

  3. Ehcache 官方文档

  4. JFinal EhCache

  5. JFinal源码

 类似资料: