返回顶部
首页 > 资讯 > 数据库 >Mybatis之插件原理
  • 521
分享到

Mybatis之插件原理

2024-04-02 19:04:59 521人浏览 泡泡鱼
摘要

鲁春利的工作笔记,好记性不如烂笔头转载自:深入浅出mybatis-插件原理Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如sql重写之类

鲁春利的工作笔记,好记性不如烂笔头



转载自:深入浅出mybatis-插件原理


Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如sql重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。


代理链的生成

Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。


下面以Executor为例。Mybatis在创建Executor对象时:

org.apache.ibatis.session.SqlSessionFactory.openSession()
    org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession()
        org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource(execType, level, false) {
            final Executor executor = configuration.newExecutor(tx, execType);
        }

说明:org.apache.ibatis.builder.xml.XMLConfigBuilder类解析MyBatis的配置文件,获取其中配置的Interceptor并调用configuration.addInterceptor(interceptorInstance);配置到拦截器链中。


org.apache.ibatis.session.Configuration

package org.apache.ibatis.session;
public class Configuration {

 protected Environment environment;

 protected String logPrefix;
  protected Class <? extends Log> logImpl;
  protected Class <? extends VFS> vfsImpl;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  // 默认的Executor
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  
   // InterceptorChain里保存了所有的拦截器,它在mybatis初始化的时候创建。
   protected final InterceptorChain interceptorChain = new InterceptorChain();
   
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
}

org.apache.ibatis.plugin.InterceptorChain

package org.apache.ibatis.plugin;


public class InterceptorChain {

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

  
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

下面以一个简单的例子来看看这个plugin方法里到底发生了什么。

package com.invicme.common.persistence.interceptor;

import java.sql.Connection;
import java.lang.reflect.Method;
import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@Intercepts(value = { 
        @Signature(type=Executor.class, method="query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type=StatementHandler.class, method="prepare", args={Connection.class})
    })
public class SamplePagingInterceptor implements Interceptor {
    private Logger logger = LoggerFactory.getLogger(getClass());
    
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        logger.info("target is {}", target.getClass().getName());
        
        Method method = invocation.getMethod();
        logger.info("method is {}", method.getName());
        
        Object[] args = invocation.getArgs();
        for (Object arg : args) {
            logger.info("arg is {}", arg);
        }
        
        Object proceed = invocation.proceed();
        logger.info("proceed is {}", proceed.getClass().getName());
        
        return proceed;
    }

    
    @Override
    public Object plugin(Object target) {
        logger.info("target is {}", target.getClass().getName());
        return Plugin.wrap(target, this);
    }

    
    @Override
    public void setProperties(Properties properties) {
        String value = properties.getProperty("sysuser");
        logger.info("value is {}", value);
    }

}

每一个拦截器都必须实现上面的三个方法,其中:

1)       Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。

2)       Object plugin(Object target)就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this)来完成的,把目标target和拦截器this传给了包装函数。

3)       setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。

注解里描述的是指定拦截方法的签名  [type, method, args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。


代理链的生成

为了更好的理解后面的内容,穿插一个Java反射的示例

package com.invicme.common.aop.proxy;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.PluginException;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.invicme.common.persistence.interceptor.SamplePagingInterceptor;


public class MainDriver {

    private static Logger logger = LoggerFactory.getLogger(MainDriver.class);

    public static void main(String[] args) {
        SamplePagingInterceptor interceptor = new SamplePagingInterceptor();
        wrap(interceptor);
    }
    
    public static Object wrap (SamplePagingInterceptor interceptor) {
        // 获取拦截器对应的@Intercepts注解
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        {
            for (Iterator<Class<?>> it = signatureMap.keySet().iterator(); it.hasNext(); ) {
                Class<?> clazz = it.next();
                Set<Method> set = signatureMap.get(clazz);
                logger.info("【Signature】clazz name is {}", clazz.getName());
                for (Iterator<Method> itt = set.iterator(); itt.hasNext(); ) {
                    Method method = itt.next();
                    logger.info("\tmethod name is {}", method.getName());
                    Type[] genericParameterTypes = method.getGenericParameterTypes();
                    for (Type type : genericParameterTypes) {
                        logger.info("\t\tparam is {}", type);
                    }
                }
            }
        }
        
        logger.info("================================================================");
        {
            Configuration configuration = new Configuration();
            Executor executor = configuration.newExecutor(null);
            Class<?>[] interfaces = executor.getClass().getInterfaces();
            for (Class<?> clazz : interfaces) {
                logger.info("【Interface】clazz name is {}", clazz);
            }
        }
        
        return null;
    }

    
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        // issue #251
        if (interceptsAnnotation == null) {
            throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        }
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
        for (Signature sig : sigs) {
            // 处理重复值的问题
            Set<Method> methods = signatureMap.get(sig.type());
            if (methods == null) {
                methods = new HashSet<Method>();
                signatureMap.put(sig.type(), methods);
            }
            try {
                Method method = sig.type().getMethod(sig.method(), sig.args());
                methods.add(method);
            } catch (NoSuchMethodException e) {
                throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
            }
        }
        return signatureMap;
    }
}

Mybatis之插件原理


继续进行Plugin代码的说明。

从前面可以看出,每个拦截器的plugin方法是通过调用Plugin.wrap方法来实现的。代码如下:

package org.apache.ibatis.plugin;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.ibatis.reflection.ExceptionUtil;


public class Plugin implements InvocationHandler {

  private Object target;
  private Interceptor interceptor;
  private Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  public static Object wrap(Object target, Interceptor interceptor) {
    // 从拦截器的注解中获取拦截的类名和方法信息  
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // 解析被拦截对象的所有接口(注意是接口)  
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      // 生成代理对象,Plugin对象为该代理对象的InvocationHandler
      //(InvocationHandler属于java代理的一个重要概念,不熟悉的请参考Java动态代理)
       // Http://luchunli.blog.51cto.com/2368057/1795921  
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclarinGClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

}

这个Plugin类有三个属性:

private Object target;                  // 被代理的目标类
private Interceptor interceptor;             // 对应的拦截器
private Map<Class<?>, Set<Method>> signatureMap;    // 拦截器拦截的方法缓存


关于InvocationHandler请参阅:Java动态代理


我们再次结合(Executor)interceptorChain.pluginAll(executor)这个语句来看,这个语句内部对executor执行了多次plugin(org.apache.ibatis.plugin.InterceptorChain)。

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
  
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

说明:

    拦截器在解析MyBatis的配置文件时已经add到了该interceptors拦截器中。

    org.apache.ibatis.builder.xml.XMLConfigBuilder类解析MyBatis的配置文件,获取其中配置的Interceptor并调用configuration.addInterceptor(interceptorInstance);配置到拦截器链中。


过程分析:

    第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象;

    第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1...;

    这样通过每个代理类的target属性就构成了一个代理链(从最后一个executorProxyN往前查找,通过target属性可以找到最原始的executor类)。


代理链的拦截

代理链生成后,对原始目标的方法调用都转移到代理者的invoke方法上来了。Plugin作为InvocationHandler的实现类,它(org.apache.ibatis.plugin.Plugin)的invoke方法是怎么样的呢?

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        // 调用代理类所属拦截器的intercept方法
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

在invoke里,如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法。我们看到传递给拦截器的是一个Invocation对象,这个对象是什么样子的,他的功能又是什么呢?

package org.apache.ibatis.plugin;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class Invocation {

  private Object target;
  private Method method;
  private Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  // 拦截器最终调用的方法,实际还是通过反射来调用的
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}

可以看到,Invocation类保存了代理对象的目标类,执行的目标类方法以及传递给它的参数。

在每个拦截器的intercept方法内,最后一个语句一定是invocation.proceed()(不这么做的话拦截器链就断了,你的mybatis基本上就不能正常工作了)。invocation.proceed()只是简单的调用了下target的对应方法,如果target还是个代理,就又回到了上面的Plugin.invoke方法了。这样就形成了拦截器的调用链推进。


在MyBatis中配置的拦截器方式为:

    <plugins>
        <plugin interceptor="com.invicme.common.persistence.interceptor.SamplePagingInterceptor"></plugin>
        <plugin interceptor="com.invicme.common.persistence.interceptor.PreparePaginationInterceptor"></plugin>
    </plugins>

我们假设在MyBatis配置了一个插件,在运行时会发生什么?

1)       所有可能被拦截的处理类都会生成一个代理

2)       处理类代理在执行对应方法时,判断要不要执行插件中的拦截方法

3)       执行插接中的拦截方法后,推进目标的执行

如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑。


您可能感兴趣的文档:

--结束END--

本文标题: Mybatis之插件原理

本文链接: https://lsjlt.com/news/38926.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Mybatis之插件原理
    鲁春利的工作笔记,好记性不如烂笔头转载自:深入浅出Mybatis-插件原理Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类...
    99+
    2024-04-02
  • Mybatis 插件原理解析
    Mybati s作为⼀个应⽤⼴泛的优秀的ORM开源框架,这个框架具有强⼤的灵活性,在四⼤组件 (Executor...
    99+
    2024-04-02
  • MyBatis插件的原理是什么
    本篇文章为大家展示了MyBatis插件的原理是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。 插件原理分析mybatis插件涉及到的几个类:我将以 E...
    99+
    2024-04-02
  • mybatis-plugin插件执行原理解析
    mybatis-plugin插件执行原理 今天主要是在看mybatis的主流程源码,其中比较感兴趣的是mybatis的plugin功能,这里主要记录下mybatis-plugin的插...
    99+
    2022-11-13
    mybatis-plugin插件 mybatis-plugin插件原理
  • MyBatis分页插件PageHelper的使用与原理
    目录MyBatis使⽤PageHelper1.limit分⻚2.PageHelper插件MyBatis使⽤PageHelper 1.limit分⻚ (1)概念: ①页码:pageNu...
    99+
    2023-02-24
    MyBatis分页插件PageHelper MyBatis分页插件 MyBatis PageHelper
  • IDEA插件之Mybatis log插件安装及使用
    一 前言分析 我们在idea控制台看见的sql日志通常是这样的,实际开发调试中我们想把完的sql复制出来,到数据库中执行分析数据情况。但是如果我们的sql有动态传参控制台输出的sq入参会用“?”代替入参,不能直接使用。 SqlSession...
    99+
    2023-08-16
    mybatis intellij-idea java
  • Mybatis源码分析之插件模块
    Mybatis插件模块 插件这个东西一般用的比较少,就算用的多的插件也算是PageHelper分页插件; PageHelper官网:https://github.com/pagehe...
    99+
    2024-04-02
  • Mybatis第三方PageHelper分页插件的使用与原理
    目录​用法​原理PageHelper.startPage做了什么Page分页信息在哪使用拦截器插件拦截器链加载&调用拦截器@Intercepts注解通过PageHelper创...
    99+
    2024-04-02
  • uni-app之android原生插件开发
    一 插件简介 1 当HBuilderX中提供的能力无法满足App功能需求,需要通过使用Andorid/iOS原生开发实现时,可使用App离线SDK开发原生插件来扩展原生能力。 2 插件类型有两种,Module模式和Component模式 ...
    99+
    2023-10-11
    uni-app
  • Mybatis(七):分页插件
    Mybatis(七):分页插件 前言一、概述二、安装和配置三、使用分页插件四、总结 前言 本博主将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识,有兴趣的小伙伴可以关注博主!也...
    99+
    2023-09-14
    mybatis java 数据库
  • Mybatis实现分表插件
    背景 事情是酱紫的,阿星的上级leader负责记录信息的业务,每日预估数据量是15万左右,所以引入sharding-jdbc做分表。 上级leader完成业务的开发后,走了一波自测,...
    99+
    2024-04-02
  • 如何开发MyBatis插件
    本篇内容介绍了“如何开发MyBatis插件”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1.MyBatis...
    99+
    2024-04-02
  • Mybatis中的分页插件
    目录 一.为什么要使用分页插件? 二.分页常用标签 三.分页插件的使用         1.在mybatis的pom中添加分页插件依赖         2.在mybatis-config.xml中创建分页插件 3.在test文件中进行查询操...
    99+
    2023-09-01
    mysql 开发语言 mybatis java
  • Vue3.0插件执行原理与实战
    目录一、编写插件1.1 包含install()方法的Object1.2 通过function的方式二、使用插件三、app.use()是如何执行插件的一、编写插件 Vue项目能够使用很...
    99+
    2024-04-02
  • Vue3.0插件执行原理是什么
    这篇文章主要介绍“Vue3.0插件执行原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue3.0插件执行原理是什么”文章能帮助大家解决问题。一、编写插件Vue项目能够使用很多插件来丰富自己...
    99+
    2023-06-29
  • Vuesnippets插件原理与使用介绍
    目录vue-snippetssnippets是什么vue-3-snippets插件支持的功能vue-snippets 随着开发者的年限的增加,不仅头发有点少,重复的代码也不断的增多,...
    99+
    2022-11-13
    Vue snippets原理 Vue snippets插件
  • 浅谈Mybatis乐观锁插件
    背景:对于数据库的同一条记录,假如有两个人同时对数据进行了修改,然后最终同步到数据库的时候,因为存在着并发,产生的结果是不可预料的。最简单的解决方式就是通过给表的记录加一个version字段,记录在修改的时候需要比较一下version是否匹...
    99+
    2023-05-30
    mybatis 乐观锁 插件
  • 详解Mybatis的分页插件
    一、概述 Mybatis 是一款非常流行的持久层框架,可以帮助我们轻松地实现数据库操作和数据访问。在 Mybatis中,如何对数据进行分页是一个非常常见的问题,现在,我们可以通过使用...
    99+
    2023-05-19
    Mybatis分页 Mybatis插件
  • Android Studio插件之Jenkins插件详解
    现在我就来介绍Android Studio上的Jenkins插件,让你可以更加方便地使用Jenkins。用Jenkins持续集成很久了,再Android Studio上的Jen...
    99+
    2022-06-06
    Android Studio studio jenkins Android
  • Mybatis工作原理
    作者:wuxinliulei链接:https://www.zhihu.com/question/25007334/answer/266187562来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。Mybatis原...
    99+
    2023-06-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作