beetl+jfinal框架下用redis做缓存

jfinal的默认插件里面是没有redis的,所以用了扒皮的Jfinal-ext(扒皮好像已经好久没有更新了)。里面的redis缓存用法只是按照SQL语句对查询结果进行缓存,感觉没有jfinal中对ehcache插件那样的注解式写法方便和高效。所以自己打算这一个注解式的写法,这样就能保证在拦截器中就直接返回结果,不会再进入到actioin了。

节约字数,就把import和package给省了。JedisKit是jfinal-ext的,Const.CACHE_ACTION_PREFIX就是一个字符串,自定义的,这里是"ACTION-"。

先说一下用法。

1.CacheKey注解可以省略,不省略的话,CacheKey就是"ACTION-ccc"。省略之后会自动生成CacheKey("ACTION-"+请求的路径+url传递的参数),本例中就是"ACTION-/user/a"(此处用了jfinal-ext的自动绑定路径功能)。注:post传递参数不会被记录到CacheKey中,所以尽量不要用post,不过一般需要cache的都是get之类的请求吧。

2.EvictKey注解也可以省略,省略的话就会将这个Controller下的所有cache都清除,本例匹配规则:ACTION-/user*。EvivtKey可以是数组(考虑到可能会出现一个操作影响多个缓存的情况)。

public class UserController extends BaseController<UserModel> {
	@Before(CacheAction.class)
	@CacheKey("ccc")//可省略
	public void a(){
		setAttr("beetl", "beetl success");
		render("a");
	}
	
	@Before(EvictCache.class)
	@EvictKey("ccc")//可省略;可以是数组如:@EvictKey({"ccc","bbb"})
	public void b(){
		setAttr("beetl", "beetl right");
		render("a");
	}
}

考虑过直接用注解,类似@CacheAction("ccc")这种形式的写法,但是这样的话,就意味着要加一个全局的拦截器,然后所有请求都要判断是否含有@CacheAction。总感觉这样未免小题大做,所以还是用@Before这个高效的注解吧。

下面是源码。

--拦截器CacheAction.java,其实就是修改了jfinal的CacheInterceptor.java。

public class CacheAction 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;
	}
	
	final public void intercept(ActionInvocation ai) {
		Controller controller = ai.getController();
		String key = buildKey(ai, controller);
		Map<String, Object> cacheData = JedisKit.get(key);
		if (cacheData == null) {
			Lock lock = getLock(key);
			lock.lock();					// prevent cache snowslide
			try {
				cacheData = JedisKit.get(key);
				if (cacheData == null) {
					ai.invoke();
					cacheAction(key, controller);
					return ;
				}
			} finally {
				lock.unlock();
			}
		}
		
		useCacheDataAndRender(cacheData, controller);
	}
	
	/**
	 * 通过post传递参数的请求不能被缓存。
	 * @param ai
	 * @param controller
	 * @return
	 */
	private String buildKey(ActionInvocation ai, Controller controller) {
		CacheKey cacheKey = ai.getMethod().getAnnotation(CacheKey.class);
		if (cacheKey != null)
			return Const.CACHE_ACTION_PREFIX + cacheKey.value();
		
		StringBuilder sb = new StringBuilder();
		sb.append(Const.CACHE_ACTION_PREFIX);
		sb.append(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 key, Controller controller) {
		HttpServletRequest request = controller.getRequest();
		HashMap<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
		JedisKit.set(key, 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
	}
}

----拦截器EvictCache.java,其实就是修改了jfinal的EvictInterceptor.java。

public class EvictCache implements Interceptor {

	final public void intercept(ActionInvocation ai) {
		ai.invoke();
		JedisKit.del(buildKeys(ai));
	}

	private String[] buildKeys(ActionInvocation ai) {
		EvictKey evictKeys = ai.getMethod().getAnnotation(EvictKey.class);
		if (evictKeys != null){
			String[] keyArray = evictKeys.value();
			for(int i=0; i<keyArray.length; i++){
				keyArray[i] = Const.CACHE_ACTION_PREFIX + keyArray[i];
			}
			return keyArray;
		}
		
		StringBuilder sb = new StringBuilder();
		sb.append(Const.CACHE_ACTION_PREFIX);
		sb.append(ai.getControllerKey());
		sb.append("/*");
		Set<String> keySet = JedisKit.keys(sb.toString());
		String[] keys = keySet.toArray(new String[keySet.size()]);
		return keys;
	}
}

--CacheKey.java

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface CacheKey {
	String value();
}

--EvictKey.java

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface EvictKey {
	String[] value();
}

-----------------------------------------------------------------------------

这样弄好之后运行时会出现java.io.NotSerializableException: org.beetl.core.GroupTemplate报错。这是因为模板引擎用了beetl的缘故。

拦截器在进行缓存的时候会把render和request中的参数全都保存起来。jfinal的Render接口是可序列化的,Beetl实现了Render接口,但是和其他的Render不同的是,beetl自带的jfinal插件BeetlRender中有一个属性是GroupTemplate。

public class BeetlRender extends Render
{
	GroupTemplate gt = null;
	private transient static final String encoding = getEncoding();
	private transient static final String contentType = "text/html; charset=" + encoding;
        ......
        ......
        ......
}

这个GroupTemplate没有实现Serializable,所以不能存入redis。并且,如果每个缓存的render里面都有一个GroupTemplate,也会浪费内存,所以还是自己写个BeetlRender为妙。

下面给出我自己的RnBeetlRender和RnBeetlRenderFactory

public class RnBeetlRenderFactory implements IMainRenderFactory {
	public static String viewExtension = ".html";
	public static GroupTemplate groupTemplate = null;

	public RnBeetlRenderFactory() {
		if (groupTemplate != null) {
			groupTemplate.close();
		}

		try {

			Configuration cfg = Configuration.defaultConfiguration();
			WebAppResourceLoader resourceLoader = new WebAppResourceLoader();
			groupTemplate = new GroupTemplate(resourceLoader, cfg);

		} catch (IOException e) {
			throw new RuntimeException("加载GroupTemplate失败", e);
		}
	}

	public Render getRender(String view) {
		return new RnBeetlRender(view + viewExtension);
	}

	public String getViewExtension() {
		return viewExtension;
	}

}


public class RnBeetlRender extends Render {
	private static final long serialVersionUID = 1L;
	
	private transient static final String encoding = getEncoding();
	private transient static final String contentType = "text/html; charset=" + encoding;

	public RnBeetlRender(String view) {
		this.view = view;
	}

	@Override
	public void render() {
		try{
			response.setContentType(contentType);
			WebRender webRender = new WebRender(
					RnBeetlRenderFactory.groupTemplate);
			webRender.render(view, request, response);
		} catch (BeetlException e) {
			throw new RenderException(e);
		}
	}
}

然后在JFinalConfig继承类里面配置即可。

        public void configConstant(Constants me) {
		me.setMainRenderFactory(new RnBeetlRenderFactory());
	}


如有问题和建议,请多多指教。



你可能感兴趣的:(redis,jedis,jFinal,beetl)