给JFinal添加了类似ROR的Flash功能。

最近在用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 波总指导。





你可能感兴趣的:(Flash,jFinal)