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 }