• Mybatis Interceptor 拦截器原理 源码分析


    Mybatis Interceptor 拦截器原理 源码分析

    Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。

    代理链的生成

    Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。

    通过查看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, ExecutorType executorType, boolean autoCommit) {
        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, autoCommit);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }

    接下来让我们通过分析源代码的方式来解读Mybatis的拦截器实现原理

    对于拦截器Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:

     1 package org.apache.ibatis.plugin;
     2 
     3 import java.util.Properties;
     4 
     5 public interface Interceptor {
     6 
     7   Object intercept(Invocation invocation) throws Throwable;
     8 
     9   Object plugin(Object target);
    10 
    11   void setProperties(Properties properties);
    12 
    13 }

    我们可以看到在该接口中一共定义有三个方法,intercept、plugin和setProperties。plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法,这点将在后文讲解。setProperties方法是用于在Mybatis配置文件中指定一些属性的。

    定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。

    对于plugin方法而言,其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。这里我们先来看一下Plugin的源码:

    复制代码
      1 package org.apache.ibatis.plugin;
      2 
      3 import java.lang.reflect.InvocationHandler;
      4 import java.lang.reflect.Method;
      5 import java.lang.reflect.Proxy;
      6 import java.util.HashMap;
      7 import java.util.HashSet;
      8 import java.util.Map;
      9 import java.util.Set;
     10 
     11 import org.apache.ibatis.reflection.ExceptionUtil;
     12 
     13 //这个类是Mybatis拦截器的核心,大家可以看到该类继承了InvocationHandler
     14 //又是JDK动态代理机制
     15 public class Plugin implements InvocationHandler {
     16 
     17   //目标对象
     18   private Object target;
     19   //拦截器
     20   private Interceptor interceptor;
     21   //记录需要被拦截的类与方法
     22   private Map<Class<?>, Set<Method>> signatureMap;
     23 
     24   private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
     25     this.target = target;
     26     this.interceptor = interceptor;
     27     this.signatureMap = signatureMap;
     28   }
     29 
     30   //一个静态方法,对一个目标对象进行包装,生成代理类。
     31   public static Object wrap(Object target, Interceptor interceptor) {
     32     //首先根据interceptor上面定义的注解 获取需要拦截的信息
     33     Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
     34     //目标对象的Class
     35     Class<?> type = target.getClass();
     36     //返回需要拦截的接口信息
     37     Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
     38     //如果长度为>0 则返回代理类 否则不做处理
     39     if (interfaces.length > 0) {
     40       return Proxy.newProxyInstance(
     41           type.getClassLoader(),
     42           interfaces,
     43           new Plugin(target, interceptor, signatureMap));
     44     }
     45     return target;
     46   }
     47 
     48   //代理对象每次调用的方法
     49   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     50     try {
     51       //通过method参数定义的类 去signatureMap当中查询需要拦截的方法集合
     52       Set<Method> methods = signatureMap.get(method.getDeclaringClass());
     53       //判断是否需要拦截
     54       if (methods != null && methods.contains(method)) {
     55         return interceptor.intercept(new Invocation(target, method, args));
     56       }
     57       //不拦截 直接通过目标对象调用方法
     58       return method.invoke(target, args);
     59     } catch (Exception e) {
     60       throw ExceptionUtil.unwrapThrowable(e);
     61     }
     62   }
     63 
     64   //根据拦截器接口(Interceptor)实现类上面的注解获取相关信息
     65   private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
     66     //获取注解信息
     67     Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
     68     //为空则抛出异常
     69     if (interceptsAnnotation == null) { // issue #251
     70       throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
     71     }
     72     //获得Signature注解信息
     73     Signature[] sigs = interceptsAnnotation.value();
     74     Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
     75     //循环注解信息
     76     for (Signature sig : sigs) {
     77       //根据Signature注解定义的type信息去signatureMap当中查询需要拦截方法的集合
     78       Set<Method> methods = signatureMap.get(sig.type());
     79       //第一次肯定为null 就创建一个并放入signatureMap
     80       if (methods == null) {
     81         methods = new HashSet<Method>();
     82         signatureMap.put(sig.type(), methods);
     83       }
     84       try {
     85         //找到sig.type当中定义的方法 并加入到集合
     86         Method method = sig.type().getMethod(sig.method(), sig.args());
     87         methods.add(method);
     88       } catch (NoSuchMethodException e) {
     89         throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
     90       }
     91     }
     92     return signatureMap;
     93   }
     94 
     95   //根据对象类型与signatureMap获取接口信息
     96   private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
     97     Set<Class<?>> interfaces = new HashSet<Class<?>>();
     98     //循环type类型的接口信息 如果该类型存在与signatureMap当中则加入到set当中去
     99     while (type != null) {
    100       for (Class<?> c : type.getInterfaces()) {
    101         if (signatureMap.containsKey(c)) {
    102           interfaces.add(c);
    103         }
    104       }
    105       type = type.getSuperclass();
    106     }
    107     //转换为数组返回
    108     return interfaces.toArray(new Class<?>[interfaces.size()]);
    109   }
    110 
    111 }
    复制代码

    下面是俩个注解类的定义源码

     
     1 package org.apache.ibatis.plugin;
     2 
     3 import java.lang.annotation.ElementType;
     4 import java.lang.annotation.Retention;
     5 import java.lang.annotation.RetentionPolicy;
     6 import java.lang.annotation.Target;
     7 
     8 @Retention(RetentionPolicy.RUNTIME)
     9 @Target(ElementType.TYPE)
    10 public @interface Intercepts {
    11   Signature[] value();
    12 }
     
     
     1 package org.apache.ibatis.plugin;
     2 
     3 import java.lang.annotation.ElementType;
     4 import java.lang.annotation.Retention;
     5 import java.lang.annotation.RetentionPolicy;
     6 import java.lang.annotation.Target;
     7 
     8 @Retention(RetentionPolicy.RUNTIME)
     9 @Target(ElementType.TYPE)
    10 public @interface Signature {
    11   Class<?> type();
    12 
    13   String method();
    14 
    15   Class<?>[] args();
    16 }
     
  • 相关阅读:
    VS2013编写的C#程序,在xp下会报错说“不是合法的win32程序”。
    能根据串口驱动来 确定com号
    javaweb工程,Servlet里面获取当前WEB跟路径的文件绝对路径地址
    import了sun开头的类,虽然它在代码里压根就没派上用处!但是必须得引用!
    页面关闭时触发的时间
    jquery设置元素的readonly和disabled
    ibatis CDATA
    form的submit与onsubmit的用法与区别
    C#操作AD及Exchange Server总结(一)
    AD如何用C#进行增删改、查询用户与OU
  • 原文地址:https://www.cnblogs.com/anyiz/p/10661384.html
Copyright © 2020-2023  润新知