学习笔记----MyBatis 插件源码分析

632 阅读9分钟

1、设计的类图

MyBatis 允许用户使用自定义拦截器(即:插件) 对 SQL 语句执行过程中的某一点进行拦截 。 默认情况 下, MyBatis允许拦截器拦截 Executor的方法 、 ParameterHandler 的方法 、 ResultSetHandler 的 方法以及 StatementHandler 的方法,这个四个对象可称为Mybatis的四大对象 。 具体可拦截的方法如下 :

  • Executor 中 的 update()方法、 query()方法 、 flushStatements()方法 、 commit()方法 、 rollback()方法、getTransaction()方法 、 close()方法、 isClosed()方法 。

  • ParameterHandler 中的 getParameterObject()方法 、setParameters()方法 。

  • ResultS etHandler 中的 handleResultSets()方法 、 handleOu飞putParameters()方法 。

  • StatementHandler 中的 prepare()方法、 parameterize()方法 、 batch()方法、 update()方法 、 query()方法 。

2、Interceptor接口

  1. MyBatis中使用的拦截器已经我们自定义的拦截器都需要实现 Interceptor 接口 。 Interceptor 接口是 MyBatis 插件模块的核心。

源码:org.apache.ibatis.plugin.Interceptor代码如下:

/**
  子类实现该接口,重写以下方法
**/
public interface Interceptor {
    //执行拦截逻辑的方法
    Object intercept(Invocation var1) throws Throwable;
    //决定是否触发intercept()方法。该方法会根据被拦截的对象来绝对是否要创建四大对象的代理对象
    Object plugin(Object var1);
    //根据mybatis-config.xml配置文件中配置的property属性,初始Interceptor对象
    void setProperties(Properties var1);
}
  1. 自定义拦截器:

    用户自定义的拦截器除了实现Interceptor 接口,还需要使用==@Intercepts== 和==@Signature== 两个 注解进行标识。@Intercepts 注解中指定了一个@Signature 注解列表,每个@Signature 注解中都 标识了该插件需要拦截的方法信息,其中@Signature 注解的type属性指定需要拦截的类型, method 属性指定需要拦截的方法 ,args 属性指定了被拦截方法的参数列表。通过这三个属性值, @Signature 注解就可 以表示一个方法签名, 唯一确定一个方法。

/**
 * 完成插件签名:
 *		告诉MyBatis当前插件用来拦截哪个对象的哪个方法
 */
@Intercepts(
		{
			@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
		})
public class MyFirstPlugin implements Interceptor{

	/**
	 * intercept:拦截:
	 * 		拦截目标对象的目标方法的执行;
	 */
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
		//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询11号员工
		Object target = invocation.getTarget();
		System.out.println("当前拦截到的对象:"+target);
		//拿到:StatementHandler==>ParameterHandler===>parameterObject
		//拿到target的元数据
		MetaObject metaObject = SystemMetaObject.forObject(target);
		Object value = metaObject.getValue("parameterHandler.parameterObject");
		System.out.println("sql语句用的参数是:"+value);
		//修改完sql语句要用的参数
		metaObject.setValue("parameterHandler.parameterObject", 11);
		//执行目标方法
		Object proceed = invocation.proceed();
		//返回执行后的返回值
		return proceed;
	}

	/**
	 * plugin:
	 * 		包装目标对象:为目标对象(即:四大对象)创建一个代理对象。注:返回的不一定就是代理对象,在Plugin.wrap()方
	 *法中会进行判断是否进行包装。
	 */
	@Override
	public Object plugin(Object target) {
		//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
		System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);
		Object wrap = Plugin.wrap(target, this);
		//返回为当前target创建的动态代理
		return wrap;
	}

	/**
	 * setProperties:
	 * 		将插件注册时的property属性设置进来
	 */
	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
		System.out.println("插件配置的信息:"+properties);
	}

}
  1. 自定义拦截器的配置
   <!--plugins:注册插件  -->
   	<plugins>
   		<plugin interceptor="com.shihy.mybatis.dao.MyFirstPlugin">
   			<property name="username" value="root"/>
   			<property name="password" value="123456"/>
   		</plugin>
   	</plgins>
  • 拦截器的加载和初始化流程:

在 MyBatis 初 始 化时, 会通过 XMLConfigBuilder.puginElement()方法解析 mybatis-config .xml 配置文件中 定义的<plugin>节点 , 得到相应的 Interceptor 对象以及配置的相应属性,之后会调用 Interceptor.setProperties(properties) 方法完成对 Interceptor 对象的初始化配 置 , 最后将 Interceptor 对象添加到Configuration. interceptorChain字段(该字段是个InterceptorChain对象的引用)中保存 。

  • 前面说了拦截器可以拦截的四大对象的方法,那么拦截器如何以及在哪里对四大对象进行拦截呢?:

在 MyBatis 中使用的这四类的对象 , 都是通过 Configuration .new*()系列方法创建的 ,在这些new*()系列方法中都会调用InterceptorChain .pluginAll(),在该方法以及后续方法中完成拦截,可以理解为在创建四大对象的时候拦截。 如果配置了用户自定义拦截器,则会在该系列方法中 , 通过 InterceptorChain .pluginAll()方法为目标对象创建代理对象 ,所以通过 Configuration . new *()系列方法得到的对象实际是一个代理对象。

如:Executor 对象在我们获取SqlSession对象的时候会被创建,然后被拦截。

//<1>获取连接
SqlSession openSession = sqlSessionFactory.openSession();

//<2>org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
//<3>org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  .......省略部分代码
      //这里就会调用了newExecutor方法
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
  .......省略部分代码
  }
//<4>org.apache.ibatis.session.Configuration
 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    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);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //调用pluginAll方法
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

如:StatementHandler 对象,在我们执行和数据库交互的方法的时候被创建,然后被拦截。

//<1>利用动态mapper查询
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById(1);

//<2>多步骤以后会调用到一下方法:
 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
     .......省略部分代码
      //这里就会调用了newStatementHandler方法
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
   .......省略部分代码
  }
//<3>org.apache.ibatis.session.Configuration
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);
    //调用pluginAll方法
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

3、InterceptorChain

记录了 mybatis-config.xml 文件中配置的拦截器

源码:org.apache.ibatis.plugin.InterceptorChain

  • 大名鼎鼎的pluginAll方法

    //<1>org.apache.ibatis.plugin.InterceptorChain
    //InterceptorChain 中使用 interceptors 字段( ArrayList<Interceptor>类型)记录了
    //mybatis-config.xml 文件中配置的拦截器
    private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
    
    public Object pluginAll(Object target) {
        //遍历interceptors 集合
        for (Interceptor interceptor : interceptors) {
           //调用 Interceptor.plugin()方法创建代理对象.该方法被各个子类自行实现
          target = interceptor.plugin(target);
        }
        return target;
      }
    
  • 一般plugin()方法都是固定的写法,利用Mybatis帮我们提供的Plugin 工具类实现。它实 现了 InvocationHandler 接口,并提供了一个 wrap()静态方法用于创建代理对象。

//<1>org.apache.ibatis.plugin.InterceptorChain
@Override
  public Object plugin(Object target) {
  	//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
  	Object wrap = Plugin.wrap(target, this);
  	//返回为当前target创建的动态代理
  	return wrap;
}
  
  • addInterceptor(Interceptor interceptor)方法用于添加拦截器
//<1>org.apache.ibatis.plugin.InterceptorChain
public void addInterceptor(Interceptor interceptor) {
        this.interceptors.add(interceptor);
    }

该方法调用链如下:

XMLConfigBuilder #pluginElement(XNode parent)--->Configuration #addInterceptor(Interceptor interceptor)--->

InterceptorChain #addInterceptor(Interceptor interceptor) 。

  • XMLConfigBuilderpluginElement(XNode parent)方法

    //<1>org.apache.ibatis.builder.xml.XMLConfigBuilder
    private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
          // 遍历 <plugins /> 标签
          for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            //创建 Interceptor 对象,并设置属性
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            interceptorInstance.setProperties(properties);
             //添加到 configuration 中
            configuration.addInterceptor(interceptorInstance);
          }
        }
      }
    
  • ConfigurationaddInterceptor(Interceptor interceptor)方法

    //<1>org.apache.ibatis.session.Configuration
    public void addInterceptor(Interceptor interceptor) {
        interceptorChain.addInterceptor(interceptor);
      }
    
  • 拦截器记录过程图解:

    SqlSessionFactoryBuilder #build(InputStream inputStream)---->

    SqlSessionFactoryBuilder #SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)---->

    XMLConfigBuilder #parse()---->

    XMLConfigBuilder#parseConfiguration(XNode root)---->

    XMLConfigBuilder#pluginElement(XNode parent)---->

    Configuration #addInterceptor(Interceptor interceptor)---->

    InterceptorChain #addInterceptor(Interceptor interceptor)

4、Plugin对象

源码:org.apache.ibatis.plugin.Plugin

  • Plugin.wrap() 方法

    //<1>org.apache.ibatis.plugin.Plugin
    //注意:该方法返回的并不是一定就是target的代理对象,也可能返回原有的对象target本身
    public static Object wrap(Object target, Interceptor interceptor) {
        //获取用户自定义 Interceptor中@Signature注解的信息,getSignatureMap()方法负责处理
        //@Signature注解。获取该拦截器要拦截的类和对应的方法。key-->类全限定名,value-->一个包含Method对象的HashSet
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        //获取目标类型。这里的target变量就是四大对象的实例
        Class<?> type = target.getClass();
        //getAllInterfaces方法中堆type实现的接口和拦截器要拦截的类进行匹配,返回一个数组
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        //若数组的长度大于0,则表示target对象的类型,正是该拦截器要拦截的类型。
        if (interfaces.length > 0) {
            //<2>返回一个代理了target对象的代理对象
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              //创建一个Plugin对象
              new Plugin(target, interceptor, signatureMap));
        }
        //<3>返回原有的target对象本身
        return target;
      }
    

    <2>,若有接口,则创建目标对象的 JDK Proxy 对象。

    <3>,若无接口,则返回原始的目标对象。

    • signatureMap的具体值:

    • 方法的返回值:

  • Plugin.getAllInterfaces(Class type, Map, Set> signatureMap)方法
//<1>org.apache.ibatis.plugin.Plugin
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    // 接口的集合
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    //  循环查找type 类以及其父类实现的接口
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
       //若在 signatureMap 中,表明type对象实现的接口正是拦截器要拦截的。则添加到 interfaces 中
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      //获取父类
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }

由此即可见,@Signature 注解的 type 属性,必须是接口

下面来看看Plugin对象的其他方法和属性。

  • Plugin 中各个字段 的含义如下:
//<1>org.apache.ibatis.plugin.Plugin
// 目标对象  
  private Object target;
//拦截器对象
  private Interceptor interceptor;
//获取该拦截器要拦截的类和对应的方法。key-->类全限定名,value-->一个包含Method对象的HashSet
  private Map<Class<?>, Set<Method>> signatureMap;
  • Plugin的构造方法

    private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
      }
    
  • Plugin 中invoke方法如下:

//<1>org.apache.ibatis.plugin.Plugin
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
       //获取该拦截器要拦截的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
       //如果当前调用的方法需要被拦截,则调用Interceptor.intercept()方法进行拦截处理
      if (methods != null && methods.contains(method)) {
        //入参是Invocation对象
        return interceptor.intercept(new Invocation(target, method, args));
      }
       //如果当前调用的方法不能被拦截,则调用target对象的原本方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

下面来看看Invocation对象。

5、Invocation对象

源码:org.apache.ibatis.plugin.Invocation

Interceptor.intercept()方法 的参数是 Invocation 对象 , 其中封装了目标对象、目标方法以及调 用目标方法的参数,并提供了 proceed()方法调用目标方法,如下所示。所以在 Interceptor. intercept() 方法中执行完拦截处理之后 ,如果需要调用目标方法,则通过 Invocation. proceed()方法实现。 所以在intercept()中只能通过proceed()方法调用目标方法。

 //<1>org.apache.ibatis.plugin.Invocation
  // 目标对象  
  private Object target;
  //要调用的方法
  private Method method;
  //要调用的方法的参数
  private Object[] args;

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

6、@Intercepts

源码:org.apache.ibatis.plugin.@Intercepts

//<1>`org.apache.ibatis.plugin.@Intercepts`
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
    /**
     * @return 拦截的方法签名的数组
     */
  Signature[] value();
}

7、 @Signature

源码:org.apache.ibatis.plugin.@Signature

//<1>org.apache.ibatis.plugin.@Signature
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {

    /**
     * @return 类
     */
    Class<?> type();

    /**
     * @return 方法名
     */
    String method();

    /**
     * @return 参数类型
     */
    Class<?>[] args();

}

8、流程总结

  • 拦截器原理的流程图
    1. 1.x表示初始化拦截器对象,放入拦截器链对象InterceptorChain中
    2. 2.x表示拦截器在四大对象实例被创建时候进行拦截,并决定是否生成代理对象。以及后续调用代理对象的方法,决定该方法是否是该拦截器要拦截的方法(该代理对象有该拦截器的引用,方便后续调用intercept()方法)。

  • 对于上图的步骤2.3的图解说明:
    
  1. 第一个代理对象

  1. 第二个代理对象

  1. 多个拦截器拦截同一个四大对象实例图解