mybatis Interceptor攔截器代碼詳解

  mybatis官方定義:MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。sql

  mybatis的攔截器,通常用於針對數據庫的一些通用操做處理,好比慢查耗時打印,壓測場景影子表寫入。用戶須要使用攔截器的時候,經過實現Interceptor接口便可。攔截器的功能,不只帶來了切面編程的優點,還使用起來也很方便。那麼mybatis具體是如何實現攔截器的呢?下面咱們來一探究竟。如下全部分析均基於3.4.5版本。數據庫

1.攔截器初始化

  經過查看源碼,咱們能夠發現,關於攔截器的代碼,都放在了plugin包目錄下,該目錄下包含七個類:編程

  1. Intercepts:註解類,其value爲Signature類數值,註解在Interceptor實現類上,表示實現類對哪些sql執行類(實現Executor)的哪些方法切入
  2. Signature:註解類,表示一個惟一的Interceptor實現類的一個方法,以及入參
  3. InterceptorChain:攔截器鏈表,用於初始化一個攔截器鏈
  4. Interceptor:攔截器接口
  5. Invocation:攔截銜接類,用於指向下一個攔截器或者sql執行類
  6. Plugin:攔截器實現輔助類
  7. PluginException:異常

  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的動態代理實現

2.sql執行

  以上是從初始化時,已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,這樣一直調用下去。調用流程圖以下:

  

3.總結

  總的來講,mybatis攔截器提供了相對方便而且可控的切面編程支持,也是一種動態代理的一種最佳實踐。經過嵌套代理,實現多個攔截器,經過傳遞被代理類方法以及入參,推遲並由用戶決定被代理類的調用,從而實現攔截。

相關文章
相關標籤/搜索