mybatis官方定義:MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。sql
mybatis的攔截器,通常用於針對數據庫的一些通用操做處理,好比慢查耗時打印,壓測場景影子表寫入。用戶須要使用攔截器的時候,經過實現Interceptor接口便可。攔截器的功能,不只帶來了切面編程的優點,還使用起來也很方便。那麼mybatis具體是如何實現攔截器的呢?下面咱們來一探究竟。如下全部分析均基於3.4.5版本。數據庫
經過查看源碼,咱們能夠發現,關於攔截器的代碼,都放在了plugin包目錄下,該目錄下包含七個類:編程
Intercepts和Signature,對於熟悉mybatis切面編程的同窗都知道,是用戶的Interceptor實現類註解。數組
Intercepts的內部結構很簡單就是Signature數組:mybatis
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface Intercepts { Signature[] value(); }
Signature註解也比較簡單,包含目標類,方法,入參類型數組,標識惟一一個方法app
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Signature {
// 目標類 Class<?> type(); // 方法 String method(); // 入參類型數組 Class<?>[] args(); }
InterceptorChain類的pluginAll方法是mybatis初始化的時候,初始化攔截器功能的入口方法框架
private final List<Interceptor> interceptors = new ArrayList();
// target是Executor實現類之一,全部sql語句執行都須要經過這些實現類生效 public Object pluginAll(Object target) { Interceptor interceptor;
// 遍歷數組,調用每個interceptor的plugin方法 for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) { interceptor = (Interceptor)var2.next(); } return target; }
interceptor實現類(須要使用者編寫)的plugin方法一個標準的實現以下:ide
@Override public Object plugin(Object target) {
// 直接調用 return Plugin.wrap(target, this); }
Plugin類wrap方法,Plugin實現InvocationHandler,用於JDK動態代理this
public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final 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) {
// 根據Intercepor實現類的註解,獲取Executor實現類各個須要攔截的方法,Map中的key是Executor實現類,value是類中須要攔截的方法集合 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass();
// 遍歷target類型的接口數值,由於target同一實現Executor接口,因此該數組長度爲1,值類型爲Executor.class Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 根據是否須要代理,返回target代理類或者target return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target; }private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { Intercepts interceptsAnnotation = (Intercepts)interceptor.getClass().getAnnotation(Intercepts.class); if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } else { Signature[] sigs = interceptsAnnotation.value(); Map<Class<?>, Set<Method>> signatureMap = new HashMap(); Signature[] var4 = sigs; int var5 = sigs.length; for(int var6 = 0; var6 < var5; ++var6) { Signature sig = var4[var6]; Set<Method> methods = (Set)signatureMap.get(sig.type()); if (methods == null) { methods = new HashSet(); signatureMap.put(sig.type(), methods); } try { Method method = sig.type().getMethod(sig.method(), sig.args()); ((Set)methods).add(method); } catch (NoSuchMethodException var10) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + var10, var10); } } return signatureMap; } } private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { HashSet interfaces; for(interfaces = new HashSet(); type != null; type = type.getSuperclass()) { Class[] var3 = type.getInterfaces(); int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) { Class<?> c = var3[var5]; if (signatureMap.containsKey(c)) { interfaces.add(c); } } } return (Class[])interfaces.toArray(new Class[interfaces.size()]); }
所以,咱們能夠看到,調用路徑爲:InterceptorChain.pulginAll --> Interceptor.plugin --> Pulgin.wrap,InterceptorChain.pulginAll的入參target和返回值經歷了這樣的一個過程:target --> 根據Intercepor實現類的註解是否包含本target,經過JDK動態代理返回Proxy或者target --> target --> 下一個Intercepor,這樣一直遍歷InterceptorChain,不斷返回當前target的代理類或者直接返回target,在target包了一層又一層:spa
最後返回的target就是就是不斷代理的結果,而相鄰代理之間經過Pulgin.wrap方法實現,wrap方法實現上調用了Proxy,也就是經過JDK的動態代理實現
以上是從初始化時,已pulginAll方法爲切入點,看攔截器各個模塊間的關係以及實現方式,下面從sql執行的角度看看。
經過調試發現,執行的入口方法的Pulgin.invoke方法,當代理對象執行方法調用的時候,就會進入
public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final 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 Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try {
// 獲取全部須要攔截的方法,這裏method.getDeclaringClass()的值爲Executor.class Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
// 判斷當前方法是否須要攔截,須要攔截則調用interceptor實現類的intercept方法並將被代理對象,接口方法,入參傳入,不然直接調用被代理對象方法 return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args); } catch (Exception var5) { throw ExceptionUtil.unwrapThrowable(var5); } }
... }
Interceptor實現類通常會處理一下業務上需求,最後調用被代理類
@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }), @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }), @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }) }) @Component public class SqlMonitorManager implements Interceptor { private boolean showSql = true; @Override public Object intercept(Invocation invocation) throws Throwable { // 這裏是業務處理 /****/ // 調用proceed方法 try { return invocation.proceed(); } catch (Throwable e) { throw e; } }
// 初始化時,能夠指定屬性值,這裏配置了showSql @Override public void setProperties(Properties properties) { if (properties == null) { return; } if (properties.containsKey("show_sql")) { String value = properties.getProperty("show_sql"); if (Boolean.TRUE.toString().equals(value)) { this.showSql = true; } } } }
intercept方法最後調用了invocation的proceed方法:
public class Invocation { private final Object target; private final Method method; private final Object[] args; public Invocation(Object target, Method method, Object[] args) { this.target = target; this.method = method; this.args = args; } // 調用被代理類方法 public Object proceed() throws InvocationTargetException, IllegalAccessException { return this.method.invoke(this.target, this.args); } }
其實從執行的調度就是從最外層的proxy,層層進入,最後調用target的方法執行sql,這與動態代理的使用也是相似的,存在調用路徑爲:
Proxy2.method --> Pulgin.invoke --> 是否方法攔截,若是是,掉用interceptor.intercept方法,最後調用被代理類方法,若是否,調用直接調用代理類方法啊 -->Proxy1.method,這樣一直調用下去。調用流程圖以下:
總的來講,mybatis攔截器提供了相對方便而且可控的切面編程支持,也是一種動態代理的一種最佳實踐。經過嵌套代理,實現多個攔截器,經過傳遞被代理類方法以及入參,推遲並由用戶決定被代理類的調用,從而實現攔截。