最近在用JFinal开发一个小型的项目,使用的是个人比较认可的HTTL模板引擎。过几天不忙了把相关集成的东西发出了。
今天先发一个:给JFinal添加了类似ROR的Flash功能。
在使用JFinal做开发时,经常会用到如下代码:
if(webUser.update()){ this.setSessionAttr("msg", "更新成功"); this.redirect("detail?id="+webUser.getLong("id")); return; } this.setAttr("msg", "更新失败"); render("edit.html");使用redirect后,会重定向到另一个页面,要传送一些信息,要么使用Session,要么拼装重定向的url地址来做。使用Session的话,需要考虑何时清除的问题,还有就是性能问题,Session中应该少放东西,少放大东西。使用拼装url地址的方法比较麻烦,容易出现错误,另外负责对象难以通过这种方式传输。当然如果系统设计成后端全是服务,返回json,这种问题就不会有了。只是个人习惯传统的页面跳转方式所以需要解决这个问题。
如何解决这个问题,想到RoR中的Flash操作,通过Fash保存数据,这些数据仅能在下次请求时获取,获取后随即清楚。最终通过Jfina的ehcache插件解决了此问题。
思路如下:
基于ehCache构建了一个FlashManager,负责将Flash保存在cache中。这个FlashManager是全局的,为了区分每个用户,使用了sessionKey(这个其实是SessionId);
package com.jfinal.core; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import com.jfinal.plugin.ehcache.CacheKit; /** * * Flash管理器 * * @author dafei * */ public class FlashManager { /** * 全局静态flash管理器 */ private static FlashManager flashManager = new FlashManager(); /** * falsh 存放在cache中的值。 */ private final String flashCacheName = "flashCache"; /** * 读写锁 */ private ReentrantLock lock = new ReentrantLock(); /** * 构造函数,不允许初始化 */ private FlashManager() { } /** * 单例方法 * * @return flash管理器 */ public static FlashManager getInstance() { return flashManager; } /** * 添加flash信息到缓存中。 * * @param sessionKey * session路径 * @param curAction * 当前ActionPath * @param key * 键 * @param value * 值 */ @SuppressWarnings("unchecked") public void setFlash(String sessionKey, String curAction, String key, Object value) { sessionKey = sessionKey + curAction.replace("/", "_"); lock.lock(); Object obj = CacheKit.get(flashCacheName, sessionKey); Map<String, Object> map = null; if (obj != null) { map = (Map<String, Object>) obj; } else { map = new HashMap<String, Object>(); CacheKit.put(flashCacheName, sessionKey, map); } map.put(key, value); lock.unlock(); } /*** * 在调用redirect forwardAction * 时调用此接口,将以当前actionPath为key更替为下一个请求actionPath作为key。 * * @param sessionKey * session的Id值 * @param curAction * 当前ActionPath * @param nextAction * 下一个ActionPath */ public void updateFlash(String sessionKey, String curAction, String nextAction) { String oldKey = sessionKey + curAction.replace("/", "_"); String newkey = sessionKey + nextAction.replace("/", "_"); lock.lock(); Object obj = CacheKit.get(flashCacheName, oldKey); if (obj != null) { CacheKit.remove(flashCacheName, oldKey); CacheKit.put(flashCacheName, newkey, obj); } lock.unlock(); } /** * 从cache中取得Flash的Map * * @param sessionKey * session路径 * @param curAction * 当前ActionPath * @return Flash的Map */ @SuppressWarnings("unchecked") public Map<String, Object> getFlash(String sessionKey, String curAction) { String sessionActionKey = sessionKey + curAction.replace("/", "_"); Map<String, Object> map = null; lock.lock(); Object obj = CacheKit.get(flashCacheName, sessionActionKey); if (obj != null) { map = (Map<String, Object>) obj; CacheKit.remove(flashCacheName, sessionActionKey); } lock.unlock(); return map; } }
修改Controler中相关代码:
增加了一个变量,用来记录是否设定了Flash信息
增加了两个方法:
/** * 根据当前路径构造将要跳转的路径的完整Action * @param currentActionPath 当前路径,类似 /sau/index * @param url 下一个路径,类似/au/login, detail?,admin/detail. * @return 下一个Action的完整路径() */ public String parsePath(String currentActionPath, String url){ if(url.startsWith("/")){//完整路径 return url.split("\\?")[0]; }else if(!url.contains("/")){//类似于detail的路径。 return "/"+ currentActionPath.split("/")[1] + "/" + url.split("\\?")[0]; }else if(url.contains("http:")|| url.contains("https:")){ return null; } ///abc/def","bcd/efg?abc return currentActionPath + "/" + url.split("\\?")[0]; } /** * 设定Flash,该flash中的所有信息将会出现在下一个请求中。 * 该操作一般用在forwardAction 及redirect操作前。 * 在设定Falsh拦截器后,拦截器会自动注入所有当前Action中设定的Flash信息到request中。 * 且仅注入一次。 * @param key 键 * @param value 值 */ public void setFlash(String key, Object value){ String sessionKey = this.getSession(false).getId(); String actionPath = this.request.getRequestURI(); FlashManager.getInstance().setFlash(sessionKey,actionPath, key, value); setFlashFalg = true; }
修改了三个方法:
public void forwardAction(String actionUrl) { if(setFlashFalg){//若有新加入的Flash。更换key。 String sessionKey = this.getSession(false).getId(); String actionPath = this.request.getRequestURI(); //将以当前actionPath为key更替为下一个请求actionPath作为key FlashManager.getInstance().updateFlash(sessionKey, actionPath, actionUrl); setFlashFalg =false; } render = new ActionRender(actionUrl); }
public void redirect(String url) { if(setFlashFalg){ String sessionKey = this.getSession(false).getId(); String actionPath = this.request.getRequestURI(); String newActionPath = parsePath(actionPath, url); FlashManager.getInstance().updateFlash(sessionKey, actionPath, newActionPath); setFlashFalg = false; } render = renderFactory.getRedirectRender(url); }
public void redirect(String url, boolean withQueryString) { if(setFlashFalg){ String sessionKey = this.getSession(false).getId(); String actionPath = this.request.getRequestURI(); String newActionPath = parsePath(actionPath, url); FlashManager.getInstance().updateFlash(sessionKey, actionPath, newActionPath); setFlashFalg = false; } render = renderFactory.getRedirectRender(url, withQueryString); }
这些解决了Flash的记录问题,怎样将数据在适当的时候可以让程序访问到呢?想到了拦截器。实现一个。
package com.jfinal.ext.interceptor; import java.util.Map; import java.util.Map.Entry; import com.jfinal.aop.Interceptor; import com.jfinal.core.ActionInvocation; import com.jfinal.core.Controller; import com.jfinal.core.FlashManager; /** * Flash拦截器。 * @author dafei * */ public class Flash implements Interceptor{ @Override /** * 该拦截器取得当前ActionPath,从Cache中检查是否有传送给当前Action的Flash对象Map * 若有,则遍历Map,并将所有key,value注入到当前的request请求中。 */ public void intercept(ActionInvocation ai) { String sessionKey = ai.getController().getSession(false).getId(); String curAction = ai.getViewPath()+ai.getMethodName(); Map<String, Object> flashMap = FlashManager.getInstance().getFlash(sessionKey, curAction); if(flashMap != null){ Controller c = ai.getController(); for(Entry<String,Object> flashEntry: flashMap.entrySet()){ c.setAttr(flashEntry.getKey(), flashEntry.getValue()); } } ai.invoke(); } }
实现完毕。
如何使用:
在ehcache.xml中添加一个
<cache name="flashCache" maxElementsInMemory="1024" eternal="false" timeToIdleSeconds="180" timeToLiveSeconds="180" overflowToDisk="true" diskSpoolBufferSizeMB="10" />
在Controller上或者需要传入数据的Action上启动Flash拦截器。
@Before({SessionInterceptor.class,Flash.class}) public class SmsApiUserController extends Controller @Before(Flash.class) public void detail(){ 。。。 }如何调用
if(webUser.update()){ this.setFlash("msg", "更新成功"); this.redirect("detail?id="+webUser.getLong("id")); return; } this.setAttr("msg", "更新失败"); render("edit.html");
总结一下:这个Flash实现主要解决 两个请求间的仅且一次的数据传递问题,当然这两个请求是有要求的:一个请求和其发起的另一个请求(redirect,forwardAction操作)。
目前用起来还不错。个人对JFinal初学,实现难免考虑不周。
目前该实现没有考虑到同一个Session中多个请求同时发起同一个请求的情况。
还请@JFinal 波总指导。