插件的配置与使用

在mybatis-config.xml配置文件中配置plugin结点,比如配置一个自定义的日志插件LogInterceptor和一个开源的分页插件PageInterceptor:

  1. <plugins>
  2. <plugin interceptor="com.crx.plugindemo.LogInterceptor"></plugin>
  3. <plugin interceptor="com.github.pagehelper.PageInterceptor">
  4. <property name="helperDialect" value="oracle" />
  5. </plugin>
  6. </plugins>

插件的工作原理

借助责任链模式,定义一系列的过滤器,在查询等方法执行时进行过滤,从而达到控制参数、调整查询语句和控制查询结果等作用。下面从插件的加载(初始化)、注册和调用这三个方面阐述插件的工作原理。

过滤器的加载(初始化)

和其他配置信息一样,过滤器的加载也会在myBatis读取配置文件创建Configuration对象时进行,相应的信息存储在Configuration的interceptorChain属性中,InterceptorChain封装了一个包含Interceptor的list:

  1. private final List<Interceptor> interceptors = new ArrayList<>();

在XMLConfigBuilder进行解析配置文件时执行pluginElement方法,生成过滤器实例,并添加到上述list中:

  1. private void pluginElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. String interceptor = child.getStringAttribute("interceptor");
  5. Properties properties = child.getChildrenAsProperties();
  6. Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor()
  7. .newInstance();
  8. interceptorInstance.setProperties(properties);
  9. configuration.addInterceptor(interceptorInstance);
  10. }
  11. }
  12. }

过滤器的注册

可以为Executor、ParameterHandler、ResultSetHandler和StatementHandler四个接口注册过滤器,注册的时机也就是这四种接口的实现类的对象的生成时机,比如Executor的过滤器的注册发生在SqlSessionFactory使用openSession方法构建SqlSession的过程中(因为SqlSession依赖一个Executor实例),ParameterHandler和StatementHandler的过滤器发生在doQuery等sql执行方法执行时注册,而ResultHandler的过滤器的注册则发生在查询结果返回给客户端的过程中。以Executor的过滤器的注册为例,经过了这样的过程:

现在详细的分析一下Plugin的wrap这个静态的包装方法:

  1. public static Object wrap(Object target, Interceptor interceptor) {
  2. // 从定义的Interceptor实现类上的注解读取需要拦截的类、方法
  3. Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  4. // Executor、ParameterHandler、ResultSetHandler、StatementHandler
  5. Class<?> type = target.getClass();
  6. // 从当前执行的目标类中进行匹配,过滤出符合当前目标的的过滤器
  7. Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  8. if (interfaces.length > 0) {
  9. // 动态代理生成Executor的代理实例
  10. return Proxy.newProxyInstance(type.getClassLoader(), interfaces,
  11. new Plugin(target, interceptor, signatureMap));
  12. }
  13. return target;
  14. }

上述代码中的getSignatureMap方法是解析Interceptor上面的注解的过程,从注解中读取出需要拦截的方法,依据@Signature的三个变量类、方法method和参数args就能通过反射唯一的定位一个需要拦截的方法。

  1. private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
  2. Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  3. if (interceptsAnnotation == null) {
  4. throw new PluginException(
  5. "No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
  6. }
  7. Signature[] sigs = interceptsAnnotation.value();
  8. Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
  9. for (Signature sig : sigs) {
  10. Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
  11. try {
  12. Method method = sig.type().getMethod(sig.method(), sig.args());
  13. methods.add(method);
  14. } catch (NoSuchMethodException e) {
  15. throw new PluginException(
  16. "Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
  17. }
  18. }
  19. return signatureMap;
  20. }

而getAllInterfaces方法是依据不同的目标对象(Executor等四种)进行过滤的过程,只给对应的目标进行注册:

  1. private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
  2. Set<Class<?>> interfaces = new HashSet<>();
  3. while (type != null) {
  4. for (Class<?> c : type.getInterfaces()) {
  5. if (signatureMap.containsKey(c)) {
  6. interfaces.add(c);
  7. }
  8. }
  9. type = type.getSuperclass();
  10. }
  11. return interfaces.toArray(new Class<?>[interfaces.size()]);
  12. }

至此,实际使用的Executor对象将是通过动态代理生成的Plugin实例。

过滤器的调用

在第二步中完成了过滤器的注册,在实际调用Executor时,将由实现了InvocationHandler接口的Plugin实例进行接管,对Executor相应方法方法的调用,将实际上调用动态代理体系下的invoke方法:

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. try {
  3. Set<Method> methods = signatureMap.get(method.getDeclaringClass());
  4. if (methods != null && methods.contains(method)) {
  5. Object result=interceptor.intercept(new Invocation(target, method, args));
  6. return result;
  7. }
  8. return method.invoke(target, args);
  9. } catch (Exception e) {
  10. throw ExceptionUtil.unwrapThrowable(e);
  11. }
  12. }

如前所述,插件的工作原理是基于责任链模式,可以注册多个过滤器,层层包装,最终由内而外形成了一个近似装饰器模式的责任链,最里面的基本实现是CachingExecutor:

从InterceptorChain的pluginAll方法可以看出这个结构的构造过程:

  1. public Object pluginAll(Object target) {
  2. for (Interceptor interceptor : interceptors) {
  3. // 从这可以看出过滤器的传递的过程:动态代理实例由内而外层层包装,类似于与装饰器的结构,基础 实现是一个Executor
  4. target = interceptor.plugin(target);
  5. }
  6. return target;
  7. }

这种由内而外的包装的栈结构从外向内层层代理调用,完成了责任链任务的逐级推送。从这个注册过程可以看到,在list中越前面的Interceptor越先被代理,在栈结构中越处于底层,执行的顺序越靠后。造成了注册顺序和执行顺序相反的现象。

插件的典型案例:PageHelper

pagehelper是一个实现物理分页效果的开源插件,并且在底层通过Dialect类适配了不同的数据库,其主要作用是拦截sql查询,构造一个查询总数的新的以"_COUNT"结尾的新sql,最终再进行分页查询。

自定义插件

定义Interceptor接口的实现类并在其上使用@Intercepts和@Signature注解进行过滤的类和方法,比如定义一个打日志的插件:

  1. @Intercepts({
  2. @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
  3. RowBounds.class, ResultHandler.class }),
  4. @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
  5. RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }), })
  6. public class LogInterceptor implements Interceptor {
  7. @Override
  8. public Object intercept(Invocation invocation) throws Throwable {
  9. System.out.println("进入了自定义的插件过滤器!");
  10. System.out.println("执行的目标是:" + invocation.getTarget());
  11. System.out.println("执行的方法是:" + invocation.getMethod());
  12. System.out.println("执行的参数是:" + invocation.getArgs());
  13. return invocation.proceed();
  14. }
  15. }

@Intercepts注解中包含了一个方法签名数组,即@Signature数组,@Signature有三个属性,type、method和args分别定义要拦截的类、方法名和参数,这样就可以通过反射唯一的确定了要拦截的方法。type即为在工作原理分析中提到的Executor、ParameterHandler、ResultSetHandler和StatementHandler,method配置对应接口中的方法。

最后

欢迎关注公众号:前程有光,领取一线大厂Java面试题总结+各知识点学习思维导+一份300页pdf文档的Java核心知识点总结!

java工作两年了,连myBatis中的插件机制都玩不懂,那你工作危险了!的更多相关文章

  1. java开发两年,连Spring中bean的装配都不知道?你怎么涨薪啊

    Spring 1.1.1.1 创建一个bean package com.zt.spring; public class MyBean { private String userName; privat ...

  2. PHP中的插件机制原理和实例

    PHP项目中很多用到插件的地方,更尤其是基础程序写成之后很多功能由第三方完善开发的时候,更能用到插件机制,现在说一下插件的实现.特点是无论你是否激活,都不影响主程序的运行,即使是删除也不会影响. 从一 ...

  3. 一次读懂mybatis中的缓存机制

    缓存功能针对于查询(没听说果UPDATE,INSERT语句要缓存什么,都是直接执行的) 默认情况下,mybatis会启用一级缓存. 如果使用同一个session对象调用了相同的SELECT语句,则直接 ...

  4. mybatis中分页插件PageHelper的使用

    转载博客:http://blog.csdn.net/u012728960/article/details/50791343

  5. Mybatis中的ognl表达式。及myabtis where标签/if test标签/trim标签

    1.mybatis默认支持使用ognl表达式来生成动态sql语句 MyBatis中可以使用OGNL的地方有两处: 动态SQL表达式中 ${param}参数中 上面这两处地方在MyBatis中处理的时候 ...

  6. MyBatis中的OGNL教程

    MyBatis中的OGNL教程 有些人可能不知道MyBatis中使用了OGNL,有些人知道用到了OGNL却不知道在MyBatis中如何使用,本文就是讲如何在MyBatis中使用OGNL. 如果我们搜索 ...

  7. JAVA的Proxy动态代理在自动化测试中的应用

    JAVA的动态代理,在MYBATIS中应用的很广,其核心就是写一个interface,但不写实现类,然后用动态代理来实例化并执行这个interface中的方法,话不多说,来看一个实现的例子: 1.先定 ...

  8. mybatis插件机制及分页插件原理

    MyBatis 插件原理与自定义插件: MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能.需要注意的是,如果没有完全理解MyBatis 的运行原理和插件的工作方式 ...

  9. mybatis(六)插件机制及分页插件原理

    转载:https://www.cnblogs.com/wuzhenzhao/p/11120848.html MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能.需要 ...

随机推荐

  1. shell脚本之字符串测试表达式

    1.字符串测试操作符 字符串测试操作符的作用有:比较两个字符串是否相同.字符串的长度是否为零,字符串是否为NULL(注:bash区分零长度字符串和空字符串等) 下表为常用字符串操作符 也可以通过man ...

  2. Learn day3 深浅拷贝/格式化/字符串/列表/字典/集合/文件操作

    1. pass break continue # ### pass break continue # (1) pass 过 """如果代码块当中,什么也不写,用pass来 ...

  3. 【Redis】Redis 持久化之 RDB 与 AOF 详解

    一.Redis 持久化 我们知道Redis的数据是全部存储在内存中的,如果机器突然GG,那么数据就会全部丢失,因此需要有持久化机制来保证数据不会一位宕机而丢失.Redis 为我们提供了两种持久化方案, ...

  4. NOI2020D1T1美食家

    传送门:QAQQAQ 完了完了NOI签到题全班打不出来,真就全部成为时代的眼泪了... 首先$O(mT)$的$dp$显然,然后因为$T$很大$w$很小矩阵快速幂显然,但是有$k=200$卡不过去. 然 ...

  5. 教你如何 分析 Android ANR 问题

    ANR介绍 ANR 的全称是 Application No Responding,即应用程序无响应,具体是一些特定的 Message (Key Dispatch.Broadcast.Service) ...

  6. 1.1 Prism安装

    Prism框架有很多安装包,即便用了很长一段时间,也可能会不知道如何安装框架.细心分析包的依赖关系,发现所有包均依赖与依赖注入扩展插件,以使用Unity为例,Prism.Unity依赖Prism.Wp ...

  7. layui常用的公共属性

    这个是今天看官网是觉得应该很有用的东西,记录在此.位置位于官网页面元素下的HTML规范:常用公共属性中.解释如下: lay-skin=" " 定义相同元素的不同风格,如checkb ...

  8. vim编辑器的常用命令

    按ESC键跳到命令模式,然后::w - 保存文件,不退出 vim.:w file -将修改另外保存到 file 中,不退出 vim.:w! -强制保存,不退出 vim .:wq -保存文件,退出 vi ...

  9. [MIT6.006] 9. Table Doubling, Karp-Rabin 双散列表, Karp-Rabin

    在整理课程笔记前,先普及下课上没细讲的东西,就是下图,如果有个操作g(x),它最糟糕的时间复杂度为Ο(c2 * n),它最好时间复杂度是Ω(c1 * n),那么θ则为Θ(n).简单来说:如果O和Ω可以 ...

  10. 使用配置文件方式记录Python程序日志

    开发者可以通过三种方式配置日志记录: 调用配置方法的Python代码显式创建记录器.处理程序和格式化程序. 创建日志配置文件并使用fileConfig() 函数读取. 创建配置信息字典并将其传递给di ...