mybatis源码解读(8)

插件配置的解析和插件功能的实现

我们看下文档对插件功能的介绍:

插件(plugins)
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 的发行包中的源代码。假设你想做的不仅仅是监控方法的调用,那么你应该很好的了解正在重写的方法的行为。因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。这些都是更低层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定了想要拦截的方法签名即可。
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = “update”,
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}






上面的插件将会拦截在 Executor 实例中所有的“update”方法调用,这里的 Executor 是负责低层映射语句执行的内部对象。
NOTE 覆盖配置类
除了用插件来修改 MyBatis 核心行为之外,还可以通过完全覆盖配置类来达到目的。只需继承后覆盖其中的每个方法,再把它传递到 sqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,这可能会严重影响 MyBatis 的行为,务请慎之又慎。

可以看出,通过插件的方式,可以影响mybatis在整个执行链路中的行为。

相比于插件功能的实现,插件配置的解析相对于来说就太简单了。

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

resolveClass(interceptor)这个方法,以后会常看到,根据传入的字符串类名返回相应的Class对象。并且支持别名方式。有兴趣可以看下实现代码,没有几行。

整个解析的流程只是简单地读取插件配置的类信息,转化为Interceptor 后,设置上配置上的属性。然后加到configuration的全局interceptors变量中。

插件从配置文件中解析出来了
那插件又是怎样执行的呢?
我们得找一个切入点。

还记得我们之前在查看执行过程时有看到类似这样的代码。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    ...
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

就是在创建一些执行器(包括newStatementHandler,newParameterHandler,newResultSetHandler和newExecutor方法,都在Configuration中可以找到)的时候,会执行
interceptorChain.pluginAll(executor).方法。

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

可以看到,这个方法是依次执行interceptor的plugin方法。这里的target就是相关的业务的实例,比如这里的newExecutor的target就是一个Executor。请注意,这里做了一件很有趣的事情,把interceptor.plugin(target)返回的结果又赋给了target。为什么这样做,我们看下plugin方法做了什么。

不过这里的plugin方法就不是源码了。因为interceptor是我们自己定义的。
我们就按照官方的例子建一个plugin

@Intercepts({@Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class})})
public class ExamplePlugin implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {

        return invocation.proceed();
    }

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

    public void setProperties(Properties properties) {
    }
}

先简单看下,这个类包含了注解,和三个方法。第二个方法plugin就是pluginAll的执行方法,第三个方法就是解析配置文件时,设置属性文件的方法。

我们看下Plugin.wrap(target, this)的实现,看他是怎么对target做处理的。

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

看下getSignatureMap这个方法

private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    if (interceptsAnnotation == null) { // issue #251
      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;
  }

我们可以看到,这个就是读取我们自定义的插件的注解部分信息,然后放到map中。
对应的是以下注解的部分。
@Intercepts({@Signature(
type = Executor.class,
method = “update”,
args = {MappedStatement.class, Object.class})})
再回来,看下Plugin.wrap的这部分代码片段。

Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }

拿到注解的type(Executor.class),然后new Plugin 并代理它。返回了代理对象。
这里还有个逻辑

private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }

这里的type是target的type。
这个方法是获取要代理的对象要提供哪些接口。这个方法循环查找该TYPE实现的接口,然后再取父类,再次循环。
再回来看下下面语句。
target = interceptor.plugin(target);
plugin方法返回的其实是target的代理对象targetProxy1,再把targetProxy1赋值给target对象,如果有第二个插件,那么就会再次执行(外面有个循环)plugin方法,那么就会以targetProxy1作为代理对象,创建targetProxy2对象。多个插件就依次下去,最终形成一个插件任务责任链。

这样,比如SimpleExecutor执行了update方法,实际上是执行了最后的代理对象targetProxyN的invoke的方法。
我们来看看这个代理对象的invoke方法。(这个对象是Plugin哦)
org.apache.ibatis.plugin.Plugin#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)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

可以看到逻辑还是比较简单的,从signatureMap中取出要拦截的方法集合,然后一一对比,如果能匹配上,就执行自定义拦截器的intercept方法。就是拦截器三个方法的第一个。如果匹配不上,就正常执行原方法(不额外执行,自然没拦截了)

public Object intercept(Invocation invocation) throws Throwable {
   //下一个拦截器
   return invocation.proceed();
}

执行下一个拦截器的实现,很简单,就执行target的相应方法,这里的target如果是代理对象,那么就还会执行一次invoke,一直到原本的对象为止。
这就是整个执行的任务链过程了。

public Object proceed() throws InvocationTargetException, IllegalAccessException {
   return method.invoke(target, args);
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis是一个开源的持久层框架,它的配置文件包含了会深深影响MyBatis行为的设置和属性信息。配置文件的顶层结构包括properties(属性)、settings(设置)、typeAliases(类型别名)、typeHandlers(类型处理器)、objectFactory(对象工厂)、plugins(插件)、environments(环境配置)、mappers(映射器)等。\[3\] 在MyBatis源码中,有一个方法build,该方法用于解析动态脚本并生成SqlSource对象。在该方法中,首先会调用parseDynamicTags方法解析动态标签,得到一个包含多个SqlNode的列表contents。然后,通过将contents传入MixedSqlNode的构造函数,创建一个MixedSqlNode对象rootSqlNode。接下来,根据是否为动态脚本,分别创建DynamicSqlSource或RawSqlSource对象,并将configuration、rootSqlNode和parameterType作为参数传入构造函数。最后,返回创建的SqlSource对象。\[2\] 另外,还有一个方法configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql),该方法用于创建StatementHandler对象。具体的解读需要查看该方法的实现代码。 #### 引用[.reference_title] - *1* [mybatis源码深度解析](https://blog.csdn.net/qq_31359923/article/details/126582701)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v4^insert_chatgpt"}} ] [.reference_item] - *2* [【MybatisMybatis源码解读](https://blog.csdn.net/keepfriend/article/details/124356649)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v4^insert_chatgpt"}} ] [.reference_item] - *3* [mybatis源码解析](https://blog.csdn.net/weixin_43189971/article/details/125418419)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v4^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值