文档章节

beetl+jfinal框架下用redis做缓存

ramnight
 ramnight
发布于 2014/07/03 13:56
字数 1166
阅读 796
收藏 11

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


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



© 著作权归作者所有

ramnight

ramnight

粉丝 3
博文 1
码字总数 1166
作品 0
海淀
私信 提问
加载中

评论(6)

ramnight
ramnight 博主

引用来自“JFinal”的评论

写得很详细,顶一个,注意这行代码:controller.render((Render)cacheData.get(renderKey)); 这个会引发线程安全问题,jfinal 下一版本会改掉此行代码,比较简单的改进方案是,用 if 判断是哪类 render,然后 new 出来再 set view 进去,例如 if (render instanceof FreeMarkerRender) render = new FreeMarkerRender(render.getView);

引用来自“RamNight”的评论

谢老大指点。不过我没有想到线程不安全的场景,请老大简单的描述一下..... 谢谢。

引用来自“JFinal”的评论

场景如下: 1:第一次请求 action 后,生成的 render 对象被保存 2:随后有两个线程同时请求这个 action,并都从 cache 中获取到了上面步骤保存的 render 对象 3:此时两个线程同时持有同一个 render 对象,再看 com.jfinal.core.ActionHandler.java 第 92 行代码为:render.setContext(request, response, action.getViewPath()).render(); 两个线程先后往同一个 render 对象中传入 request、response 对象,而这两个对象来自于不同的请求,甚至不同的用户,所以造成结果输出时的并发混乱
明白了,还是jfinal的代码没有看详细。多谢老大,那就只保存viewPath,然后每次都生成一个Render。
JFinal
JFinal

引用来自“JFinal”的评论

写得很详细,顶一个,注意这行代码:controller.render((Render)cacheData.get(renderKey)); 这个会引发线程安全问题,jfinal 下一版本会改掉此行代码,比较简单的改进方案是,用 if 判断是哪类 render,然后 new 出来再 set view 进去,例如 if (render instanceof FreeMarkerRender) render = new FreeMarkerRender(render.getView);

引用来自“RamNight”的评论

谢老大指点。不过我没有想到线程不安全的场景,请老大简单的描述一下..... 谢谢。
场景如下: 1:第一次请求 action 后,生成的 render 对象被保存 2:随后有两个线程同时请求这个 action,并都从 cache 中获取到了上面步骤保存的 render 对象 3:此时两个线程同时持有同一个 render 对象,再看 com.jfinal.core.ActionHandler.java 第 92 行代码为:render.setContext(request, response, action.getViewPath()).render(); 两个线程先后往同一个 render 对象中传入 request、response 对象,而这两个对象来自于不同的请求,甚至不同的用户,所以造成结果输出时的并发混乱
ramnight
ramnight 博主

引用来自“闲.大赋”的评论

写的很认真啊,顶一个,我把他贴到ibeetl.com 上了
谢老大成全...
闲大赋
闲大赋
写的很认真啊,顶一个,我把他贴到ibeetl.com 上了
ramnight
ramnight 博主

引用来自“JFinal”的评论

写得很详细,顶一个,注意这行代码:controller.render((Render)cacheData.get(renderKey)); 这个会引发线程安全问题,jfinal 下一版本会改掉此行代码,比较简单的改进方案是,用 if 判断是哪类 render,然后 new 出来再 set view 进去,例如 if (render instanceof FreeMarkerRender) render = new FreeMarkerRender(render.getView);
谢老大指点。不过我没有想到线程不安全的场景,请老大简单的描述一下..... 谢谢。
JFinal
JFinal
写得很详细,顶一个,注意这行代码:controller.render((Render)cacheData.get(renderKey)); 这个会引发线程安全问题,jfinal 下一版本会改掉此行代码,比较简单的改进方案是,用 if 判断是哪类 render,然后 new 出来再 set view 进去,例如 if (render instanceof FreeMarkerRender) render = new FreeMarkerRender(render.getView);
spring操作redis用哪种方式好?

最近在学习redis. 在网上看了很多教程. 有的用 redisTemplate , 有的用 jedisClient , 还有用 Cacheable 缓存注解的. 我想问一下,这几种方式有什么区别. 什么样的情况下用哪种方式. 实际业务...

skr
2018/04/16
1K
6
felly822/wacache

wacache 一款java缓存开发标准: >1.抽象并统一了目前流行缓存访问接口,概括目前流行的缓存框架,并且很容易继续扩展. >2.同时为缓存的写入,删除,读取提供监听事件支持. github地址:https://g...

felly822
2017/02/22
0
0
写给分布式神器fourinone

fourinone号称了集成了hadoop,zk,memcache,mq于一身的四不像超级神器,致力于分布式应用。别的我不懂,今儿聊一下fourinone中的分布式缓存以及分布式文件系统,说到分布式,不得不用到网络通...

zhh5919
2014/05/06
7.9K
11
两级缓存框架 J2Cache 的简单实验

今天对 J2Cache 做了最基本的测试,代码已经开放出来,地址是 http://git.oschina.net/ld/J2Cache 补充为什么要做这个框架: 说实话,OSC 现在还是单个 Tomcat,只不过我们做了一些分流措施将...

红薯
2014/01/07
18.9K
45
.NET缓存框架CacheManager在混合式开发框架中的应用(1)-CacheManager的介绍和使用

在我们开发的很多分布式项目里面(如基于WCF服务、Web API服务方式),由于数据提供涉及到数据库的相关操作,如果客户端的并发数量超过一定的数量,那么数据库的请求处理则以爆发式增长,如果...

walb呀
2017/12/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周一乱弹 —— 熟悉的味道,难道这就是恋爱的感觉

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @xiaoshiyue :好久没分享歌了分享张碧晨的单曲《今后我与自己流浪》 《今后我与自己流浪》- 张碧晨 手机党少年们想听歌,请使劲儿戳(这里)...

小小编辑
今天
126
7
SpringBoot中 集成 redisTemplate 对 Redis 的操作(二)

SpringBoot中 集成 redisTemplate 对 Redis 的操作(二) List 类型的操作 1、 向列表左侧添加数据 Long leftPush = redisTemplate.opsForList().leftPush("name", name); 2、 向列表右......

TcWong
今天
18
0
排序––快速排序(二)

根据排序––快速排序(一)的描述,现准备写一个快速排序的主体框架: 1、首先需要设置一个枢轴元素即setPivot(int i); 2、然后需要与枢轴元素进行比较即int comparePivot(int j); 3、最后...

FAT_mt
昨天
4
0
mysql概览

学习知识,首先要有一个总体的认识。以下为mysql概览 1-架构图 2-Detail csdn |简书 | 头条 | SegmentFault 思否 | 掘金 | 开源中国 |

程序员深夜写bug
昨天
10
0
golang微服务框架go-micro 入门笔记2.2 micro工具之微应用利器micro web

micro web micro 功能非常强大,本文将详细阐述micro web 命令行的功能 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go-micro环境, golang微服务框架...

非正式解决方案
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部