Mybatis源碼分析之(六)mybatis攔截器(Interceptor)的實現原理

前言

mybatis攔截器是一個非常有用的功能,當你想實現自動分頁,自動記錄執行的sql等功能時,若在service層,每次調用時,都寫代碼的話,會非常麻煩,而使用mybatis攔截器,就可以非常輕鬆的實現了。

  Executor , ResultSetHandler,StatementHandler,ParameterHandler,這是Mybatis中的四大對象,也是攔截器的切入點。我們可以基於這四大對象的方法進行增強。解釋一下,因爲這四個都是接口,我們可以利用動態代理進行方法的增強。
  動態代理,這是底層的原理,若只用了動態代理,那麼你肯定要自己寫代理類,在使用的時候實例化,然後再替換Mybatis裏面的原有對象,對不?但是實際你並不需要這麼做,那是因爲mybatis一開始就爲你設計好了讓你如何簡單快速的添加攔截器,讓你在添加攔截器的時候只用關注業務邏輯,而不需要管類之間的關係。
  那麼Mybatis究竟是怎麼做到這一點的呢?接下來就讓LZ帶大家來看看Mybatis究竟是如何實現的吧。

我們實現mybatis攔截器的步驟,首先創建Interceptor的實現類,然後我們要在mybatis.xml中配置plugins,這就是我們爲Mybatis添加攔截器的步驟。

InterceptorChain保存所有的Interceptor

我們來到Configuration類中,看到裏面他有個屬性叫InterceptorChain,裏面是用來存放我們的所有攔截器,針對四大對象的攔截器全部在裏面。

//這裏就是解析並把plugin加入到interceptorChain中
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
  //加入到interceptorChain
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

創建四大對象都走Configuration

然後,Mybatis在創建四大對象的時候都是走的Configuration類中的方法

//創建ParameterHandler對象
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    //都有interceptorChain.pluginAll()
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }
//創建ResultSetHandler對象
  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);
    //都有interceptorChain.pluginAll()
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }
//創建StatementHandler對象
  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);
    //都有interceptorChain.pluginAll()
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
//創建Executor對象
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    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);
    }
    //都有interceptorChain.pluginAll()
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

觀察上面的四個函數,他們都有一個共有的點,調用了interceptorChain.pluginAll()方法,這也是Mybatis實現攔截器功能的中點,這個pluginAll(),使攔截器有了一個特性,那就是邏輯可以向下傳遞,是責任鏈模式。

InterceptorChain增強對象方法

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
  //通過for循環遍歷interceptors,將所有的interceptor都加進去。
  //一層包一層,直到所有的interceptor都包裝好
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

這裏進去的對象,和出來的對象已經不是同一個了。進去的基礎四大對象,出來的是增強版四大對象。
這裏其實用到了責任鏈模式,每個Interceptor 都有自己要服務的對象,只有當請求的方法和Interceptor要服務的對象匹配時,它纔會執行,你攔截器裏的方法。
接下來LZ帶大家來看看Mybatis是怎麼比對兩個對象是否匹配的。

Plugin封裝動態代理,讓你使用Mybatis攔截器更簡單

我們去Plugin類中

//看到這個類,大家有沒有覺得很熟悉,沒錯,實現了InvocationHandler ,使用動態代理
//這個就是Mybatis實現攔截器功能的底層。
public class Plugin implements InvocationHandler {
    private Object target;
    private Interceptor interceptor;
    private 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;
    }
//我們不需要手動的實例化動態代理對象,是因爲wrap爲我們做了這件事
    public static Object wrap(Object target, Interceptor interceptor) {
        //這句也蠻重要的,是將我們寫的攔截器類的註解轉換成了map,key爲我們的類對象,value是方法
        //爲了之後判斷是否需要執行攔截器的方法
        //這句就不進去仔細分析了,因爲很簡單,就是解析註解而已
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        //這裏實例了動態代理對象 
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }
}
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //獲得方法
            Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
            //如果方法不爲空,說明這個對象確實有拓展攔截器,之後看method是不是有這個方法,有的話才說明這個方法確實被拓展了,之後執行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);
        }
    }

Invocation,讓我們能在攔截器中使用動態代理類中的invoke方法中的對象

//通過這個對象,把代理類中的invoke方法中的對象和我們攔截器類相連。
//我們在攔截器類中的intercept(Invocation invocation)中的invocation就是這個類型
//所以我們就能在攔截器類中獲取到代理類中的各個對象啦
public class Invocation {

  private Object target;
  private Method method;
  private Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}

調用時序圖

process

小結

總結一下,其實底層用到的還是動態代理,但是Mybatis通過封裝,讓我們開發攔截器更加簡單。通過InterceptorChain,使得攔截器能將邏輯向下傳遞,然後通過Invocation,讓攔截器類能使用到動態代理類invoke中的對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章