zoukankan      html  css  js  c++  java
  • mybatis源码分析:插件是什么

    在上篇文章中,《mybatis源码配置文件解析之四:解析plugins标签  》分析了mybatis中的plugin标签的解析过程,plugin指的是插件,或者说拦截器更为形象,因为它的作用就是拦截特定的方法,根据拦截到的方法进行特定的处理。

    一、概述

    在mybatis中插件我认为叫拦截器更贴切,下面的统一称为拦截器,在上篇文章中说到了拦截器的使用方式及作用,现在来分析下其实现原理,是如何进行方法拦截的。在上篇最后提到在mybatis核心配置文件中配置的拦截器经过解析后,最终都保存在了InterceptorChai类中的interceptors属性中,如下图,

    最终所有的拦截器均保存在了ArrayList中。回过头来看InterceptorChain类,在该类中有pluginAll方法,从该方法的定义分析知道,此方法在遍历所有的拦截器,并调用其plugin方法,既然是遍历拦截器,那就说明在使用拦截器,可以大胆判断pluginAll方法和拦截器的使用有关。搜索mybatis的源码,plugin方法被调用的地方如下,

    从上面可以看出在整个源码中仅有四处调用,都是在Configuration类中,这四处调用分别和ParameterHandler、ReslutSetHandler、StatementHandler、Executor有关,从上篇文件可以知道,拦截器拦截的就是这四个接口中的方法,现在可以肯定这里便是拦截器起作用的地方,从这四处中选择statementHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler);来分析其实现原理。

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

    二、详述

    根据上面的分析,在newStatementHandler方法中调用了pluginAll方法,我们便从newStatementHandler方法开始分析,改方法中首先生成了一个RoutingStatementHandler的实例,此行代码就是单纯的new,暂时不必关注,紧接着下面一句interceptorChain.pluginAll,调用了pluginAll方法,在开篇就提到该方法在InterceptorChain类中。

    1、pluginAll方法

    下面是pluginAll方法的定义,

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

    此方法在遍历interceptors即遍历所有的拦截器,调用每个拦截器的plugin方法,plugin方法会返回一个对象,我们看默认的拦截器接口的plugin方法,

    public interface Interceptor {
    
      Object intercept(Invocation invocation) throws Throwable;
    
      Object plugin(Object target);
    
      void setProperties(Properties properties);
    
    }

    也就是所有实现该接口的方法需要实现上面的三个方法,如果对plugin方法,不进行重写,那么默认会返回null。回到下面这行代码看下能否返回null,

    target = interceptor.plugin(target);

    如果plugin方法返回null,那么最后pluginAll方法会返回null,该方法返回null,那么下面这行代码便为null,

     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

    最后newStatementHandler便会为null,我们知道newStatementHandler方法不能为null,为null了mybatis就无法往下处理。所以对于实现了Interceptor接口的类必须重新plugin方法,怎么重新那,该方法如何返回一个对象那,返回一个什么样的对象那,我们知道在newStatementHandler方法中已经new了一个StatementHandler对象,然后调用了pluginAll方法,为什么不直接返回statementHandler对象,而是把statementHandler作为参数调用pluginAll之后返回statementHandler那,因为要进行代理。这就说明plugin方法必须要返回一个JDK代理对象。

    2、plugin方法

    由于plugin方法定义在Interceptor接口中,并且上面已经看到了其在接口中的定义,下面看我自定义的接口中plugin的实现,

    package cn.com.mybatis.plugins;
    
    import java.util.Properties;
    
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.plugin.Interceptor;
    import org.apache.ibatis.plugin.Intercepts;
    import org.apache.ibatis.plugin.Invocation;
    import org.apache.ibatis.plugin.Plugin;
    import org.apache.ibatis.plugin.Signature;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    
    @Intercepts({@Signature(type=Executor.class,method="query",args= {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
    public class MyInterceptor implements Interceptor{
    
        private Properties properties;
        
        /**
         * incation 封装了拦截的类,方法,方法参数
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // TODO Auto-generated method stub
            //执行被拦截的类中的方法
            
            
            Object o=invocation.proceed();
            return o;
        }
    
        /**
         * 返回一个JDK代理对象
         */
        @Override
        public Object plugin(Object target) {
            // TODO Auto-generated method stub
            return Plugin.wrap(target, this);
        }
    
        /**
         * 允许在配置文件中配置一些属性即 
         * <plugin interceptor=""> 
         * <property name ="" value =""/>
         * </plugin>
         */
        @Override
        public void setProperties(Properties properties) {
            // TODO Auto-generated method stub
            this.properties = properties;
        }
    
    }

    从上面的代码中,看到调用了Plugin.wrap方法,wrap的意思是包装,这里返回的是一个JDK代理对象。且改方法的入参有一个target,这里指的就是statementHandler对象。

    2.1、Plugin.wrap方法

    我们现在看Plugin类的wrap方法,如下

    public static Object wrap(Object target, Interceptor interceptor) {
          //1、获得自定义拦截器上的注解信息key为要拦截的接口的Class对象,value为该接口中的Method
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        //2、获得目标类中所有接口中被拦截的接口信息
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        //3、如果存在接口,则使用JDK动态代理,否则返回被代理对象
        if (interfaces.length > 0) {
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        return target;
      }

    上面已经写上了注释,第一步会获得自定义拦截器上的注解,即下面的内容,

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

    获得@Intercepts注解的内容,该注解中的值是@Signature注解的一个数组,看下@Intercepts注解的定义,

    /**
     *    Copyright 2009-2016 the original author or authors.
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *       http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    package org.apache.ibatis.plugin;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author Clinton Begin
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Intercepts {
      Signature[] value();

    很明显必须是一个Signature类型的数组,和配置的注解的内容是一样的,在看@Signature注解的定义,

    /**
     *    Copyright 2009-2016 the original author or authors.
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *       http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    package org.apache.ibatis.plugin;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author Clinton Begin
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Signature {
      Class<?> type();
    
      String method();
    
      Class<?>[] args();
    }

    根据注解中的定义,再对比配置,发现一样,不一样就惨了。

    回到wrap方法中,第二步就是要找到被代理对象statementHandler(这里是RoutingStatementHandler)所实现的接口和拦截器中配置的接口,返回被代理对象所实现的接口且配置在拦截器中的接口。

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

    第三步就是大名鼎鼎的JDK动态代理了,

    //3、如果存在接口,则使用JDK动态代理,否则返回被代理对象
        if (interfaces.length > 0) {
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        return target;

    如果第二步存在接口则生成一个JDK动态代理,也就是RoutingStatementHandler的代理对象并返回;如果没有则直接返回原对象,也就是RoutingStatementHandler。看第三个参数new Plugin(target,interceptor,signatureMap),下面看Plugin类,

    构造方法给target、interceptor、signatureMap进行了赋值。看该类实现了InvocationHandler接口,这类和代理有关系,肯定实现了invoke方法。

    2.2、Plugin的invoke方法

    从上面的分析知道使用Plugin生成了代理类,那么在执行方法时肯定会调用该类的invoke方法,

    @Override
      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)) {
    //1、拦截器的interceptor方法
    return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args);//2、直接调用方法 } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }

    从上面可以看到invoke方法主要完成了两项工作,一个就是调用拦截器的intercept方法,第二个就方法的正常调用。主要看intercept函数,由于intercept方法是Interceptor接口中的,下面看我的拦截器中的intercept方法,

    /**
         * invocation 封装了拦截的类,方法,方法参数
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // TODO Auto-generated method stub
            //写自己的逻辑,改变参数、监控SQL执行等
            
            //最后一定要执行下面的代码,对原方法的调用
            Object o=invocation.proceed();
            return o;
        }

    看intercept方法的参数Invocation,在调用intercept方法时传入了一个Invocation的实例,

    return interceptor.intercept(new Invocation(target, method, args));

    target表示的是被代理的对象,这里就是RoutingStatementHandler对象,method就是要执行的方法,args是方法的参数。在自定义的拦截器中重写intercept方法时除了加入自己的逻辑处理外,要调用invocation.proceed()方法,此方法是做什么的那,

    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
      }

    可以看到此方法是对被代理对象的方法的调用,也就是在执行完自定义逻辑外肯定要执行目标方法,也就是被代理对象的方法。

    三、总结

    上面就分析完了mybatis拦截器的实现原理,综合拦截器标签<plugin>的解析,以及Interceptor接口中的三个方法,

    /**
     *    Copyright 2009-2015 the original author or authors.
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *       http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    package org.apache.ibatis.plugin;
    
    import java.util.Properties;
    
    /**
     * @author Clinton Begin
     */
    public interface Interceptor {
    
      Object intercept(Invocation invocation) throws Throwable;
    
      Object plugin(Object target);
    
      void setProperties(Properties properties);
    
    }
    • 在解析<plugin>标签时如果有<property>标签,则解析为Proerties对象,并调用setProperties方法设置到拦截器中;
    • mybatis拦截器可以拦截的Executor、StatementHandler、ParameterHandler、ResultSetHandler中的方法,在生成这些接口的实例时如果有拦截,则调用plugin方法,生成一个JDK动态代理对象;
    • 在执行Executor、StatementHandler、ParameterHandler、ResultSetHandler中的方法时,如果自定义拦截器拦截了相应的方法,则调用intercept方法,执行自定义拦截器中的逻辑;

    有不正之处,欢迎指正,感谢!

  • 相关阅读:
    MySQL常用命令
    Jstack、Jmap命令简单使用
    MySQL慢查询日志ES索引模板
    MySQL二进制安装部署
    varnish启动报错
    基础训练 Sine之舞
    基础训练 FJ的字符串
    基础训练 芯片测试
    基础训练 龟兔赛跑预测
    基础训练 回形取数
  • 原文地址:https://www.cnblogs.com/teach/p/13164177.html
Copyright © 2011-2022 走看看