欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談myBatis中的插件機制

 更新時間:2020年11月26日 09:26:38   作者:Che-ri-sh  
這篇文章主要介紹了淺談myBatis中的插件機制,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

插件的配置與使用

在mybatis-config.xml配置文件中配置plugin結(jié)點,比如配置一個自定義的日志插件LogInterceptor和一個開源的分頁插件PageInterceptor:

<plugins>
  <plugin interceptor="com.crx.plugindemo.LogInterceptor"></plugin>
  <plugin interceptor="com.github.pagehelper.PageInterceptor">
    <property name="helperDialect" value="oracle" />
  </plugin>
</plugins>

插件的工作原理

借助責(zé)任鏈模式,定義一系列的過濾器,在查詢等方法執(zhí)行時進(jìn)行過濾,從而達(dá)到控制參數(shù)、調(diào)整查詢語句和控制查詢結(jié)果等作用。下面從插件的加載(初始化)、注冊和調(diào)用這三個方面闡述插件的工作原理。

過濾器的加載(初始化)

和其他配置信息一樣,過濾器的加載也會在myBatis讀取配置文件創(chuàng)建Configuration對象時進(jìn)行,相應(yīng)的信息存儲在Configuration的interceptorChain屬性中,InterceptorChain封裝了一個包含Interceptor的list:

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

在XMLConfigBuilder進(jìn)行解析配置文件時執(zhí)行pluginElement方法,生成過濾器實例,并添加到上述list中:

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).getDeclaredConstructor()
        .newInstance();
      interceptorInstance.setProperties(properties);
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

過濾器的注冊

可以為Executor、ParameterHandler、ResultSetHandler和StatementHandler四個接口注冊過濾器,注冊的時機也就是這四種接口的實現(xiàn)類的對象的生成時機,比如Executor的過濾器的注冊發(fā)生在SqlSessionFactory使用openSession方法構(gòu)建SqlSession的過程中(因為SqlSession依賴一個Executor實例),ParameterHandler和StatementHandler的過濾器發(fā)生在doQuery等sql執(zhí)行方法執(zhí)行時注冊,而ResultHandler的過濾器的注冊則發(fā)生在查詢結(jié)果返回給客戶端的過程中。以Executor的過濾器的注冊為例,經(jīng)過了這樣的過程:

現(xiàn)在詳細(xì)的分析一下Plugin的wrap這個靜態(tài)的包裝方法:

public static Object wrap(Object target, Interceptor interceptor) {
  // 從定義的Interceptor實現(xiàn)類上的注解讀取需要攔截的類、方法
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  // Executor、ParameterHandler、ResultSetHandler、StatementHandler
  Class<?> type = target.getClass();
  // 從當(dāng)前執(zhí)行的目標(biāo)類中進(jìn)行匹配,過濾出符合當(dāng)前目標(biāo)的的過濾器
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  if (interfaces.length > 0) {
    // 動態(tài)代理生成Executor的代理實例
    return Proxy.newProxyInstance(type.getClassLoader(), interfaces,
                   new Plugin(target, interceptor, signatureMap));
  }
  return target;
}

上述代碼中的getSignatureMap方法是解析Interceptor上面的注解的過程,從注解中讀取出需要攔截的方法,依據(jù)@Signature的三個變量類、方法method和參數(shù)args就能通過反射唯一的定位一個需要攔截的方法。

private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
  Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  if (interceptsAnnotation == null) {
    throw new PluginException(
      "No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
  }
  Signature[] sigs = interceptsAnnotation.value();
  Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
  for (Signature sig : sigs) {
    Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
    try {
      Method method = sig.type().getMethod(sig.method(), sig.args());
      methods.add(method);
    } catch (NoSuchMethodException e) {
      throw new PluginException(
        "Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
    }
  }
  return signatureMap;
}

而getAllInterfaces方法是依據(jù)不同的目標(biāo)對象(Executor等四種)進(jìn)行過濾的過程,只給對應(yīng)的目標(biāo)進(jìn)行注冊:

private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
  Set<Class<?>> interfaces = new HashSet<>();
  while (type != null) {
    for (Class<?> c : type.getInterfaces()) {
      if (signatureMap.containsKey(c)) {
        interfaces.add(c);
      }
    }
    type = type.getSuperclass();
  }
  return interfaces.toArray(new Class<?>[interfaces.size()]);
}

至此,實際使用的Executor對象將是通過動態(tài)代理生成的Plugin實例。

過濾器的調(diào)用

在第二步中完成了過濾器的注冊,在實際調(diào)用Executor時,將由實現(xiàn)了InvocationHandler接口的Plugin實例進(jìn)行接管,對Executor相應(yīng)方法方法的調(diào)用,將實際上調(diào)用動態(tài)代理體系下的invoke方法:

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)) {
      Object result=interceptor.intercept(new Invocation(target, method, args));
      return result;
    }
    return method.invoke(target, args);
  } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
  }
}

如前所述,插件的工作原理是基于責(zé)任鏈模式,可以注冊多個過濾器,層層包裝,最終由內(nèi)而外形成了一個近似裝飾器模式的責(zé)任鏈,最里面的基本實現(xiàn)是CachingExecutor:

從InterceptorChain的pluginAll方法可以看出這個結(jié)構(gòu)的構(gòu)造過程:

public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    // 從這可以看出過濾器的傳遞的過程:動態(tài)代理實例由內(nèi)而外層層包裝,類似于與裝飾器的結(jié)構(gòu),基礎(chǔ)			 實現(xiàn)是一個Executor
    target = interceptor.plugin(target);
  }
  return target;
}

這種由內(nèi)而外的包裝的棧結(jié)構(gòu)從外向內(nèi)層層代理調(diào)用,完成了責(zé)任鏈任務(wù)的逐級推送。從這個注冊過程可以看到,在list中越前面的Interceptor越先被代理,在棧結(jié)構(gòu)中越處于底層,執(zhí)行的順序越靠后。造成了注冊順序和執(zhí)行順序相反的現(xiàn)象。

插件的典型案例PageHelper

pagehelper是一個實現(xiàn)物理分頁效果的開源插件,并且在底層通過Dialect類適配了不同的數(shù)據(jù)庫,其主要作用是攔截sql查詢,構(gòu)造一個查詢總數(shù)的新的以"_COUNT"結(jié)尾的新sql,最終再進(jìn)行分頁查詢。

自定義插件

定義Interceptor接口的實現(xiàn)類并在其上使用@Intercepts和@Signature注解進(jìn)行過濾的類和方法,比如定義一個打日志的插件:

@Intercepts({
		@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 }), })
public class LogInterceptor implements Interceptor {
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		System.out.println("進(jìn)入了自定義的插件過濾器!");
		System.out.println("執(zhí)行的目標(biāo)是:" + invocation.getTarget());
		System.out.println("執(zhí)行的方法是:" + invocation.getMethod());
		System.out.println("執(zhí)行的參數(shù)是:" + invocation.getArgs());
		return invocation.proceed();
	}
}

@Intercepts注解中包含了一個方法簽名數(shù)組,即@Signature數(shù)組,@Signature有三個屬性,type、method和args分別定義要攔截的類、方法名和參數(shù),這樣就可以通過反射唯一的確定了要攔截的方法。type即為在工作原理分析中提到的Executor、ParameterHandler、ResultSetHandler和StatementHandler,method配置對應(yīng)接口中的方法。

到此這篇關(guān)于淺談myBatis中的插件機制的文章就介紹到這了,更多相關(guān)myBatis 插件機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring Boot Admin 環(huán)境搭建與基本使用詳解

    Spring Boot Admin 環(huán)境搭建與基本使用詳解

    這篇文章主要介紹了Spring Boot Admin 環(huán)境搭建與基本使用,本文主要是對于Spring Boot Admin的基本認(rèn)識和基本運用,通過本篇博客能夠?qū)pring Boot Admin有一個宏觀認(rèn)知和能夠快速上手,需要的朋友可以參考下
    2023-08-08
  • 在Java中int和byte[]的相互轉(zhuǎn)換

    在Java中int和byte[]的相互轉(zhuǎn)換

    這篇文章主要介紹了在Java中int和byte[]的相互轉(zhuǎn)換的相關(guān)資料,需要的朋友可以參考下
    2016-11-11
  • Java日常練習(xí)題,每天進(jìn)步一點點(46)

    Java日常練習(xí)題,每天進(jìn)步一點點(46)

    下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你
    2021-08-08
  • 詳解spring security之httpSecurity使用示例

    詳解spring security之httpSecurity使用示例

    這篇文章主要介紹了詳解spring security之httpSecurity使用示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • += 和 ++ 操作符區(qū)別簡單介紹

    += 和 ++ 操作符區(qū)別簡單介紹

    這篇文章主要介紹了+= 和 ++ 操作符區(qū)別簡單介紹的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-09-09
  • java實現(xiàn)日期拆分的方法

    java實現(xiàn)日期拆分的方法

    這篇文章主要介紹了java實現(xiàn)日期拆分的方法,基于java日期類實現(xiàn)對日期字符串的拆分功能,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • 簡單談?wù)凧ava中的棧和堆

    簡單談?wù)凧ava中的棧和堆

    堆和棧都是Java用來在RAM中存放數(shù)據(jù)的地方,下面這篇文章主要給大家介紹了關(guān)于Java中棧和堆的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2021-11-11
  • 自定義spring mvc的json視圖實現(xiàn)思路解析

    自定義spring mvc的json視圖實現(xiàn)思路解析

    這篇文章主要介紹了自定義spring mvc的json視圖的實現(xiàn)思路解析,本文給大家介紹的非常詳細(xì),具有參考借鑒價值,需要的朋友可以參考下
    2017-12-12
  • SpringBoot基于HttpMessageConverter實現(xiàn)全局日期格式化

    SpringBoot基于HttpMessageConverter實現(xiàn)全局日期格式化

    這篇文章主要介紹了SpringBoot基于HttpMessageConverter實現(xiàn)全局日期格式化,使用Jackson消息轉(zhuǎn)換器,非常具有實用價值,需要的朋友可以參考下
    2018-12-12
  • Java使用云片API發(fā)送短信驗證碼

    Java使用云片API發(fā)送短信驗證碼

    這篇文章主要介紹了Java使用云片API發(fā)送短信驗證碼,主要用的是Java實現(xiàn)短信驗證碼。需要的朋友可以參考下
    2017-02-02

最新評論