1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 3525字,需瀏覽 8分鐘

         ·

        2021-01-09 18:45

        大多數(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)代表了只要是插件,都會以責(zé)任鏈的方式逐一執(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:緩存需攔截對象的反射結(jié)果,避免多次反射,即target的反射結(jié)果。

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

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

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

        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進行重寫增強。

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

        推薦一個專注后端面試的公眾號

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


        往期推薦

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

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

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

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

        Java 項目權(quán)威排名:Spring生態(tài)搶鏡,Gradle戰(zhàn)勝Maven排第2,點擊查看更多...

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



        瀏覽 56
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            美女被狂揉下部羞羞电视 | 男女叉叉叉叉 | 欧美国产精品成人爆乳 | 免费开心青涩网 | 捆绑调教一区二区三区 | 五月天草逼网 | 性一交一乱一区二区洋洋av | 色噜噜在线播放 | 爽人人妻人人爽人人爽 | 免费无码又爽又黄A片视频免费看 |