如何为 ActiveRecord 的 Model、Db 添加拦截器

    今天有同学问到如何对 ActiveRecord 的组件添加拦截器:https://jfinal.com/feedback/7218 

    这个问题挺有意义,因为 jfinal 的拦截器平常看似都是针对 Controller、Service,让人误以为不支持这两个者组件以外的拦截。要是这么来看的话,那是对 jfinal 的灵活、强大缺乏深入认知。

    其实 jfinal 的拦截器是可以施加到几乎任何地方的,下面就以 Model 为例,将 Interceptor 用在 Model 上。

1、创建一个拦截器

public class Test implements Interceptor {
  @Override
  public void intercept(Invocation inv) {
    // 通过 inv.getMethodName() 得知当前的拦截的是哪个方法
    System.out.println(inv.getMethodName());

    // 通过 inv.getArgs() 可以获取当前被拦截方法的实参值
    Object[] args = inv.getArgs();
    for (Object arg : args) {
      System.out.println(arg);
    }
    
    // 通过 inv.setArg(...) 可以改变当前被拦截方法的实参值
    inv.setArg(0, null);
    
    // 调用被拦截的方法		
    inv.invoke();
    
    // 通过 inv.getReturnValue() 获取被拦截方法的返回值
    Object ret = inv.getReturnValue();
  }
}

     以上拦截器 Test 可以实现很多强大的功能:识别目标方法、获取实参、改变实参、调用目标方法、获取目标方法调用后的返回值等等。上述代码的注释中给出了部分方法的详细说明。

     此外通过 Invocation 对象还可以做很多事情,例如通过 inv.getMethod().getAnnotations() 可以获取被拦截方法上的 java 注解,从而可以实现很多通过注解扩展出来的功能。

    提问的 @netwild 同学,希望对 update、query 方法的 sql 进行预处理,只需要通过 if ( inv.getMethodName().equals("update") 这种方法判断被拦截方法,然后通过 inv.getArgs() 获取形参,再通过 inv.setArg(...) 进行干预就可以了。

2、配置拦截器

    假定需要拦截的 Model 是 Article

@Before(Test.class)
public class Article extends BaseArticle {

}

    注意上面的 Article 继承了 BaseArticle,是假定了你的 model 生成了 base model,如果没有生成的话,直接继承 Model 也可以。

3、在 serivce 中使用

    jfinal 最佳实践是在 service 中使用 dao

public class ArticleService {

   @Inject
   Article dao;

   public List<Article> findAll() {
      return dao.find("select * from article");
   }
}

     通过上面的方法为业务层注入 Article dao 对象,如果没有开启注入的话,可以使用 Aop 工具类。

public class ArticleService {

   Article dao = Aop.get(Article.class).dao();

   public List<Article> findAll() {
      return dao.find("select * from article");
   }
}

3、配置使用 cglib 代理实现

    对于 Model 的代理需要使用 cglib,配置如下:

public void configConstant(Constants me) {
  me.setToCglibProxyFactory();
}

   需要引入 cglib 依赖

<dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib-nodep</artifactId>
   <version>3.2.5</version>
</dependency>


    同理对于 Db.java 内的方法也可以像上面这样处理,大致流程一样,只是扩展方式稍有不同:

1、扩展 DbPro

@Before(Test.class)
public class MyDbPro extends DbPro {
   public MyDbPro(String configName) {
      super(configName);
   }
}

    以上的重点为:扩展类 MyDbPro 上添加了 @Before(Test.class) 拦截器。

2、切换为自己的 MyDbPro 实现

public void configPlugin(Plugins me) {
    ActiveRecordPlugin arp = new ActiveRecordPlugin(...);
    
    // 将自己扩展的 MyDbPro 切换掉默认实现
    arp.setDbProFactory((configName) -> new MyDbPro(configName) );

    me.add(arp);
}

     以上代码是在初始化 ActiveRecordPlugin 的地方通过 arp.setDbProFactory(...) 方法来接管 Db.java 内的所有方法。

     jfinal 有无数隐藏功能在文档中是不可能一一例举的,否则文档内容会膨胀到不可想象的地步。

     要掌握并运用这些隐藏功能,只需要熟悉 jfinal 源代码,在碰到一个新功能需求的时候,解决方案自然就显现出来了。

评论区

netwild

2020-04-12 21:59

感谢波总的启发,jfinal 的拦截器确实强大,就看怎么用。

弯道加速跑

2020-04-13 14:14

JFinal的拦截器是我见过的学习成本最低,功能最强大的拦截器了

haojay

2020-04-13 14:24

老规矩,先收藏,后点赞,最后再看

prelove

2020-04-14 16:18

强大!!!