MyBatis plugin的使用与源码解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012734441/article/details/85833430

MyBatis plugin的使用与源码解析

这一节来讲下Mybatis中的plugin的使用,plugin作为对执行期间对Executor、StatementHandler的一种增强等等,我见过用的最多的应该就是Mybatis的分页插件PageHelper,PageHelper因为简单易用被广泛用于各种大小工程中,虽说PageHelper使用起来确实挺舒服,但是在遇到一些查询性能上的问题时,PageHelper带来的弊端倒是挺大的,这个以后有空单独抽一个专题出来说下PageHelper,这一节就不再多说了,就直说Plugin用法与原理。

1. Plugin用法


Plugin用法说起来挺简单的,和写拦截器一样,在这也想好具体写什么Plugin,就写一个拦截query方法的Plugin吧,打印方法具体耗时,命名为ExecuteLogPlugin,如下:

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

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object target = invocation.proceed();
        long end = System.currentTimeMillis();
        System.out.println("intercept " + invocation.getMethod().getName() + " cost : " + (end - start));
        return target;
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

这段里面真正起作用的就是invocation.proceed()这段,就是执行代理方法,然后在前后埋点,最后输出对应method耗时,在这图简便,就没有单独引入log的包,就用System输出了,这个Plugin中最重要的就是@Intercepts注解,表明拦截的是什么类型,拦截方法。这个注解的解析在后面会提到。

除了编写Plugin之外,还需要做的事情就是在xml中需要配置Plugin,具体如下:

<plugins>
        <plugin interceptor="cn.com.plugin.ExecuteLogPlugin"></plugin>
</plugins>

这一切做完后,在运行main程序时就能得到期望的效果,在这几乎每个query方法都进行了拦截,如果只想对某一个或者几个方法进行拦截的话,invocation中args字段中有对应的class类与方法名,可以在这做些文章。

用法就到此为止了,运行中的截图也没啥好贴出来的,运行时可以自己看下,下面开始对原理性的分析。

2. 原理分析


分析Plugin的加载仍然需要回到configuration的初始化,继续回到Main程序的第一行开始分析,我们的main程序中的SessionFactory的初始化如下:

String resource = "conf.xml";
        //使用类加载器加载mybatis的配置文件(它也加载关联的映射文件)
        InputStream is = Main.class.getClassLoader().getResourceAsStream(resource);
        //构建sqlSession的工厂
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);

还是进入到build方法中,查看configuration的初始化过程.

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

如同第一篇文章!Mybatis源码解析之配置加载(一)一样,我们回到解析configuration的源码处,此次其他均不讲,只说pluginElement(root.evalNode(“plugins”))这一行,这一行就是plugin的具体加载过程,进入到pluginElement()中。

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

在这里解析出Plugin,并进行实例化操作,(Interceptor) resolveClass(interceptor).newInstance(),这一步追踪到最后就是调用了Class.forName()的过程,没有太多深究的东西。

在实例化完了后,将当前的plugin实例保存进InterceptorCahin中,等待Executor执行时调用,接下来我们再看session获取到mapper,mapper执行的过程。

扫描二维码关注公众号,回复: 4783958 查看本文章
UserMapper userMapper = session.getMapper(UserMapper.class);
//执行查询返回一个唯一user对象的sql
User user = userMapper.getUser(1);

从前几篇文章我们知这里的Mapper对象其实就是一个代理对象,真正在起作用的为MapperProxy,然后在执行调用方法时,是调用MapperProxy的invoke方法,然后对于select方法,最终都是调用selectList,selectList经过多层调用后最终还是调用doQuery方法,我们回到doQuery处。

@Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

此处我们看获取StatementHandler的方法,进入到newStatementHandler。

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

此处调用了interceptorChain.pluginAll(statementHandler)得到statementHandler对象,而pluginAll中做的操作为:

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

就是调用了每个plugin的plugin方法,返回一个包装对象,然后在statementHandler调用的时候发挥代理的作用,这最终解释了Plugin中intercept方法发挥作用的时机,但是仍然没有解释到@Intercepts注解的作用,以及何时进行拦截,这里就要说下Plugin中plugin方法的作用了。plugin方法中做的事情为:

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

对target与当前对象进行了一层包装,进入到warp方法中。

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

进行注解解析的一行代码为:

Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);

跳转到getSignatureMap方法中。

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

这个方法做的事情就是解析**@Signature**注解,将对应的type、method保存进Map中返回。

然后在wrap方法中对当前class进行判断,如果是当前class是@Signature中指定的type的子类,则进行代理操作。

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

对当前class的判断方法为getAllInterfaces。

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

这里判断了当前signatureMap中是否包含当前class,如果是则返回包含当前class的数组,如果不是则返回空数组,而在上一代码块中得知,只有当当前数组长度大于0时,才会返回代理对象,我们在@Signature注解中标注拦截的type为Executor,那么所有的方法基本都会被返回代理对象,但是我们选择拦截方法是query,不能拦截update、delete等操作,所以这里的玄机就在于实例化的对象传入的参数包括signatureMap。

new Plugin(target, interceptor, signatureMap))

代理对象最终执行的方法为invoke方法,我们看当前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取出的methods中包含当前方法名才会调用代理对象的intercept方法,否则直接进行method.invoke操作,不会对方法进行拦截。

Plugin的用法以及原理分析就到此为止了。

猜你喜欢

转载自blog.csdn.net/u012734441/article/details/85833430