Mybatis 拦截器原理

mybatis拦截器(插件)原理

所谓的插件其实就是方法拦截器,mybatis四大组件(Executor,StatementHandler,ParameterHandler,ResultSetHandler)在运行过程中,对四大组件方法拦截并增强,底层用的jdk的动态代理实现,常见的插件有分页插件,mybatis-plus中的MybatisPlusInterceptor,自定义插件,如在插入数据库前设置一些默认的字段(这个在mybatis-plus中不需要拦截器实现)等。

普通方法的拦截

先用mybatis中的Interceptor对普通方法拦截,加强理解

接口:

package mybatis;

/**
 * 接口,jdk动态代理必须有接口
 */
public interface TestInterface {
    String test();
    String test(String arg1);
}

实现类:

package mybatis;

/**
 * 实现类
 * 我们这里拟定目标,只增强待参数的test 方法
 */
public class TestInterfaceImpl implements TestInterface{

    @Override
    public String test() {
        return "无参";
    }

    @Override
    public String test(String arg1) {
        return arg1;
    }
}

方法拦截器

package mybatis;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

import java.util.Properties;

/**
 * 拦截器,作用:
 * 指定拦截哪个类的哪个方法(Intercepts注解)
 * 方法的增强逻辑(intercept方法)
 * 对目标对象包装,创建动态代理代替目标对象(plugin方法)
 */
// 指定拦截哪个接口的哪个方法
@Intercepts(
        {
                @Signature(type = TestInterface.class, method = "test", args = {String.class})
        }
)
public class MyInterceptor implements Interceptor {

        // 增强逻辑写在这里,invocation 里可以获取目标对象,参数信息
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
                Object[] args = invocation.getArgs();
                // 拦截的是TestInterface接口实现类的方法,所以可以直接强转
                TestInterface target = (TestInterface) invocation.getTarget();
                System.out.println("进入方法拦截。。。。。");
                return target.test(args[0].toString());
        }
        // 创建代理对象提供给客户端,底层用的jdk的动态代理
        @Override
        public Object plugin(Object target) {
                return Interceptor.super.plugin(target);
        }
        // 目前没用上
        @Override
        public void setProperties(Properties properties) {
                Interceptor.super.setProperties(properties);
        }
}

单元测试类:

    @Test
    public void testPlug() {
        // 需要增强的对象
        TestInterface testInterface = new TestInterfaceImpl();
        // 创建两个拦截器对象,一个目标可以被多个拦截器包装,一层一层的执行
        Interceptor myInterceptor = new MyInterceptor();
        Interceptor myInterceptor2 = new MyInterceptor();
        //执行一次wrap方法就创建一个代理对象,wrap执行后相当于:Proxy1(target)
        testInterface = (TestInterface) Plugin.wrap(testInterface, myInterceptor);
        // 第二次wrap方法执行后相当于:Proxy(Proxy1(target))
        testInterface = (TestInterface) Plugin.wrap(testInterface, myInterceptor2);
        // 执行被拦截的方法
        System.out.println(testInterface.test("hello"));
    }

结果:

进入方法拦截。。。。。
进入方法拦截。。。。。
hello

Process finished with exit code 0

可以看到两个增强器的方法都执行了

Mybatis 插件原理分析

mybatis 框架只对四大组件设置了插件机制
1、组件是怎么添加到框架中的,拦截器对象保存在哪?
项目启动时解析mybatis 配置文件,解析xml中的plugins 标签或者配置类中通过java代码直接添加。
保存在一个集合中,InterceptorChain 类中的List interceptors 成员变量中

2、何时创建代理对目标进行包装?
Configuration 创建四大组件时,如果有插件,就会创建代理对象,可以是多个,多个代理对象嵌套形成责任链,源码如下:

 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 用所有的拦截器包装
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        // 用所有的拦截器包装
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

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

  public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

  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);
    }
        // 用所有的拦截器包装
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

包装源码:
interceptorChain.pluginAll 最终会调用到Plugin.wrap(target, this) 方法,过程比较简单, wrap() 方法源码

package org.apache.ibatis.plugin;
/**
 * @author Clinton Begin
 */
public class Plugin implements InvocationHandler {

  private final Object target;
  private final Interceptor interceptor;
  private final Map, Set> signatureMap;
    // 创建动态代理,将interceptor(拦截器中对目标增强逻辑) 包装成Plugin对象 (动态代理的InvocationHandler)
  public static Object wrap(Object target, Interceptor interceptor) {
    Map, Set> 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;
  }
// jdk 动态代理,代理对象执行目标方法前先执行invoke方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
          //  @Signature 注解中标注了的方法才回去拦截
        return interceptor.intercept(new Invocation(target, method, args));
      }
      // 否则直接执行目标方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
}

3、只拦截目标类的目标方法(其他普通方法不拦截)的逻辑在哪实现,如何调用到Interceptor的interceptor方法?
上面Plugin类中的invoke方法执行时,会对方法进行判断是否需要增强,然后执行interceptor.intercept

总结

mybatis 的插件原理其实还是比较简单的,对四大组件指定的方法实现方法增强,多个拦截器就会创建多个代理对象,一层层包装,形成拦截器链,逐个执行。
另:
四大组件一般就Executor(最常见) 和StatementHandler用的多一些,mybatis-plus中
MybatisPlusInterceptor对Executor的query,update,StatementHandler的prepare 方法都进行了拦截,在其中设置了内部拦截器(InnerInterceptor),在不同执行时机都预留了接口,实现拦截器时直接都是实现内部拦截器,这样就不需要创建多个代理对象,性能应该会好一点。

你可能感兴趣的:(mybatis,java)