现在有这样的需求:根据数据库中设置的一个拦截器类路径,对本类动态添加拦截器。也可以这样理解:对特定的方法(如增删改查系列方法)执行可后期配置的拦截器。此特性主要用于“开发平台”等项目。
我原先的思路/代码,如下:
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("拦截器被触发");//不过看到有大神说尽量不要这样调试,不知道是何原因,哪位大神顺便告知下小弟?
me.addGlobalServiceInterceptor(new MyInterceptor());
然后让这个 MyInterceptor 去决定哪些目标有拦截动作,哪些该直接放行,大致逻辑如下:
public void intercept(Invocation inv) {
Object target = inv.getTarget();
if (needInterceptTarget(target)) {
doInterept(target);
}
inv.invoke();
}