使用缓存的好处很多,能够极大的提高页面显示速度。
1、准备好 jar包 ehcache.jar。并引入工程中。
2、jfinal主要提供了3中使用缓存的方法,这里主要说两种。
1)、注解使用法
java 注解 理解不多,个人觉得 注解其实就是 一个属性。例如给一个 test方法加了 @before 注解后,这个before 对象其实就成了 test 方法的一个属性,在调用 test时,可以获取到 这个 before 对象。
加了 注解后的的 index():
public class WokersController extends Controller { /** * index 表示根路径,对应的路径为 localhost:..../hello * 这里我们让它显示所有的工人。这里是使用了模板引擎 freeMark,它会根据list自动显示数据 */ @Before(CacheInterceptor.class)//相当于给这个方法里面所有被 setAttr 的对象都添加到内存中 @CacheName("workers")//这个缓存的名字叫workers public void index() { List<Worker> list = Worker.dao.find("select * from worker where rownum<10"); //==request.setAttribute(name, value),就是将list放入request中 setAttr("workersList", list); //渲染到根路径下的workers.html页面中。 render("/workers.html"); }
看下 浏览器 请求的时间有没有变化。
不加缓存时的请求:
index 加了缓存后:
为什么加了这个注解后它会缓存。我们先来看看代码
//CacheInterceptor cacheData.put(name, request.getAttribute(name));//将request 中的对象加入缓存
@before :
/** * Copyright (c) 2011-2014, James Zhan 詹波 ([email protected]). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jfinal.aop; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Before is used to configure Interceptor or Validator. */ @Inherited @Retention(RetentionPolicy.RUNTIME)//在程序运行时,也能获取到这个before @Target({ElementType.TYPE, ElementType.METHOD})//可以用在方法,类...上 public @interface Before { Class<? extends Interceptor>[] value();//有多个Interceptor的class } //也就是说,这个注解可以用在方法上,使用方法: @before(Interceptor1.class,Interceptor2.class)
我们使用的注解是@Before(CacheInterceptor.class),我们看看CacheInterceptor
/** * Copyright (c) 2011-2014, James Zhan 詹波 ([email protected]). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jfinal.plugin.ehcache; 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.core.ActionInvocation; import com.jfinal.core.Controller; import com.jfinal.render.Render; /** * CacheInterceptor. */ public class CacheInterceptor implements Interceptor { private static final String renderKey = "$renderKey$"; private static volatile ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<String, ReentrantLock>(); 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; } /** 这个方法是关键,主要作用应该是将内存中的缓存直接给request了。该方法是在 过滤器JFinalFilter的 dofilter方法时被执行。下面我们在看下 dofilter 方法 */ final public void intercept(ActionInvocation ai) { Controller controller = ai.getController(); String cacheName = buildCacheName(ai, controller); String cacheKey = buildCacheKey(ai, controller); 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) { ai.invoke(); cacheAction(cacheName, cacheKey, controller); return ; } } finally { lock.unlock(); } } useCacheDataAndRender(cacheData, controller); } // TODO 考虑与 EvictInterceptor 一样强制使用 @CacheName private String buildCacheName(ActionInvocation ai, Controller controller) { CacheName cacheName = ai.getMethod().getAnnotation(CacheName.class); if (cacheName != null) return cacheName.value(); cacheName = controller.getClass().getAnnotation(CacheName.class); return (cacheName != null) ? cacheName.value() : ai.getActionKey(); } private String buildCacheKey(ActionInvocation ai, Controller controller) { StringBuilder sb = new StringBuilder(ai.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(); } private void cacheAction(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)); } cacheData.put(renderKey, controller.getRender()); // cache render CacheKit.put(cacheName, cacheKey, cacheData); } private void useCacheDataAndRender(Map<String, Object> cacheData, Controller controller) { HttpServletRequest request = controller.getRequest(); Set<Entry<String, Object>> set = cacheData.entrySet(); for (Iterator<Entry<String, Object>> it=set.iterator(); it.hasNext();) { Entry<String, Object> entry = it.next(); request.setAttribute(entry.getKey(), entry.getValue()); } request.removeAttribute(renderKey); controller.render((Render)cacheData.get(renderKey)); // set render from cacheData } }
//JFinalFilter类 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; request.setCharacterEncoding(encoding); String target = request.getRequestURI(); if (contextPathLength != 0) target = target.substring(contextPathLength); boolean[] isHandled = {false}; try { //执行到这里 handler.handle(target, request, response, isHandled); } catch (Exception e) { if (log.isErrorEnabled()) { String qs = request.getQueryString(); log.error(qs == null ? target : target + "?" + qs, e); } } if (isHandled[0] == false) chain.doFilter(request, response); } //ActionHandler类 public final void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) { if (target.indexOf(".") != -1) { return ; } isHandled[0] = true; String[] urlPara = {null}; Action action = actionMapping.getAction(target, urlPara); if (action == null) { if (log.isWarnEnabled()) { String qs = request.getQueryString(); log.warn("404 Action Not Found: " + (qs == null ? target : target + "?" + qs)); } renderFactory.getErrorRender(404).setContext(request, response).render(); return ; } try { Controller controller = action.getControllerClass().newInstance(); controller.init(request, response, urlPara[0]); if (devMode) { boolean isMultipartRequest = ActionReporter.reportCommonRequest(controller, action); new ActionInvocation(action, controller).invoke(); if (isMultipartRequest) ActionReporter.reportMultipartRequest(controller, action); } else { //执行到这里 new ActionInvocation(action, controller).invoke(); } Render render = controller.getRender(); if (render instanceof ActionRender) { String actionUrl = ((ActionRender)render).getActionUrl(); if (target.equals(actionUrl)) throw new RuntimeException("The forward action url is the same as before."); else handle(actionUrl, request, response, isHandled); return ; } if (render == null) render = renderFactory.getDefaultRender(action.getViewPath() + action.getMethodName()); render.setContext(request, response, action.getViewPath()).render(); } catch (RenderException e) { if (log.isErrorEnabled()) { String qs = request.getQueryString(); log.error(qs == null ? target : target + "?" + qs, e); } } catch (ActionException e) { int errorCode = e.getErrorCode(); if (errorCode == 404 && log.isWarnEnabled()) { String qs = request.getQueryString(); log.warn("404 Not Found: " + (qs == null ? target : target + "?" + qs)); } else if (errorCode == 401 && log.isWarnEnabled()) { String qs = request.getQueryString(); log.warn("401 Unauthorized: " + (qs == null ? target : target + "?" + qs)); } else if (errorCode == 403 && log.isWarnEnabled()) { String qs = request.getQueryString(); log.warn("403 Forbidden: " + (qs == null ? target : target + "?" + qs)); } else if (log.isErrorEnabled()) { String qs = request.getQueryString(); log.error(qs == null ? target : target + "?" + qs, e); } e.getErrorRender().setContext(request, response).render(); } catch (Exception e) { if (log.isErrorEnabled()) { String qs = request.getQueryString(); log.error(qs == null ? target : target + "?" + qs, e); } renderFactory.getErrorRender(500).setContext(request, response).render(); } } //ActionInvocation /** * Invoke the action. */ public void invoke() { if (index < inters.length) inters[index++].intercept(this);//执行每个 Interceptor的intercept 方法,刚刚那个缓存类 //,就执行了在缓存中读数据的操作 else if (index++ == inters.length) // index++ ensure invoke action only one time // try {action.getMethod().invoke(controller, NULL_ARGS);} catch (Exception e) {throw new RuntimeException(e);} try { action.getMethod().invoke(controller, NULL_ARGS); } catch (InvocationTargetException e) { Throwable cause = e.getTargetException(); if (cause instanceof RuntimeException) throw (RuntimeException)cause; throw new RuntimeException(e); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } }
我们理清思路:
页面请求到来---》进入 filter 的dofilter 方法,并获取注解中的Interceptor--》执行Interceptor中的方法,从缓存中直接获取数据--》最后返回前端。
这个过程中并没有继续查询数据库,所以速度要快,就是消耗一些内存。
@Before(EvictInterceptor.class) 是作为清空缓存使用的,可以自己去看看。
2)、使用 CacheKit来管理缓存,代码为
index 方法 //这个方法的意思是如果存在缓存就使用缓存,没有缓存就去 查询数据库。应该比较容易理解 List<Record> list = CacheKit.get("workers", "workersList", new IDataLoader(){ public Object load() { return Db.find("select * from worker"); }}); setAttr("workersList", list); //渲染到根路径下的workers.html页面中。 render("/workers.html");