MyBatis - 插件之原理机制

简介: MyBatis - 插件之原理机制

image.png


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


MyBatis之所以通过SQL_XML(替代实现类),底层的操作就是MyBatis的核心四大对象。

责任链的模式相当于过滤器链。


一、代理链的生成


image.png


Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。下面以Executor为例。Mybatis在创建Executor对象时会执行下面一行代码

executor =(Executor) interceptorChain.pluginAll(executor);

InterceptorChain里保存了所有的拦截器,它在mybatis初始化的时候创建。上面这句代码的含义是调用拦截器链里的每个拦截器依次对executor进行plugin(插入)代码如下

/**
 * 每一个拦截器对目标类都进行一次代理
 * @paramtarget
 * @return 层层代理后的对象
 */
public Object pluginAll(Object target) {
    for(Interceptor interceptor : interceptors) {
        target= interceptor.plugin(target);
    }
    return target;
}

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

@Intercepts({
    @Signature(type = Executor.class, method ="update", args = {MappedStatement.class, Object.class})})
public class ExamplePlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }
}

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


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


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


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


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


二、Plugin.wrap方法

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

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代理的一个重要概念,不熟悉的请参考相关概念)
        return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target,interceptor,signatureMap));
    }
    return target;
} 

这个Plugin类有三个属性


  private Object target;//被代理的目标类


  private Interceptor interceptor;//对应的拦截器


  private Map, Set> signatureMap;//拦截器拦截的方法缓存


我们再次结合(Executor)interceptorChain.pluginAll(executor)这个语句来看,这个语句内部对


executor执行了多次plugin,第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象。第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1...这样通过每个代理类的target属性就构成了一个代理链(从最后一个executorProxyN往前查找,通过target属性可以找到最原始的executor类)。


三、代理链上的拦截

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


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对象,这个对象是什么样子的,他的功能又是什么呢?

public class Invocation {
  private Object target;
  private Methodmethod;
  private Object[]args;
  public Invocation(Object target, Method method, Object[] args) {
    this.target =target;
    this.method =method;
    this.args =args;
  }
  ...
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }
}

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


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


public Object intercept(Invocation invocation) throws Throwable {
   //完成代理类本身的逻辑
   ...
   //通过invocation.proceed()方法完成调用链的推进
   return invocation.proceed();
}

四、总结


image.png


image.png


针对多个拦截器,拦截同一个对象的同一个方法时,会有这般规则。


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


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


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


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


如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑。这里面的层层代理要多次生成动态代理,是比较影响性能的。虽然能指定插件拦截的位置,但这个是在执行方法时动态判断,初始化的时候就是简单的把插件包装到了所有可以拦截的地方。


因此,在编写插件时需注意以下几个原则


image.png

目录
打赏
0
0
0
0
37
分享
相关文章
MyBatis 实现分页的机制
MyBatis 的分页机制主要依赖于 `RowBounds` 对象和分页插件。`RowBounds` 实现内存分页,适合小数据量场景,通过设定偏移量和限制条数对结果集进行筛选。而针对大数据量,则推荐使用分页插件(如 PageHelper),实现物理分页。插件通过拦截 SQL 执行,动态修改语句添加分页逻辑,支持多种数据库方言。配置插件后,无需手动调整查询方法即可完成分页操作,提升性能与灵活性。
|
1月前
|
十二、MyBatis分页插件
十二、MyBatis分页插件
73 17
框架源码私享笔记(02)Mybatis核心框架原理 | 一条SQL透析核心组件功能特性
本文详细解构了MyBatis的工作机制,包括解析配置、创建连接、执行SQL、结果封装和关闭连接等步骤。文章还介绍了MyBatis的五大核心功能特性:支持动态SQL、缓存机制(一级和二级缓存)、插件扩展、延迟加载和SQL注解,帮助读者深入了解其高效灵活的设计理念。
深入 MyBatis-Plus 插件:解锁高级数据库功能
Mybatis-Plus 提供了丰富的插件机制,这些插件可以帮助开发者更方便地扩展 Mybatis 的功能,提升开发效率、优化性能和实现一些常用的功能。
709 26
深入 MyBatis-Plus 插件:解锁高级数据库功能
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
198 4
MyBatis缓存机制
MyBatis提供两级缓存机制:一级缓存(Local Cache)默认开启,作用范围为SqlSession,重复查询时直接从缓存读取;二级缓存(Second Level Cache)需手动开启,作用于Mapper级别,支持跨SqlSession共享数据,减少数据库访问,提升性能。
56 1
springboot整合mybatis-plus及mybatis-plus分页插件的使用
这篇文章介绍了如何在Spring Boot项目中整合MyBatis-Plus及其分页插件,包括依赖引入、配置文件编写、SQL表创建、Mapper层、Service层、Controller层的创建,以及分页插件的使用和数据展示HTML页面的编写。
springboot整合mybatis-plus及mybatis-plus分页插件的使用