如何动态添加拦截器

现在有这样的需求:根据数据库中设置的一个拦截器类路径,对本类动态添加拦截器。也可以这样理解:对特定的方法(如增删改查系列方法)执行可后期配置的拦截器。此特性主要用于“开发平台”等项目。

我原先的思路/代码,如下:

public class MyController extends Controller {
    public void index(){
        MyController mc = this;
        this.setJfastObject();
        try {
	    mc = diyObjectInterceptor();
	} catch (ClassNotFoundException e) {
	    e.printStackTrace();
	    renderText("未定义元数据拦截器");
	    return;
	} //绑定自定义元对象拦截器
	mc.afterBindIntercepter();
    }
    private MyController diyObjectInterceptor() throws ClassNotFoundException{
        //获取自定义拦截器
        //String intercept=this.getIntercept();
		
        //这里为了说明简便,直接写死拦截器路径
	String intercept = "com.core.MyIntercept";
	Class<? extends Interceptor> class1=null;
	MyController mc=this;
	if(intercept!=null && !intercept.equals("")){
    	    class1=(Class<? extends Interceptor>) Class.forName(intercept);
	    mc=Enhancer.enhance(this, class1);	
	}
	return mc;
    }
    
    private void afterBindIntercepter(){
        ……
    }
    
    ……
}
这个思路基本解决了动态绑定拦截器的目的;
但是有限制,比如,实际只有index方法添加了拦截器,index实现此功能时还需要其他方法配合。

有没有更好的方法?

花了两天的时间对詹总的方案进行了思考和实践,不知对与不对,下面写出核心代码和思路,请大家批评指正: (2017-18-19修改)

1、新建一个业务类,对需要进行全局业务拦截的方法进行定义:

package com.jfast.core.controller;

import com.jfast.core.interfaces.Often4Sql;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
/**
 * 增删改查
 * <br>方便业务拦截
 * @author 王维
 * hi.wangwei@qq.com
 * 2017年8月18日 下午12:03:07
 * @param <T>
 */
public class JfastService<T extends JfastController> implements Often4Sql<T>{
	public T delete(T c) {
		c.result = Db.use(c.ds).delete(c.tableName, c.pk, c.record);
		return c;
	}
	public T deletes(T c) {
		boolean res = true;//操作结果
		for(Record temp : c.recordList ){
			boolean tempResult;//本次循环的结果
			tempResult = Db.use(c.ds).delete(c.tableName, c.pk, temp);
			res = res && tempResult;
		}
		c.result = res;
		return c;
	}
        //……节约篇幅,其余方法省略
}

2、正如大家第一眼看到的,参数c是JfastController的子类,这个父类怎么定义的呢:

package com.jfast.core.controller;

import java.util.ArrayList;
import java.util.List;

import com.jfast.core.interfaces.Often4Sql;
import com.jfinal.core.Controller;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.activerecord.SqlPara;
/**
 * 配合业务拦截的控制器
 * @author 王维
 * hi.wangwei@qq.com
 * 2017年8月18日 下午2:41:57
 */
public class JfastController extends Controller implements Often4Sql<JfastController>{
	public Record record = new Record(); //待操作记录
	/**
	 * 写操作时:待操作记录集合
	 * 读操作时:读取结果集合
	 */
	public List<Record> recordList = new ArrayList<Record>();
	public String tableName = "";//数据库表名
	public String ds = "";//数据源
	public boolean result = true;//操作结果
	public String pk = "";//主键名
	public SqlPara sqlPara = new SqlPara();//数据库操作语句类
	public List<Record> fieldList = new ArrayList<Record>(); //字段集合
	
	@Override
	public JfastController deletes(JfastController c) {
		return null;
	}
	@Override
	public JfastController delete(JfastController c) {
		return null;
	}
        //……继续节省篇幅,省略其他方法
	
}

这个类的重点是对属性的定义,因为拦截器拦截到方法后,主要采用对类属性的“修改”达到拦截目的的。子类继承后,即得到被全局业务拦截的特性。

3、上面都省略了很多方法,但细心的观众一定注意到了,我写了一个接口Often4Sql。好吧,这里我就不省略了:

package com.jfast.core.interfaces;

/**
 * 
 * @author 王维
 * <pre>
 * 常用增删改查
 * 试图配合全局业务拦截器
 * </pre>
 * @param <E>
 */
public interface Often4Sql<T> {
	T delete(T c);
	T deletes(T c);
	T find(T c);
	T finds(T c);
	T add(T c);
	T adds(T c);
	T update(T c);
	T updates(T c);
}

哈哈,接口就是简洁,我就希望这类简洁的东西!是不是程序员都喜欢?

4、上面充其量是铺垫,下面来正主,继承JfastController这个父类,目的是获得拦截器需要的属性定义和方法,举例:(先贴原文,为了节省时间,可以先看后面的精简与解释)

package com.jfast.core.controller;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jfast.core.exception.NullTableException;
import com.jfast.core.intercept.validate.AddValidate;
import com.jfast.core.intercept.validate.DeleteValidate;
import com.jfast.core.intercept.validate.EditValidate;
import com.jfast.core.intercept.validate.ImportsValidate;
import com.jfast.core.model.JfastField;
import com.jfast.core.model.JfastObject;
import com.jfast.util.XX;
import com.jfinal.aop.Before;
import com.jfinal.aop.Duang;
import com.jfinal.kit.HttpKit;
import com.jfinal.kit.Kv;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.activerecord.SqlPara;
import com.jfinal.upload.UploadFile;
/**
 * 数据的增删改查
 * @author WangWei
 * @param <E>
 *
 */
public class MetaController extends JfastController {
	private static JfastObject jfastObject;
	private LinkSql data = new LinkSql(new HashMap<>());

	/**
	 * 删除
	 * @return 
	 */
	@Before(DeleteValidate.class)
	public boolean deletes(){
		String json = HttpKit.readData(getRequest());
		JSONArray jsonArray = null;
		try{
			jsonArray = JSONArray.parseArray(json);
		}catch (Exception e) {
			renderJson(Kv.by("errCode", 1).set("msg", "传入的数据格式有误!"));
			return false;
		}
		this.setJfastObject();
		ds = jfastObject.getStr("data_source");
		if(XX.isEmpty(ds)){
			renderJson(Kv.by("errCode", 1).set("msg","数据库可能被本损坏或篡改ds"));
			return false;
		}
		pk = jfastObject.getStr("pk_name");
		if(XX.isEmpty(pk)){
			renderJson(Kv.by("errCode", 1).set("msg","数据库可能被本损坏或篡改pk"));
			return false;
		}
		boolean isDelete = true;
		int success = 0;
		int fail = 0;
		for(int i=0;i<jsonArray.size();i++){
			JSONObject jsonObject = JSONObject.parseObject(jsonArray.get(i).toString());
			boolean temp = false;
			try {
				temp = this.deleteOne(jsonObject, pk);
			} catch (NullTableException e) {
				e.printStackTrace();
				temp = false;
			}
			if(temp){
				success++;
			}else{
				fail++;
			}
			isDelete = isDelete & temp;
		}
		if(isDelete){
			renderJson(Kv.by("errCode", 0).set("msg", "删除成功"));
		}else{
			renderJson(Kv.by("errCode", 1).set("msg", "删除失败:成功"+success+",失败"+fail));
		}
		return true;
	}
	
	/**
	 * 删除元数据
	 * @param jsonObject
	 * @return
	 * @throws NullTableException 
	 */
	private boolean deleteOne(JSONObject jsonObject ,String pkName) throws NullTableException{
		String pkValue = jsonObject.getString(pkName);
		tableName = jfastObject.getStr("table_name");
		if(XX.isEmpty(tableName)){
			tableName = jfastObject.getStr("view_table");
			if(XX.isEmpty(tableName)){
				throw new NullTableException("未查到相应数据表或视图");
			}
		}
		record.set("code", jsonObject.get("code"));
		record.set(pkName, pkValue);
		JfastService<MetaController> js = Duang.duang(new JfastService<>());
		MetaController mc = js.deletes(this);
		return mc.result;	
	}
}

上面的一段代码是我的真实代码,作为思路的展示,看着还是有点费劲的,我抽丝剥茧,对上述代码进行了压缩:

package com.jfast.core.controller;


import java.util.Map;//……继续省略导包行


/**
 * 数据的增删该查
 * @author WangWei
 * @param <E>
 *
 */
public class MetaController extends JfastController {

	/**
	 * 删除
	 * @return 
	 */
	@Before(DeleteValidate.class)
	public void deletes(){
	    recordList = getData();//通过各种手段获得recordList,至于getData()里的内容,大家根据需要自行定义
	    ds = getDs();//同上,获得数据源ds
	    tableName = getTable(); //同上,不择手段地获取tableName
	    pk = getPk();//同上,获取pk
	    MetaController mc = this;
	    boolean temp = true;
	    for(Record r :recordList){
	        this.record = r;
	        mc = this.deleteOne(this);
	        temp = temp && mc.result;
	    }
	    
    	    if(temp){
		renderJson(Kv.by("errCode", 0).set("msg", "删除成功"));
	    }else{
		renderJson(Kv.by("errCode", 1).set("msg", "删除失败"));
	    }
	}
	
	/**
	 * 删除元数据
	 * @param MetaController
	 * @return
	 * @throws NullTableException 
	 */
	private boolean deleteOne(MetaController mc) throws NullTableException{
		JfastService<MetaController> js = Duang.duang(new JfastService<>());
		MetaController mc = js.delete(this);
		return mc.result;	
	}
}


6、Duang.duang已经给业务类套上,但是拦截器在哪?是谁?全局业务拦截器来也:JfastGlobeServiceInterceptor。

package com.jfast.core.intercept;

import java.util.ArrayList;
import java.util.List;

import com.jfast.util.XX;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.core.Controller;
import com.jfinal.kit.Kv;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
/**
 * 全局业务拦截器
 * @author 王维
 * hi.wangwei@qq.com
 * 2017年8月18日 下午2:44:32
 * @param <T>
 */
public class JfastGlobeServiceInterceptor implements Interceptor{

	private Interceptor[] inters;
	private List<Interceptor> interList;
	
	protected final JfastGlobeServiceInterceptor addInterceptors(Interceptor... interceptors) {
		if (interceptors == null || interceptors.length == 0)
			throw new IllegalArgumentException("Interceptors can not be null");
		if (interList == null)
			interList = new ArrayList<Interceptor>();
		
		for (Interceptor ref : interceptors){
			interList.add(ref);
		}
		inters = interList.toArray(new Interceptor[interList.size()]);
		interList.clear();
		interList = null;
		
		return this;
	}
	
	public final void intercept(Invocation inv) {
		Interceptor temp = getObjectInterceptor(inv);
		if(XX.isEmpty(temp)){
			inv.invoke();
		}else{
			addInterceptors(temp);
			/*
			 * Jfinal源码中的InvocationWrapper不可见,
			 * 我只好自己复制了一个到项目中。
			 */
			new InvocationWrapper(inv, inters).invoke();
		}
	}
	
	private  Interceptor getObjectInterceptor(Invocation inv){
		Controller c = (Controller)inv.getArg(0);
		Record r = Db.use("jfast").findFirst(Db.getSqlPara("objects.getObjectInterceptorByCode", Kv.by("code",c.getPara(0))));
		Interceptor inter = null;
		String myIntercept = r.getStr("biz_intercept");
		if(!XX.isEmpty(myIntercept)){
			try {
				inter = (Interceptor) Class.forName(myIntercept).newInstance();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return inter;
	}

}

这里是重点之一。作为新手,这段代码也是我吃不准的地方,还请各位大神指教!!!

代码是根据 Jfinal 源码中的com.jfinal.aop.InterceptorStack改造的。

它是“特殊的拦截器”,因为它本身不拦截操作,只是动态的引入拦截器,由引入的拦截器执行拦截操作。

核心是从数据库中找到拦截器的记录,如果没有则返回null,最终不拦截;如果有,则引入后加入到拦截器队伍中。

这个拦截器要想生效,需要在配置类中定义:

package com.jfast.core.config;

import com.jfast.core.intercept.JfastGlobeServiceInterceptor;
//……省略

public class JfastConfig extends JFinalConfig {
	@Override
	public void configInterceptor(Interceptors me) {
		me.addGlobalServiceInterceptor(new JfastGlobeServiceInterceptor());
		me.addGlobalActionInterceptor(new JfastGlobeExceptionInterceptor());
		//……还可以有登录拦截器、权限拦截器等
	}
	//……

}

生效后,全局动态拦截的框架就搭好了。

用户每次触发被拦截的方法,JfastGlobeServiceInterceptor从数据库中查找与本次访问对应的拦截器,比如,某次访问查到有拦截器记录,且为com.jfast.core.ObjectInterceptor。

接下来就是该拦截器到底张啥样?

7、实际生效的拦截器ObjectInterceptor

以下是其代码和相关类的代码。

package com.jfast.core.intercept;

import java.util.List;

import com.jfast.core.controller.JfastController;
import com.jfast.core.intercept.JfastServicesInterceptor;
import com.jfinal.kit.Kv;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.activerecord.SqlPara;
/**
 * 元对象操作拦截器
 * @author 王维
 * hi.wangwei@qq.com
 * 2017年8月18日 下午4:49:02
 */
public class ObjectInterceptor extends JfastServicesInterceptor{
	/**
	 * 删除关联字段列表
	 * @para JfastController 控制器
	 */
	@Override
	public void afterDelete(JfastController jc){ //删除关联字段列表
		String code = jc.record.get("code");
		SqlPara sqlPara = Db.getSqlPara("field.selectForDeletes", Kv.by("objectCode", code));
		List<Record> list = Db.use(jc.ds).find(sqlPara);
		for(Record r : list){
			Db.use(jc.ds).delete("jfast_field", r);
		}
	}
}
package com.jfast.core.intercept;

import com.jfast.core.controller.JfastController;
import com.jfinal.aop.Invocation;
/**
 * 全局业务拦截器
 * @author 王维
 * hi.wangwei@qq.com
 * 2017年8月18日 下午2:44:32
 * @param <T>
 */
public class JfastServicesInterceptor extends JfastServicesAbstractInterceptor<JfastController>{
	@Override
	public void intercept(Invocation inv) {
		JfastController jc = (JfastController) inv.getArg(0);
		String method = inv.getMethodName();
		if(method.equals("find")){
			beforeFind(jc);
			inv.invoke();			
			afterFind(inv.getReturnValue());
		}else if(method.equals("add")){
			beforeAdd(jc);
			inv.invoke();
			afterAdd(inv.getReturnValue());
		}else if(method.equals("delete")){
			beforeDelete(jc);
			inv.invoke();
			afterDelete(inv.getReturnValue());
		}else if(method.equals("update")){
			beforeUpdate(jc);
			inv.invoke();
			afterUpdate(inv.getReturnValue());
		}else if(method.equals("finds")){
			beforeFinds(jc);
			inv.invoke();			
			afterFinds(inv.getReturnValue());
		}else if(method.equals("adds")){
			beforeAdds(jc);
			inv.invoke();
			afterAdds(inv.getReturnValue());
		}else if(method.equals("deletes")){
			beforeDeletes(jc);
			inv.invoke();
			afterDeletes(inv.getReturnValue());
		}else if(method.equals("updates")){
			beforeUpdates(jc);
			inv.invoke();
			afterUpdates(inv.getReturnValue());
		}
	}
	@Override
	public void beforeFind(JfastController jc) {}

	@Override
	public void afterFind(JfastController c) {}

	@Override
	public void beforeAdd(JfastController c) {}

	@Override
	public void afterAdd(JfastController c) {}

	@Override
	public void beforeUpdate(JfastController c) {}

	@Override
	public void afterUpdate(JfastController c) {}

	@Override
	public void beforeDelete(JfastController c) {}

	@Override
	public void afterDelete(JfastController c) {}

	@Override
	public void beforeFinds(JfastController jc) {}

	@Override
	public void afterFinds(JfastController c) {}

	@Override
	public void beforeAdds(JfastController c) {}

	@Override
	public void afterAdds(JfastController c) {}

	@Override
	public void beforeUpdates(JfastController c) {}

	@Override
	public void afterUpdates(JfastController c) {}

	@Override
	public void beforeDeletes(JfastController c) {}

	@Override
	public void afterDeletes(JfastController c) {}
}
package com.jfast.core.intercept;

import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
/**
 * 全局业务拦截器
 * @author 王维
 * hi.wangwei@qq.com
 * 2017年8月18日 下午12:44:32
 * @param <T>
 */
public abstract class JfastServicesAbstractInterceptor<T> implements Interceptor{

	@Override
	public void intercept(Invocation inv) {
		inv.invoke();
	}
	
	public abstract void beforeFind(T jc);
	public abstract void afterFind(T c);
	public abstract void beforeFinds(T jc);
	public abstract void afterFinds(T c);
	public abstract void beforeAdd(T c);
	public abstract void afterAdd(T c);
	public abstract void beforeAdds(T c);
	public abstract void afterAdds(T c);
	public abstract void beforeUpdate(T c);
	public abstract void afterUpdate(T c);
	public abstract void beforeUpdates(T c);
	public abstract void afterUpdates(T c);
	public abstract void beforeDelete(T c);
	public abstract void afterDelete(T c);
	public abstract void beforeDeletes(T c);
	public abstract void afterDeletes(T c);
	
}

至此,全局动态拦截的工作就结束了。

Tips:业务拦截器的触发不会打印在控制台。没关系,这个功能可以自己造:

我就喜欢在方法体里System.out.println("拦截器被触发");//不过看到有大神说尽量不要这样调试,不知道是何原因,哪位大神顺便告知下小弟?

评论区

JFinal

2017-08-07 19:23

换一个思路解决,在 configInterceptor(Interceptors me) 方法中添加一个业务层全局拦截器:
me.addGlobalServiceInterceptor(new MyInterceptor());

然后让这个 MyInterceptor 去决定哪些目标有拦截动作,哪些该直接放行,大致逻辑如下:
public void intercept(Invocation inv) {
Object target = inv.getTarget();
if (needInterceptTarget(target)) {
doInterept(target);
}

inv.invoke();
}

麻言

2017-08-16 14:15

@JFinal 我今天试着这样做了,但是与我的诉求有所差异。故,先求证我的理解是否正确:您提供的思路是为“不确定的方法”添加一个确定的拦截器MyInterceptor? 而我真正诉求是为“确定的方法”(比如index)添加一个“不确定的拦截器”(这个拦截器可同通过修改数据库指定,可更换——当然,生效的前提是需要预先在源码中写入对应的拦截器)。

JFinal

2017-08-16 16:07

@麻言 为了性能最大化,jfinal 里面的所有的控制层拦截器是在系统启动的时候,一次性建立好对应关系的,这个关系那好以后就存放在了 Action 这个对象里头,是不能改变的

但这仅仅是 jfinal 层面上的解决方案,你完全可以添加一个 jfinal 全局拦截器,然后在这个拦截器里面添加一个自己的拦截器架构,让这个架构可以动态配置就可以了

仿照一下 jfinal 的已有拦截器体系,在全局拦截器内部再搞一个拦截器体系出来就可以动态化了

重点在于对 Invocation 与 Interceptor 工作方式的理解决,可以看一下 InterceptorStack 这个类中的 intercept(...) 方法中的 new InvocationWrapper(inv, inters).invoke(); 这行代码,仿照着写一个很容易

JFinal

2017-08-16 16:09

我再补充一下,我前面讲的你可以利用 jfinal 全局拦截器,其实还可以做得更好,你可以做一个专用于动态的拦截器叫:
public class DynamicInterceptor implements Interceptor

然后完全参考 InterceptorStack 与 InvocationWrapper 的工作方式,只要在里面将内部的拦截器变成读数据库动态化就可以了

麻言

2017-08-19 14:39

@JFinal 詹总您好,根据您的提点,我对我的项目进行了改造。过程和结果也在正文中写出来了。我尽量写得很详细,不知您是否有空帮忙看看:思路是否正确?哪里还可以改进?

热门反馈

扫码入社