手把手教你新建 jfinal 项目 (四)

    使用缓存的好处很多,能够极大的提高页面显示速度。

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");
    }

看下 浏览器 请求的时间有没有变化。

不加缓存时的请求:

手把手教你新建 jfinal 项目 (四)_第1张图片

index 加了缓存后:

手把手教你新建 jfinal 项目 (四)_第2张图片

为什么加了这个注解后它会缓存。我们先来看看代码

//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");


你可能感兴趣的:(手把手教你新建 jfinal 项目 (四))