<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插件?那說說自定義插件是如何加載的吧?

          共 3525字,需瀏覽 8分鐘

           ·

          2021-01-09 18:45

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

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

          1. 插件配置

          Mybatis的插件配置在configuration內(nèi)部,初始化時(shí),會(huì)讀取這些插件,保存于Configuration對(duì)象的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)代表了只要是插件,都會(huì)以責(zé)任鏈的方式逐一執(zhí)行(別指望它能跳過某個(gè)節(jié)點(diǎn)),所謂插件,其實(shí)就類似于攔截器。

          2. 如何編寫一個(gè)插件

          插件必須實(shí)現(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)容的地方,比如想收點(diǎn)保護(hù)費(fèi)。由plugin()方法觸發(fā),interceptor.plugin(target)足以證明。

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

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

          下面自定義一個(gè)攔截器:

          @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)建一個(gè)JDK的動(dòng)態(tài)代理對(duì)象,觸發(fā)intercept()方法
          ??return?Plugin.wrap(target,?this);
          ?}

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

          }

          面對(duì)上面的代碼,我們需要解決兩個(gè)疑問:

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

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

          @Intercepts注解:裝載一個(gè)@Signature列表,一個(gè)@Signature其實(shí)就是一個(gè)需要攔截的方法封裝。那么,一個(gè)攔截器要攔截多個(gè)方法,自然就是一個(gè)@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的動(dòng)態(tài)代理,給target對(duì)象創(chuàng)建一個(gè)delegate代理對(duì)象,以此來實(shí)現(xiàn)方法攔截和增強(qiáng)功能,它會(huì)回調(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動(dòng)態(tài)代理對(duì)象
          ??????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:緩存需攔截對(duì)象的反射結(jié)果,避免多次反射,即target的反射結(jié)果。

          所以,我們不要?jiǎng)硬粍?dòng)就說反射性能很差,那是因?yàn)槟銢]有像Mybatis一樣去緩存一個(gè)對(duì)象的反射結(jié)果。

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

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

          3. Mybatis可以攔截哪些接口對(duì)象?

          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個(gè)接口對(duì)象內(nèi)的方法。

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

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

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

          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)用時(shí)機(jī)
          ????????interceptorInstance.setProperties(properties);
          ????????configuration.addInterceptor(interceptorInstance);
          ??????}
          ????}
          ??}

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

          6. 分頁插件原理

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

          要實(shí)現(xiàn)物理分頁,就需要對(duì)String sql進(jìn)行攔截并增強(qiáng),Mybatis通過BoundSql對(duì)象存儲(chǔ)String sql,而BoundSql則由StatementHandler對(duì)象獲取。

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

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

          因此,就需要編寫一個(gè)針對(duì)StatementHandler的query方法攔截器,然后獲取到sql,對(duì)sql進(jìn)行重寫增強(qiáng)。

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

          推薦一個(gè)專注后端面試的公眾號(hào)

          長(zhǎng)按掃碼關(guān)注,每天學(xué)習(xí),一起進(jìn)大廠!


          往期推薦

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

          IDEA中無法import自己工程中類的問題解決方法

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

          云服務(wù)商正在殺死開源商業(yè)模式

          Java 項(xiàng)目權(quán)威排名:Spring生態(tài)搶鏡,Gradle戰(zhàn)勝M(fèi)aven排第2,點(diǎn)擊查看更多...

          當(dāng)會(huì)打王者榮耀的AI學(xué)會(huì)踢足球,一不小心拿下世界冠軍!



          瀏覽 56
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  日韩精品视频观看 | 久久精品导航 | 插逼网站视频 | 操b视频手机观看 | 日本无码 视频在线观 |