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 }

Plugin源代码分析

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

 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 }

http://www.cnblogs.com/daxin/p/3541922.html

时间: 2024-11-05 13:47:16

Mybatis Interceptor 拦截器原理 源码分析的相关文章

【Flume】flume中拦截器的源码分析,以TimestampInterceptor为例

本文将以TimestampInterceptor为例来分析一下flume中拦截器的工作原理 首先来看下改拦截器的实现结构 1.实现了Interceptor接口 该接口的方法定义如下: public void initialize(); public Event intercept(Event event); public List<Event> intercept(List<Event> events); public void close(); /** Builder imple

MyBatis框架的使用及源码分析(十一) StatementHandler

我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Excecutor执行sql操作的最终都调用了StatementHandler 来执行,我们拿SimpleExecutor来看: public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement st

Mybatis结合Spring注解自动扫描源码分析

作为一个想做架构师的程序员,必须是一个优秀的程序员,在引入某一个框架的时候,必须要研究源码,将新的开源框架的风险变为可控性. 1.Spring结合Mybatis最常用的配置. <!--理论加实践,才是架构师嘚最佳实践 --> <!--JDBC Data Source --> < bean id= "testdataSource" class= "org.springframework.jdbc.datasource.DriverManagerDa

[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的

Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的 不过源码其实比较简单,接下来简单介绍一下 我们先从启动类说起 有一个Launcher类   sun.misc.Launcher; 仔细看下这简短的几行注释,可以得到有用的信息 ps:直接IDE里面查看反编译的,看不到注释的,可以下载openJDK查看源码,我的这个版本是openjdk-8-src-b132-03_mar_2014 sun.misc.Launcher这个类是系统用于

AtomicInteger原理&amp;源码分析

转自https://www.cnblogs.com/rever/p/8215743.html 深入解析Java AtomicInteger原子类型 在进行并发编程的时候我们需要确保程序在被多个线程并发访问时可以得到正确的结果,也就是实现线程安全.线程安全的定义如下: 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的. 举个线程不安全的例子.假如我们想实现一个功能来统计

storm事件管理器EventManager源码分析-event.clj

storm事件管理器定义在event.clj中,主要功能就是通过独立线程执行"事件处理函数".我们可以将"事件处理函数"添加到EventManager的阻塞队列中,EventManager的事件处理线程不断地从阻塞队列中获取"事件处理函数"并执行. EventManager协议 协议就是一组函数定义的集合,协议中函数的第一个参数必须为实现该协议的实例本身,类似于java中实例方法的第一个参数为this:协议类似于java中的接口. (defpro

Mybatis ResultMap复合映射使用以及源码分析

我们知道在mybatis中可以针对一列值作为入参进行嵌套查询,那么如果入参为多个时该如何处理呢? mybatis支持复合映射,下面通过示例代码看看复合映射的使用 <resultMap id="postLiteMap2NestedWithSelect" type="org.apache.ibatis.domain.blog.BlogLite"> <id column="blog_id" property="id"

MyBatis框架的使用及源码分析(十二) ParameterHandler

在StatementHandler使用prepare()方法后,接下来就是使用ParameterHandler来设置参数,让我们看看它的定义: package org.apache.ibatis.executor.parameter; import java.sql.PreparedStatement; import java.sql.SQLException; /** * A parameter handler sets the parameters of the {@code Prepare

MyBatis拦截器的执行顺序引发的MyBatis源码分析

你猜一下哪个先执行?反正不要按常规来. 1 <plugins> 2 <plugin interceptor="com.Interceptor1"></plugin> 3 <plugin interceptor="com.Interceptor2"></plugin> 4 </plugins> 之前看有的博客分析源码,都没提到这一点.之前我只是用一下而已,这个顺序测试一下其实结论也很容易获得,但是