mybatis源码-plugin源码

本文介绍了MyBatis Plugin的工作原理,包括如何声明和配置自定义Plugin,以及在MyBatis启动时解析Plugin的过程。在执行SQL时,Plugin通过拦截器机制对Executor进行增强,创建动态代理对象,使得在调用query方法时能够执行自定义的intercept方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在使用mybatis的时候,我们可以自己指定plugin,在sql执行过程中,增加一些额外的逻辑处理,这篇笔记主要记录plugin的原理

使用

1、声明自定义的plugin

@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class , RowBounds.class, ResultHandler.class}))
public class TestPlugin1 implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("测试插件1被执行");
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

在自定义的plugin插件中,我们需要通过注解,执行当前插件作用于哪个对象?
在mybatis底层,有四个比较重要的对象,分别是:executor、parameterHandler、ResultSetHandler、StatementHandler

2、在全局配置文件中,增加plugin节点的配置

<plugins>
    <plugin interceptor="org.apache.ibatis.study.nativestu.TestPlugin1"></plugin>
    <plugin interceptor="org.apache.ibatis.study.nativestu.TestPlugin2"></plugin>
</plugins>

这样即可,在sql被执行的时候,会被自定义的plugin拦截到,执行对应的intercept()方法
但是,在使用的时候,需要通过注解,指定当前插件对哪个类生效,在mybatis底层,有四个比较重要的类,分别是:executor、parameterHandler、ResultSetHandler、StatementHandler

需要声明当前插件是作用于哪个类的哪个方法,我上面指定的是executor的query方法,为什么是query方法,因为不管是selectList还是selectOne,底层都是调用的executor的query方法

源码

在mybatis中的plugin,其实就是拦截器,自然,也肯定是通过代理来实现的

解析plugin

我们先说解析的逻辑,在mybatis中,会解析全局配置文件,在解析全局配置文件的时候,会解析配置,将我们自定义的plugin,转换为interceptor对象存储
在前面博客中,mybatis源码:mybatis的sql解析 有提到过,原生的mybatis,是从SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 这里的build方法开始的,所有的解析逻辑,都会在这个方法中完成;
所以我们直接调到这个方法中

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

这个方法是mybatis在解析了节点之后,会依次解析其子节点的方法
在这个方法中,有一行和本篇博客有关的代码

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


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

org.apache.ibatis.plugin.InterceptorChain
public void addInterceptor(Interceptor interceptor) {
  interceptors.add(interceptor);
 }
private final List<Interceptor> interceptors = new ArrayList<>();

上面这个方法,入参的node节点,就是,可以看到,在方法中,会获取到期children节点,然后依次获取到对应的interceptor配置,也就是我们自定义的插件的全类目,然后根据全类名,通过newInstance()方法来初始化对象,然后把对象设置到了configuration的interceptor属性中,最终,会添加到一个list集合中

以上,就是mybatis在启动时,解析plugin的逻辑

使用

把所有的自定义插件,解析成interceptor,然后存入到list集合中,自然是为了使用

SqlSession sqlSession = sqlSessionFactory.openSession();

这是要讲的代码,在这里的openSession()方法中,会获取到一个sqlSession对象,在初始化对象的时候,会先初始化一个executor对象,
在这里插入图片描述

这里中间的代码,不是本篇博客要关心的,所以就跳过,调用链可以参考上面的视频,我们只关心初始化executor的这个方法

org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 默认的executor是SimpleExecutor
    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);
    }
    //如果二级缓存开启,创建cachingExecutor,这里的executor会被包装在cachingExecutor中
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    /**
     * 上面的逻辑是根据不同的executorType类型来初始化不同的执行器
     * 下面是为executor生成动态代理对象
     */
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

这一段代码,就是根据当前指定的executorType初始化不同的对象,我们需要关心的,是倒数第二行代码

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

可以看到,这个方法就是从上面解析之后的list集合中,依次遍历,调用自定义插件的plugin()方法;
我们在自定义的plugin对应的plugin()方法中,调用的是Plugin.wrap()

所以,我们接着来看Plugin.wrap()的源码

public static Object wrap(Object target, Interceptor interceptor) {
    //1.获取@Intercepts注解的配置信息
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    // 2.获取当前target的class(这里的target,是从前面入参进来的,就是mybatis的四大对象其中的一个)
    Class<?> type = target.getClass();
    // 3.判断注解配置的类中,是否包含当前入参的target
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    /**
     * 4.如果包含,就生成代理对象
     * 可以看到这里入参的invocation对象,就是plugin
     */
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

所以,这几段代码的逻辑,连起来看,是这样的:

  1. 在初始化executor对象之后,会判断是否需要给当前executor生成动态代理对象
  2. 判断的依据是什么?就是我们自定义的插件,是否有要作用于executor对象的
  3. 如果有,就依次通过jdk代理,生成代理对象
  4. 所以,如果我们自定义了插件,作用于executor对象,最后生成的executor对象是一个代理对象,而这个代理对象,是包了N层(这里的N层就是指有N对plugin要作用于executor对象)

在我们执行selectList查询的时候,依赖于executor对象的query方法,所以,会被代理对象所拦截到,就会执行我们自定义插件的interceptor()方法

结论

所以,对于自定义插件的原理,大致是这样的:

  1. 在解析全局配置文件的时候,会解析; 然后将自定义的插件,转换为interceptor对象,存到内存中,以list集合的形式存入
  2. 然后在初始化executor时,会对executor进行增强处理
  3. 根据自定义的插件相关配置,来判断是否要对executor进行增强,如果需要增强,会通过jdk动态代理生成代理对象
  4. 在执行sql的时候,会被代理对象拦截到,然后先执行对应的intercept方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值