溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

深入淺出MyBatis:MyBatis插件及開發(fā)過程

發(fā)布時間:2020-07-14 20:12:12 來源:網(wǎng)絡 閱讀:5455 作者:情情說 欄目:開發(fā)技術(shù)

本篇文章是「深入淺出MyBatis:技術(shù)原理與實踐」書籍的總結(jié)筆記。

上一篇介紹了 MyBatis解析和運行原理 ,包括SqlSessionFactory的構(gòu)建和SqlSession的執(zhí)行過程,其中,SqlSession包含四大對象,可以在四大對象調(diào)度的時候插入自定義的代碼,以滿足特殊的需求,這便是MyBatis提供的插件技術(shù)。

有些特殊場景,需要使用插件統(tǒng)一處理,比如:在進行多租戶開發(fā)時,數(shù)據(jù)要按租戶隔離,可以在sql語句后面統(tǒng)一添加租戶編號篩選條件。

本篇就來介紹下插件,通過本篇的介紹,你會了解到:

  • 插件接口和初始化
  • 插件的代理和反射設計
  • 工具類MetaObject介紹
  • 插件的開發(fā)過程
插件的接口和初始化分析
插件接口

在MyBatis中使用插件,需要實現(xiàn)Interceptor接口,定義如下:

public interface Interceptor {
  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);
}

詳細說說這3個方法:

  • intercept:它將直接覆蓋你所攔截的對象,有個參數(shù)Invocation對象,通過該對象,可以反射調(diào)度原來對象的方法;
  • plugin:target是被攔截的對象,它的作用是給被攔截對象生成一個代理對象;
  • setProperties:允許在plugin元素中配置所需參數(shù),該方法在插件初始化的時候會被調(diào)用一次;
插件初始化

插件的初始化時在MyBatis初始化的時候完成的,讀入插件節(jié)點和配置的參數(shù),使用反射技術(shù)生成插件實例,然后調(diào)用插件方法中的setProperties方法設置參數(shù),并將插件實例保存到配置對象中,具體過程看下面代碼。

plugin配置示例如下:

<plugins>
    <plugin interceptor="com.qqdong.study.mybatis.TenantPlugin">
        <property name="dbType" value="mysql"/>
    </plugin>
<plugins>

插件初始化過程:

public class XMLConfigBuilder extends BaseBuilder {
  ......
  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);
      }
    }
  }
  ......
}

配置對象Configuration的添加插件方法:

public class Configuration {
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }
}

InterceptorChain是一個類,主要包含一個List屬性,保存Interceptor對象:

public class InterceptorChain {
  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
    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);
  }
}
插件的代理和反射設計原理
責任鏈模式

插件用的是責任鏈模式,責任鏈模式是一種對象行為模式。在責任鏈模式里,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈,請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。

設計細節(jié)

前面提到了InterceptorChain類,其中有個pluginAll方法,責任鏈就是在該方法定義的。

public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
}

上面介紹過plugin方法,它是生成代理對象的方法,從第一個對象(四大對象中的一個)開始,將對象傳遞給了plugin方法,返回一個代理;如果存在第二個插件,就拿著第一個代理對象,傳遞給plugin方法,返回第一個代理對象的代理.....

plugin方法是需要我們?nèi)崿F(xiàn)的,如何生成代理類呢,MyBatis提供了Plugin工具類,它實現(xiàn)了InvocationHandler接口(JDK動態(tài)代理的接口),看看它的2個方法:

public class Plugin implements InvocationHandler {
  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
}

分析下這塊代碼,Plugin提供了靜態(tài)方法wrap方法,它會根據(jù)插件的簽名配置,使用JDK動態(tài)代理的方法,生成一個代理類,當四大對象執(zhí)行方法時,會調(diào)用Plugin的invoke方法,如果方法包含在聲明的簽名里,就會調(diào)用自定義插件的intercept方法,傳入Invocation對象。

另外,Invocation對象包含一個proceed方法,這個方法就是調(diào)用被代理對象的真實方法,如果有n個插件,第一個傳遞的參數(shù)是四大對象本身,然后調(diào)用一次wrap方法產(chǎn)生第一個代理對象,這里的反射就是四大對象的真實方法,如果有第二個插件,這里的反射就是第一個代理對象的invoke方法。

所以,在多個插件的情況下,調(diào)度proceed方法,MyBatis總是從最后一個代理對象運行到第一個代理對象,最后是真實被攔截的對象方法被執(zhí)行。

工具類MetaObject介紹

MetaObject是MyBatis給我們提供的工具類,它可以有效的獲取或修改一些重要對象的屬性。

舉例說明,我們攔截StatementHandler對象,首先要獲取它要執(zhí)行的SQL,添加返回行數(shù)限制。

編寫一個自定義插件,實現(xiàn)intercept方法,方法實現(xiàn)如下

StatementHandler statementHandler=(StatementHandler)invocation.getTarget();
MetaObject metaObj=SystemMetaObject.forObject(statementHandler);

//獲取sql
String sql=(String)metaStatementHandler.getValue("delegate.bound.sql");
//添加limit條件
sql="select * from (" + sql + ") limit 1000";
//重新設置sql
metaStatementHandler.setValue("delegate.bound.sql",sql);
插件的開發(fā)過程

最后總結(jié)下插件的開發(fā)步驟。

確定要攔截的簽名
  • 確定要攔截的對象,四大對象之一;
  • 確定攔截的方法和參數(shù);

比如想攔截StatementHandler對象的prepare方法,該方法有一個參數(shù)Connection對象,可以這樣聲明:

@Intercepts({
    @Signature(type =StatementHandler.class,
        method="prepare" , 
        args={Connection.class})})
public class MyPlugin implements Interceptor{
    ......
}
定義插件類,實現(xiàn)攔截方法

上面已經(jīng)分析過原理,實現(xiàn)Interceptor接口的方法即可,通過Plugin工具類方便生成代理類,通過MetaObject工具類方便操作四大對象的屬性,修改對應的值。

配置

最后配置自定義的插件:

<plugins>
    <plugin interceptor="com.qqdong.study.mybatis.TenantPlugin">
        <property name="dbType" value="mysql"/>
    </plugin>
<plugins>

自定義插件還是比較復雜的,如果不了解原理,很容易出錯,能不用插件盡量不要使用,因為它是修改MyBatis的底層設計。 插件生成的是層層代理對象的責任鏈模式,通過反射方法運行,性能不高,要考慮全面,特別是多個插件層層代理的邏輯。

下一篇會介紹MyBatis與Spring的集成。

歡迎掃描下方二維碼,關(guān)注我的個人微信公眾號 ~

深入淺出MyBatis:MyBatis插件及開發(fā)過程

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI