现在有这样的需求:根据数据库中设置的一个拦截器类路径,对本类动态添加拦截器。也可以这样理解:对特定的方法(如增删改查系列方法)执行可后期配置的拦截器。此特性主要用于“开发平台”等项目。
我原先的思路/代码,如下: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;
/**
* 增删改查
*
方便业务拦截
* @author 王维
* 2017年8月18日 下午12:03:07
* @param
*/
public class JfastService implements Often4Sql{
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 王维
* 2017年8月18日 下午2:41:57
*/
public class JfastController extends Controller implements Often4Sql{
public Record record = new Record(); //待操作记录
/**
* 写操作时:待操作记录集合
* 读操作时:读取结果集合
*/
public List recordList = new ArrayList();
public String tableName = "";//数据库表名
public String ds = "";//数据源
public boolean result = true;//操作结果
public String pk = "";//主键名
public SqlPara sqlPara = new SqlPara();//数据库操作语句类
public List fieldList = new ArrayList(); //字段集合
@Override
public JfastController deletes(JfastController c) {
return null;
}
@Override
public JfastController delete(JfastController c) {
return null;
}
//……继续节省篇幅,省略其他方法
}
这个类的重点是对属性的定义,因为拦截器拦截到方法后,主要采用对类属性的“修改”达到拦截目的的。子类继承后,即得到被全局业务拦截的特性。
3、上面都省略了很多方法,但细心的观众一定注意到了,我写了一个接口Often4Sql。好吧,这里我就不省略了:package com.jfast.core.interfaces;
/**
*
* @author 王维
*
* 常用增删改查
* 试图配合全局业务拦截器
*
* @param
*/
public interface Often4Sql {
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
*
*/
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
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 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
*
*/
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 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 王维
* 2017年8月18日 下午2:44:32
* @param
*/
public class JfastGlobeServiceInterceptor implements Interceptor{
private Interceptor[] inters;
private List 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();
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 王维
* 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 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 王维
* 2017年8月18日 下午2:44:32
* @param
*/
public class JfastServicesInterceptor extends JfastServicesAbstractInterceptor{
@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 王维
* 2017年8月18日 下午12:44:32
* @param
*/
public abstract class JfastServicesAbstractInterceptor 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("拦截器被触发");//不过看到有大神说尽量不要这样调试,不知道是何原因,哪位大神顺便告知下小弟?