<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          寫過Mybatis插件?那說說自定義插件是如何加載的吧?

          共 3524字,需瀏覽 8分鐘

           ·

          2021-02-14 16:43


          大多數(shù)框架,都支持插件,用戶可通過編寫插件來自行擴展功能,Mybatis也不例外。

          我們從插件配置、插件編寫、插件運行原理、插件注冊與執(zhí)行攔截的時機、初始化插件、分頁插件的原理等六個方面展開闡述。

          1. 插件配置

          Mybatis的插件配置在configuration內(nèi)部,初始化時,會讀取這些插件,保存于Configuration對象的InterceptorChain中。


          configuration?PUBLIC?"-//mybatis.org//DTD?Config?3.0//EN"?"http://mybatis.org/dtd/mybatis-3-config.dtd">
          <configuration>
          ????<plugins>
          ??<plugin?interceptor="com.mybatis3.interceptor.MyBatisInterceptor">
          ???<property?name="value"?value="100"?/>
          ??plugin>
          ?plugins>
          configuration>
          public?class?Configuration?{
          ????protected?final?InterceptorChain?interceptorChain?=?new?InterceptorChain();
          }

          org.apache.ibatis.plugin.InterceptorChain.java源碼。

          public?class?InterceptorChain?{

          ??private?final?List?interceptors?=?new?ArrayList();

          ??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?getInterceptors()?{
          ????return?Collections.unmodifiableList(interceptors);
          ??}

          }

          上面的for循環(huán)代表了只要是插件,都會以責任鏈的方式逐一執(zhí)行(別指望它能跳過某個節(jié)點),所謂插件,其實就類似于攔截器。

          2. 如何編寫一個插件

          插件必須實現(xiàn)org.apache.ibatis.plugin.Interceptor接口。

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

          ??Object?plugin(Object?target);

          ??void?setProperties(Properties?properties);

          }

          intercept()方法:執(zhí)行攔截內(nèi)容的地方,比如想收點保護費。由plugin()方法觸發(fā),interceptor.plugin(target)足以證明。

          plugin()方法:決定是否觸發(fā)intercept()方法。

          setProperties()方法:給自定義的攔截器傳遞xml配置的屬性參數(shù)。

          下面自定義一個攔截器:

          @Intercepts({
          ??@Signature(type?=?Executor.class,?method?=?"query",?args?=?{?MappedStatement.class,?Object.class,
          ????RowBounds.class,?ResultHandler.class?}),
          ??@Signature(type?
          =?Executor.class,?method?=?"close",?args?=?{?boolean.class?})?})
          public?class?MyBatisInterceptor?implements?Interceptor?
          {

          ?private?Integer?value;

          ?@Override
          ?public?Object?intercept(Invocation?invocation)?throws?Throwable?{
          ??return?invocation.proceed();
          ?}

          ?@Override
          ?public?Object?plugin(Object?target)?{
          ??System.out.println(value);
          ????????//?Plugin類是插件的核心類,用于給target創(chuàng)建一個JDK的動態(tài)代理對象,觸發(fā)intercept()方法
          ??return?Plugin.wrap(target,?this);
          ?}

          ?@Override
          ?public?void?setProperties(Properties?properties)?{
          ??value?=?Integer.valueOf((String)?properties.get("value"));
          ?}

          }

          面對上面的代碼,我們需要解決兩個疑問:

          1. 為什么要寫Annotation注解?注解都是什么含義?

          答:Mybatis規(guī)定插件必須編寫Annotation注解,是必須,而不是可選。

          @Intercepts注解:裝載一個@Signature列表,一個@Signature其實就是一個需要攔截的方法封裝。那么,一個攔截器要攔截多個方法,自然就是一個@Signature列表。

          type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }

          解釋:要攔截Executor接口內(nèi)的query()方法,參數(shù)類型為args列表。

          1. Plugin.wrap(target, this)是干什么的?

          答:使用JDK的動態(tài)代理,給target對象創(chuàng)建一個delegate代理對象,以此來實現(xiàn)方法攔截和增強功能,它會回調(diào)intercept()方法。

          org.apache.ibatis.plugin.Plugin.java源碼:

          public?class?Plugin?implements?InvocationHandler?{

          ??private?Object?target;
          ??private?Interceptor?interceptor;
          ??private?Map,?Set>?signatureMap;

          ??private?Plugin(Object?target,?Interceptor?interceptor,?Map,?Set>?signatureMap)?{
          ????this.target?=?target;
          ????this.interceptor?=?interceptor;
          ????this.signatureMap?=?signatureMap;
          ??}

          ??public?static?Object?wrap(Object?target,?Interceptor?interceptor)?{
          ????Map,?Set>?signatureMap?=?getSignatureMap(interceptor);
          ????Class?type?=?target.getClass();
          ????Class[]?interfaces?=?getAllInterfaces(type,?signatureMap);
          ????if?(interfaces.length?>?0)?{
          ??????//?創(chuàng)建JDK動態(tài)代理對象
          ??????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?methods?=?signatureMap.get(method.getDeclaringClass());
          ??????//?判斷是否是需要攔截的方法(很重要)
          ??????if?(methods?!=?null?&&?methods.contains(method))?{
          ????????//?回調(diào)intercept()方法
          ????????return?interceptor.intercept(new?Invocation(target,?method,?args));
          ??????}
          ??????return?method.invoke(target,?args);
          ????}?catch?(Exception?e)?{
          ??????throw?ExceptionUtil.unwrapThrowable(e);
          ????}
          ??}
          //...
          }

          Map, Set> signatureMap:緩存需攔截對象的反射結果,避免多次反射,即target的反射結果。

          所以,我們不要動不動就說反射性能很差,那是因為你沒有像Mybatis一樣去緩存一個對象的反射結果。

          判斷是否是需要攔截的方法,這句注釋很重要,一旦忽略了,都不知道Mybatis是怎么判斷是否執(zhí)行攔截內(nèi)容的,要記住。

          知乎砍出正義一刀,PDD祭出終極防御:“供應商員工”!輕松化解攻勢!

          3. Mybatis可以攔截哪些接口對象?

          public?class?Configuration?{
          //...
          public?ParameterHandler?newParameterHandler(MappedStatement?mappedStatement,?Object?parameterObject,?BoundSql?boundSql)?{
          ????ParameterHandler?parameterHandler?=?mappedStatement.getLang().createParameterHandler(mappedStatement,?parameterObject,?boundSql);
          ????parameterHandler?=?(ParameterHandler)?interceptorChain.pluginAll(parameterHandler);?//?1
          ????return?parameterHandler;
          ??}

          ??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);
          ????resultSetHandler?=?(ResultSetHandler)?interceptorChain.pluginAll(resultSetHandler);?//?2
          ????return?resultSetHandler;
          ??}

          ??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);
          ????statementHandler?=?(StatementHandler)?interceptorChain.pluginAll(statementHandler);?//?3
          ????return?statementHandler;
          ??}

          ??public?Executor?newExecutor(Transaction?transaction)?{
          ????return?newExecutor(transaction,?defaultExecutorType);
          ??}

          ??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);
          ????}
          ????executor?=?(Executor)?interceptorChain.pluginAll(executor);?//?4
          ????return?executor;
          ??}
          //...
          }

          Mybatis只能攔截ParameterHandler、ResultSetHandler、StatementHandler、Executor共4個接口對象內(nèi)的方法。

          重新審視interceptorChain.pluginAll()方法:該方法在創(chuàng)建上述4個接口對象時調(diào)用,其含義為給這些接口對象注冊攔截器功能,注意是注冊,而不是執(zhí)行攔截。

          攔截器執(zhí)行時機:plugin()方法注冊攔截器后,那么,在執(zhí)行上述4個接口對象內(nèi)的具體方法時,就會自動觸發(fā)攔截器的執(zhí)行,也就是插件的執(zhí)行。

          所以,一定要分清,何時注冊,何時執(zhí)行。切不可認為pluginAll()或plugin()就是執(zhí)行,它只是注冊。

          4. Invocation

          public?class?Invocation?{
          ??private?Object?target;
          ??private?Method?method;
          ??private?Object[]?args;
          }

          intercept(Invocation invocation)方法的參數(shù)Invocation ,我相信你一定可以看得懂,不解釋。

          JDK 16 即將發(fā)布,新特性速覽!

          5. 初始化插件源碼解析

          org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XNode)方法部分源碼。

          pluginElement(root.evalNode("plugins"));

          ?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();
          ????????//?這里展示了setProperties()方法的調(diào)用時機
          ????????interceptorInstance.setProperties(properties);
          ????????configuration.addInterceptor(interceptorInstance);
          ??????}
          ????}
          ??}

          對于Mybatis,它并不區(qū)分是何種攔截器接口,所有的插件都是Interceptor,Mybatis完全依靠Annotation去標識對誰進行攔截,所以,具備接口一致性。

          6. 分頁插件原理

          由于Mybatis采用的是邏輯分頁,而非物理分頁,那么,市場上就出現(xiàn)了可以實現(xiàn)物理分頁的Mybatis的分頁插件。

          要實現(xiàn)物理分頁,就需要對String sql進行攔截并增強,Mybatis通過BoundSql對象存儲String sql,而BoundSql則由StatementHandler對象獲取。

          public?interface?StatementHandler?{
          ?????List?query(Statement?statement,?ResultHandler?resultHandler)?throws?SQLException;

          ????BoundSql?getBoundSql();
          }
          public?class?BoundSql?{
          ???public?String?getSql()?{
          ????return?sql;
          ??}
          }

          因此,就需要編寫一個針對StatementHandler的query方法攔截器,然后獲取到sql,對sql進行重寫增強。

          任它天高海闊,任它變化無窮,我們只要懂得原理,再多插件,我們都可以對其投送王之蔑視。

          更多好文章

          1. Java高并發(fā)系列(共34篇)
          2. MySql高手系列(共27篇)
          3. Maven高手系列(共10篇)
          4. Mybatis系列(共12篇)
          5. 聊聊db和緩存一致性常見的實現(xiàn)方式
          6. 接口冪等性這么重要,它是什么?怎么實現(xiàn)?
          7. 泛型,有點難度,會讓很多人懵逼,那是因為你沒有看這篇文章!

          瀏覽 36
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费又黄又爽又色的视频 | 欧美日韩国产高清视频 | 欧美成人在线资源 | 视频亚洲无码 | 欧美大屌操逼 |