开发网站的时候,有些页面在后台SQL
查询时间比较慢(比如需要集成多个表的数据),如果页面内容不常变化,那么能否设置页面缓存,让用户访问的时候能直接显示已经查询过的数据,快速显示页面?
Java
生态里就有这一个产品ehcache
, 可以做到缓存的功能. 那么它如何集成进JFinal
框架里?
集成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
就是永不过期,timeToIdleSeconds
和timeToLiveSeconds
就会失效.maxElementsInMemory
:最大的存储对象数,比如需要存储某个页面,url
作为key
,页面内容作为value
. 那么这里设置的就是key
的数量。overflowToDisk
: 是否在缓存过大时,内存容不下时存储到磁盘。diskPersistent
:Server
重启时将缓存序列化到本地,后再加载,保证缓存在重启后依然有效。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.setAttr
或request.setAttribute
设置request
属性,在渲染enjoy
模板时就会拿到这些变量值. 无缓存的做法是,第一次查询数据库后得到的数据,会放到request
的属性里,之后调用Controller
的render
方法进行渲染模板。这时候如果不想缓存整个页面,可以通过读取request
的属性,把所有的属性值都封装到一个Map
里,之后再把这个Map
存到CacheKit
里即可. 我这里封装了一个工具类CacheUtils
的applyViewCache
用于做这些工作,它的Supplier
返回值是一个Pair<Boolean,String>
, 如果k
为true
时,说明是正常返回, v
就是这个enjoy
模板文件的路径. 注意当想让缓存失效时,调用CacheKit
的remove()
方法移除缓存,那么调用就会重新查询数据库。
boolean result = CacheUtils.applyViewCache(kLocalCacheName,subUrlPath,oc,
()->{
...
return new Pair<>(true,"/public/_home.enjoy");
});
public class Pair<K,V>{
public K k;
public V v;
public Pair(K k, V v){
this.k = k;
this.v = v;
}
}
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());
}
}
}