Mybatis之插件实现原理

为什么要有插件

可以在映射语句执行前后加一些自定义的操作,比如缓存、分页等 <br />

可以拦截哪些方法

默认情况下,Mybatis允许使用插件来拦截的方法有:

  • Executor:update、query、flushStatements、commit、rollback、getTransaction、close、isClosed。
    实现类:SimpleExecutor/BatchExecutor/ReuseExecutor/CachingExecutor
  • ParameterHandler:getParameterObject、setParameters。
    实现类:DefaultParameterHandler
  • ResultSetHandler:handleResultSets、handleOutputParameters。
    实现类:DefaultResultSetHandler
  • StatementHandler:prepare、parameterize、batch、update、query。
    实现类:CallableStatementHandler/PreparedStatementHandler/SimpleStatementHandler/RoutingStatementHandler

如何自定义插件

只需实现Interceptor接口,并指定要拦截的方法签名

@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){
      //传入配置项
      String size = properties.getProperty("size");
    }
}
<!-- mybatis-config.xml -->
<plugins>
    <plugin interceptor="org.mybatis.example.ExamplePlugin">
        <!-- 这里的配置项就传入setProperties方法中 -->
        <property name="size" value="100">
    </plugin>
</plugins>

拦截器实现原理

如果了解Mybatis的拦截器实现原理,可以在以后的工作中也可使用该方法实现自己的拦截器


插件包
//拦截器接口,供外部实现,实现该接口就定义了一个插件
public interface Interceptor {
  //拦截方法,可以将自定义逻辑写在该方法中
  Object intercept(Invocation invocation) throws Throwable;
  //包装成插件,一般Plugin.wrap(target,this)就行了
  Object plugin(Object target);
  //传入自定义配置参数
  void setProperties(Properties properties);
}
拦截器上定义的注解
@Intercepts:拦截器注解,包括一个或多个@Signature,拦截的目标类信息
@Signature:拦截的目标类信息,包括type、method、args,一个@Intercepts中可包含多个@Signature

public class Invocation {
  private Object target;//目标对象
  private Method method;//调用方法
  private Object[] args;//方法形参列表
  //省略get和set方法
  //执行调用,基于动态代理,在Interceptor的intercept方法中一定要调用该方法
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }
}
//动态代理实现
public class Plugin implements InvocationHandler {
  private Object target;
  private Interceptor interceptor;//拦截器
  private Map<Class<?>, Set<Method>> signatureMap;//拦截目标类的目标方法

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }
  //包装目标实例
  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    //目标类所有接口是否有signatureMap中定义的Class
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    //如果拦截器中有定义拦截目标类中的方法时,就返回代理实例 
   if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    //没有就返回目标实例
    return target;
  }

  @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)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
  //获取拦截器上的SignatureMap
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    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;
  }

  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    while (type != null) {//获取type上的所有接口
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {//这里不判断Method,只判断Class<?>
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
}

在配置文件中定义的过滤器,都保存在Configuration类的interceptorChain中,这个类保存了mybatis的所有配置,interceptorChain类中保存中所有Interceptor集合组成的拦截器链,这个链是如何添加进去的呢?请看源码。

  //XMLConfigBuilder类中解析mybatis-config.xml 核心方法parseConfiguration(XNode root)
  pluginElement(root.evalNode("plugins"));//插件配置项

 private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
    //遍历 plugins的子节点plugin
    for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");//获取interceptor属性值
        Properties properties = child.getChildrenAsProperties();//获取plugin属性值
        //创建拦截器实例,这里interceptor值也可以是typeAlias注册的简名
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        //设置属性项
        interceptorInstance.setProperties(properties);
        //添加到interceptorChain中
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
  //Configuration类,添加拦截器
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

拦截的哪些接口

  //SQL语句处理器
  public interface StatementHandler {
    //预备工作
    Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
    //参数处理
    void parameterize(Statement statement) throws SQLException;
    //批量处理
    void batch(Statement statement)  throws SQLException;
    //更新处理
    int update(Statement statement) throws SQLException;
    //查询处理
    <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
  }
  //返回集处理器
  public interface ResultSetHandler {
    //处理返回结果
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;
    //处理输出参数
    void handleOutputParameters(CallableStatement cs) throws SQLException;
  }
  //参数处理器
  public interface ParameterHandler {
     
    Object getParameterObject();

    void setParameters(PreparedStatement ps) throws SQLException;
  }

如何拦截这些接口

//创建相应Handler时会将所有拦截器通过动态代理方式返回代理Handler
public class Configuration {

  //创建ParameterHandler(参数处理器)
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, 
        Object parameterObject, BoundSql boundSql) {
  // 根据指定Lang(默认RawLanguageDriver),创建ParameterHandler,将实际参数传递给JDBC语句
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(
        mappedStatement, parameterObject, boundSql);
    //返回代理实例
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  //创建ResultSetHandler(结果处理器)
  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, 
     RowBounds rowBounds,ParameterHandler parameterHandler,
     ResultHandler resultHandler,BoundSql boundSql) {
    //默认使用DefaultResultSetHandler创建ResultSetHandler实例
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement,
       parameterHandler, resultHandler, boundSql, rowBounds);
    //返回代理实例
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  //创建StatementHandler(SQL语句处理器)
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, 
    Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //默认使用RoutingStatementHandler(路由作用)
    //创建指定StatementHandler实例(默认SimpleStatementHandler)
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, 
          parameterObject, rowBounds, resultHandler, boundSql);
    //返回代理实例
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  //创建Executor(执行器)
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //获取executorType,默认是SIMPLE
    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;
  }
}
//执行器
public interface Executor {
    //更新
    int update(MappedStatement ms, Object parameter) throws SQLException;
    //查询(先查缓存)
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, 
          ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
    //查询
    <E> List<E> query(MappedStatement ms, Object parameter, 
          RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
    //查询游标
    <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) 
          throws SQLException;
    //刷新Statement
    List<BatchResult> flushStatements() throws SQLException;
    //提交事务
    void commit(boolean required) throws SQLException;
    //回滚事务
    void rollback(boolean required) throws SQLException;
    //创建缓存key
    CacheKey createCacheKey(MappedStatement ms, Object parameterObject,RowBounds rowBounds, 
          BoundSql boundSql);
    //是否存在key
    boolean isCached(MappedStatement ms, CacheKey key);
    //清除本地缓存
    void clearLocalCache();
    //延迟加载
    void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, 
          Class<?> targetType);
    //获取事务
    Transaction getTransaction();
    //关闭连接
    void close(boolean forceRollback);
    //是否关闭
    boolean isClosed();
    //设置Executor
    void setExecutorWrapper(Executor executor);
}

总结

当然具体实现肯定不止这么多代码,如果需要了解,需要自行看源码,下面坐下总结。
1.拦截器实现
Interceptor接口供插件实现,@Intercepts注解在插件实现上,表示这是一个插件类并配置将要拦截哪些方法,@Signature定义将要拦截的方法信息,如名称/类型/形参列表,Plugin类实现了InvocationHandler接口,是动态代理的具体实现,Invocation类包装了拦截的目标实例,InterceptorChain保存所有拦截器。
2.如何实现拦截
创建目标实例,比如A a = new A();
Interceptor interceptor = new LogInterceptor();//如果拦截a中的save方法
将A b = (A)interceptor.plugin(a);这里b就是a的代理实例,在调用a中的save方法时,实际将调用interceptor的intercept方法,在该方法中一定要调用Invocation的proceed方法并将返回值返回。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBati...
    七寸知架构阅读 3,252评论 3 54
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,423评论 0 4
  • 记录是一种精神,是加深理解最好的方式之一。 最近看了下Mybatis的源码,分析了Mybatis插件的实现方式,在...
    曹金桂阅读 17,829评论 15 52
  • 我曾经所天真的那些 都被时间验证了遍 虚假的不切实际的 最终沦为齿边的黑点 不能再在大雨中肆无忌惮的奔跑 不能再在...
    黄小骨阅读 100评论 0 1
  • 三天一小吵,五天一大吵,有时候感觉吵架已经是家常便饭,有时候挺不能理解女生所谓的安全感到底是什么。 没有秒回没有买...
    wensmily阅读 202评论 0 0