【Mybatis】-03

Mybatis插件分析探索


插件的原理:责任链模式 + JDK动态代理(接口,代理对象、代理类 implment InvocationHandler)

01、了解mybatis的插件的接口

如果你要自定mybatis插件需要实现Interceptor接口,我们看下这个接口定义的方法。如下:

public interface Interceptor {
    Object intercept(Invocation invocation) throws Throwable;
    Object plugin(Object target);
    void setProperties(Properties properties);
}
  • intercept 方法:如果自定插件实现Interceptor覆盖intercept方法,这个方法是一个核心方法,里面参数Invocation对象,这个对象可以通过反射调度原来的对象的方法。

    插件的核心:其实会拦截四个接口的子对象,拦截以后会进入到intercept方法中进行业务的处理。Invocation对象可以获取到四个接口的具体。

  • plugin方法:target被拦截的对象。它的作用:把拦截的target对象变成一个代理对象。并且返回它?

    动态代理规则:当一个代理对象,执行某个方法的时候,就进入代理类的invoke方法中

  • setProperties方法:允许plugin在注册的时候,配置插件需要的参数,这个参数可以在mybatsi的核心配置文件中注册插件的时候,一起配置到文件中,如下:

    <plugin interceptor="com.kuangstudy.plugin.QueryLimitPlugin">
        <property name="limit" value="5"/>
        <property name="dbtype" value="mysql"/>
    </plugin>
    

在这里插入图片描述

02、Mybatis的插件的注册(代理生成)

插件的注册:在mybatis的核心配置文件中进行配置和注册,如下:

public class IdIncrementPlugin implements Interceptor 
public class MetaObjectPlugin implements Interceptor 
public class QueryLimitPlugin implements Interceptor 

覆盖三个方法即可

Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);

在配置文件中进行注册

<plugins>
    <plugin interceptor="com.kuangstudy.plugin.IdIncrementPlugin"></plugin>
    <plugin interceptor="com.kuangstudy.plugin.MetaObjectPlugin"></plugin>
    <plugin interceptor="com.kuangstudy.plugin.QueryLimitPlugin">
        <property name="limit" value="5"/>
        <property name="dbtype" value="mysql"/>
    </plugin>
</plugins>

03、Mybatis的插件的初始化

插件的初始化在Mybatis的初始化的时候完成的,这里通过XMLConfigBuilder中解析方法,生产Configuration对象进行的初始化,初始化它经过什么样子的流程呢?

在这里插入图片描述

核心代码:

1:插件对象的创建InterceptorChain

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

new XMLConfigBuilder(inputStream, environment, properties); 会实例化一个Configuration对象,在创建Configuration对象,会调用构造函数,InterceptorChain对象的创建,就是在Configuration的构造函数中进行了初始化,如下:

public Configuration() {
  this.interceptorChain = new InterceptorChain();
}

InterceptorChain 如下:

public class InterceptorChain {

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

  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);
  }

}

分析:

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

interceptors 这个集合就是把下面解析的插件,进行注册和收集的容器

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

addInterceptor方法:是插件注册的具体方法,调用这个方法会把自定义的插件,添加到集合中

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

pluginAll方法是:是把具体的四大接口的具体实现类,生成动态代理的方法。

2:解析注册号的插件

解析

 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

具体解析节点

private void parseConfiguration(XNode root) {
      pluginElement(root.evalNode("plugins"));    
}
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

上面就是把所以的插件注册到IntercetorChain的集合中,具体如下:

 configuration.addInterceptor(interceptorInstance);
 public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

最后就调用到了TnterceptorChain类中的addInterceptor的方法,这个方法其实就把所有的插件进行一个注册,注册到全局成员变量的interceptors 集合中。

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

总结

显然,初始化阶段的就是把插件保存到这个List集合中吗,等待后续将其取出使用。

同时这个类中有很关键的信息:属性的解析和注入

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      // 1: 这里循环解析配置文件中的所有定义的插件
      for (XNode child : parent.getChildren()) {
        // 2: 如果插件有配置属性。获取到配置的属性,然后把属性的值,注册到Properties对象中
        Properties properties = child.getChildrenAsProperties();
        // 3: 同时获取到具体的注册的插件对象
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        // 4 : 然后调用这个插件的setProperties的方法,然后属性值复制给插件中的属性去使用
        interceptorInstance.setProperties(properties);
        // 5 : 注册插件到集合中interceptors中
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

Mybatis插件的执行阶段

04、Mybatis的插件的围绕的四大接口

Mybatis插件又称拦截器,Mybatis采用责任链设计模式。

首先什么是责任链模式呢?就是一个对象,在Mybatis中可能是四大对象的其中一个,在多个角色中传递,处在传递链上任何角色都有处理它的机会。比如:你在公司是一个重要的人物,你需要请假一周,那么请假的流程是,首先你需要项目经理批准,最后总裁批准才能完成,你的请假请求就是一个对象,它经过项目经理、部门经理、总裁等多个角色的审批处理,每个角色都可以对你的请假做出修改和批示,这就是责任链模式。它的作用是让每个在责任 链上的角色都有机会拦截这个对象,在将来如果有新的角色也可以轻松拦截请求对象,进行处理。

通过动态代理组织多个插件(拦截器),通过这些插件可以改变Mybatis的默认行为。MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed) 拦截执行器的方法;

    @Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
    

    Executor :执行器接口:SimpleExcecutor、CachingExecutor、BaseExcecutor等.

  • ParameterHandler (getParameterObject, setParameters) 拦截参数的处理;

  • ResultSetHandler (handleResultSets, handleOutputParameters) 拦截结果集的处理;

  • StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理;

  • Executor是 Mybatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过 ResultSetHandler进行自动映射,另外,他还处理了二级缓存的操作。从这里可以看出,我们也是可以通过插件来实现自定义的二级缓存的。
  • StatementHandler是Mybatis直接和数据库执行sql脚本的对象。另外它也实现了Mybatis的一级缓存。这里,我们可以使用插件来实现对一级缓存的操作(禁用等等)。
  • ParameterHandler是Mybatis实现Sql入参设置的对象。插件可以改变我们Sql的参数默认设置。
  • ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口对象。我们可以定义插件对Mybatis的结果集自动映射进行修改。

比如:保存为例

@Test
public void findAccountTest() throws Exception{
    // 1.加载mybatis框架主配置文件sqlMapConfig.xml,
    InputStream inputStream = Resources.getResourceAsStream("mybatis.config.xml");
    // 2.读取解析配置文件内容,获取框架核心对象SqlSessionFactory
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = builder.build(inputStream);
    // 3.获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 4.执行数据库操作
    Account account = new Account();
    account.setMoney(12.4f);
    account.setName("zhangsan");
    sqlSession.insert("test.insertAccount",account);
    sqlSession.commit();
    // 5.释放资源
    sqlSession.close();
}

内部执行的过程


  public int insert(String statement, Object parameter) {
   // 参数1:statement = "test.insertAccount" 
   // 参数2:parameter = account  
    return update(statement, parameter);
  }

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      // 参数1:statement = "test.insertAccount" 
      // 参数2:parameter = account  
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

开始调用执行器处理的保存逻辑

 return executor.update(ms, wrapCollection(parameter));

executor 是以执行器,执行器初始化

创建SqlSession对象

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

初始化一个执行器

final Executor executor = configuration.newExecutor(tx, execType);

如下

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 1: 判断你执行器是不是null, 如果是null我就给你默认的执行器
    executorType = executorType == null ? defaultExecutorType : executorType;
    // 2: 判断你执行器是不是null, 给你简单执行器
    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;
  }

在这里看到一个非常核心的代码如下:

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

interceptorChain其实就是前面通过Configuration构造函数初始化的InterceptorChain的实例。然后调用pluginAll方法。这个方法如下:

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

这个方法在干嘛呢?

private final List<Interceptor> interceptors = new ArrayList<>();
interceptors.add(IdIncrementPlugin implements Interceptor) 覆盖了:plugin()
interceptors.add(MetaObjectPlugin implements Interceptor)覆盖了:plugin()
interceptors.add(QueryLimitPlugin implements Interceptor)覆盖了:plugin()
    
// target对象:Executor对象  
public Object pluginAll(Object target) {
    // 循环的调用集合中注册的插件,然后把三个插件是否符合规则插件找到,然后并且进行target对象的增强处理
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}
 target = interceptor.plugin(target);

上面是核心代码,这个代码就是把target(执行器对象)进行对象变换:变成代理对象,具体如下:

package com.kuangstudy.plugin;

import com.kuangstudy.increment.IdType;
import com.kuangstudy.increment.Sequence;
import com.kuangstudy.increment.TableId;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;

import java.lang.reflect.Field;
import java.util.Properties;
import java.util.UUID;

/**
 * @version: 1.0
 * @author: tesla
 * @className: Myplugin
 * @packageName: com.tesla.mybatis.plugin
 * @description: 自定义插件拦截器,实现字段的属性注入
 * @data: 2020/7/6 15:10
 *
 */

@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class IdIncrementPlugin implements Interceptor {

    private Properties properties;

    //获取到拦截的对象,底层也是通过代理来实现的,实际上是拿到一个目标的代理对象
    @Override
    public Object plugin(Object target) {
        // 生成代理对象
        return Plugin.wrap(target, this);
    }

}
public class Plugin implements InvocationHandler {

  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class<?>, Set<Method>> 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) {
        // 把具体的接口的对象,变换一个代理对象
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

}

Plugin 对象是什么呢?一个代理类?里面内部的原理就是一个JDK动态代理。

05、 代理对象执行方法会进入到invoke方法进行处理

在这里插入图片描述

06、总结

问:你对mybatis插件的理解是什么。你谈一下mybatis插件你的认识是什么?

我使用mybatis也开发过两三个项目了,在其中的一个项目,我自己去开发过一个插件。那时候因为公司需求所以我对进行一个研究,通过研究分析:其实里面使用机制就是:jdk动态代理 + 责任链的设计模式。我认识的是:

插件的运行和注册会分为几个阶段:

1:定义阶段,我定义个插件类,然后实现Interceptor接口,覆盖这个接口三个方法,分别是:plugin方法,interceptor方法,setProperties。

  • intercept 方法:如果自定插件实现Interceptor覆盖intercept方法,这个方法是一个核心方法,里面参数Invocation对象,这个对象可以通过反射调度原来的对象的方法。

    插件的核心:其实会拦截四个接口的子对象,拦截以后会进入到intercept方法中进行业务的处理。Invocation对象可以获取到四个接口的具体。

  • plugin方法:target被拦截的对象。它的作用:把拦截的target对象变成一个代理对象。并且返回它?

    动态代理规则:当一个代理对象,执行某个方法的时候,就进入代理类的invoke方法中

  • setProperties方法:允许plugin在注册的时候,配置插件需要的参数,这个参数可以在mybatsi的核心配置文件中注册插件的时候,一起配置到文件中,如下:

2:然后会插件进行一个注册,写入到mybatis配置文件中,如果是spring整合myabtis化,就使用配置类来进行插件的注册。

3:同时在定义的时候,会通过@Intercepts注解和签名,来告诉插件具体要拦截那些类执行的方法。mybatis对四个接口实现类都会进行拦截,这个四大接口是:

  • Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed) 拦截执行器的方法;

    @Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
    

    Executor :执行器接口:SimpleExcecutor、CachingExecutor、BaseExcecutor等.

  • ParameterHandler (getParameterObject, setParameters) 拦截参数的处理;

  • ResultSetHandler (handleResultSets, handleOutputParameters) 拦截结果集的处理;

  • StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理;

4:然后在运行和执行阶段,比如如果我定义了执行器的插件,这个时候在初始化sqlsession的时候,会确定一个执行器,而执行器在创建的时候,会调用executor = (Executor) interceptorChain.pluginAll(executor);这个方法的作用:就是把执行器对象变成一个代理对象,而代理对象的生成,是通过插件的的plugin方法进行生成和创建,具体的话是通过代理类Plugin中的wrap方法创建而生成,生成executor代理对象之后,当代理执行器执行方法的时候,就进入Plugin代理类中invoke方法中进行业务处理。如果你执行的代理类的方法和signatureMap中进行比较,如果存在就进入插件的intercept方法进行业务增强处理。:

5:总结其实Plugin代理类是通过jdk动态代理来实现的。

06、Mybatis-plus的分页插件的注册查看核心代码

07、Mybatis插件总结

1:能不用插件尽量不要使用,因为它会修改mybatis的底层结构

2:插件的生成采用的素层层代理的责任链的模式,通过反射方法进行运行,性能并不会很高,所以减少插件就能减少代理,从而提升的系统性能。

3:编写插件需要了解mybatis的运行原理,了解四大接口对象的子类和具体方法,准确判断需要拦截的对象,什么方法,参数是什么,才能够确定签名如何编写。

4:在插件中往往需要读取和修改mybatis映射器中的对象属性,你需要熟练的掌握内部的源码和结构。

5:尽量少改动mybatis底层的东西,以减少错误的方法

6:插件的原理是:jdk动态代理的改写,具体代理类是:Plugin 。具体代理对象生成是在四个接口的创建的时候进行注册调用interceptors的pluginAll方法.

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值