Mybatis攔截器(六)


攔截器的作用就是我們可以攔截某些方法的調用,在目標方法前后加上我們自己邏輯。

Mybatis攔截器設計的一個初衷是為了供用戶在某些時候可以實現自己的邏輯而不必去動Mybatis固有的邏輯。

Mybatis為我們提供了一個Interceptor接口,通過實現該接口就可以定義我們自己的攔截器(不過,要實現攔截器,需要對mybatis的源碼有很好地理解才行)。

/**
 * mybatis 自定義攔截器
 * 三步驟:
 *  1 實現 {@link Intercepts} 接口
 *  2 添加攔截注解 {@link Intercepts}
 *  3 配置文件中添加攔截器
 *
 * 1 實現 {@link Intercepts} 接口
 *      具體作用可以看下面代碼每個方法的注釋
 * 2 添加攔截注解 {@link Intercepts}
 *      mybatis 攔截器默認可攔截的類型只有四種,即四種接口類型 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler
 *      對於我們的自定義攔截器必須使用 mybatis 提供的注解來指明我們要攔截的是四類中的哪一個類接口
 *      具體規則如下:
 *          a:Intercepts 標識我的類是一個攔截器
 *          b:Signature 則是指明我們的攔截器需要攔截哪一個接口的哪一個方法
 *              type    對應四類接口中的某一個,比如是 Executor
 *              method  對應接口中的哪類方法,比如 Executor 的 update 方法
 *              args    對應接口中的哪一個方法,比如 Executor 中 query 因為重載原因,方法有多個,args 就是指明參數類型,從而確定是哪一個方法
 * 3 配置文件中添加攔截器
 *      攔截器其實就是一個 plugin,在 mybatis 核心配置文件中我們需要配置我們的 plugin :
 *          <plugin interceptor="liu.york.mybatis.study.plugin.MyInterceptor">
 *              <property name="username" value="LiuYork"/>
 *              <property name="password" value="123456"/>
 *          </plugin>
 *
 * 攔截器順序
 * 1 不同攔截器順序:
 *      Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
 *
 * 2 對於同一個類型的攔截器的不同對象攔截順序:
 *      在 mybatis 核心配置文件根據配置的位置,攔截順序是 從上往下
 */
@Intercepts({
        @Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class}),
        @Signature(method = "query", type = StatementHandler.class, args = {Statement.class, ResultHandler.class})
})
public class MyInterceptor implements Interceptor {

    /**
     * 這個方法很好理解
     * 作用只有一個:我們不是攔截方法嗎,攔截之后我們要做什么事情呢?
     *      這個方法里面就是我們要做的事情
     *
     * 解釋這個方法前,我們一定要理解方法參數 {@link Invocation} 是個什么鬼?
     * 1 我們知道,mybatis攔截器默認只能攔截四種類型 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler
     * 2 不管是哪種代理,代理的目標對象就是我們要攔截對象,舉例說明:
     *      比如我們要攔截 {@link Executor#update(MappedStatement ms, Object parameter)} 方法,
     *      那么 Invocation 就是這個對象,Invocation 里面有三個參數 target method args
     *          target 就是 Executor
     *          method 就是 update
     *          args   就是 MappedStatement ms, Object parameter
     *
     *   如果還是不能理解,我再舉一個需求案例:看下面方法代碼里面的需求
     *
     *  該方法在運行時調用
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        /*
         * 需求:我們需要對所有更新操作前打印查詢語句的 sql 日志
         * 那我就可以讓我們的自定義攔截器 MyInterceptor 攔截 Executor 的 update 方法,在 update 執行前打印sql日志
         * 比如我們攔截點是 Executor 的 update 方法 :  int update(MappedStatement ms, Object parameter)
         *
         * 那當我們日志打印成功之后,我們是不是還需要調用這個query方法呢,如何如調用呢?
         * 所以就出現了 Invocation 對象,它這個時候其實就是一個 Executor,而且 method 對應的就是 query 方法,我們
         * 想要調用這個方法,只需要執行 invocation.proceed()
         */

        /* 因為我攔截的就是Executor,所以我可以強轉為 Executor,默認情況下,這個Executor 是個 SimpleExecutor */
        Executor executor = (Executor)invocation.getTarget();

        /*
         * Executor 的 update 方法里面有一個參數 MappedStatement,它是包含了 sql 語句的,所以我獲取這個對象
         * 以下是偽代碼,思路:
         * 1 通過反射從 Executor 對象中獲取 MappedStatement 對象
         * 2 從 MappedStatement 對象中獲取 SqlSource 對象
         * 3 然后從 SqlSource 對象中獲取獲取 BoundSql 對象
         * 4 最后通過 BoundSql#getSql 方法獲取 sql
         */
        MappedStatement mappedStatement = ReflectUtil.getMethodField(executor, MappedStatement.class);
        SqlSource sqlSource = ReflectUtil.getField(mappedStatement, SqlSource.class);
        BoundSql boundSql = sqlSource.getBoundSql(args);
        String sql = boundSql.getSql();
        logger.info(sql);

        /*
         * 現在日志已經打印,需要調用目標對象的方法完成 update 操作
         * 我們直接調用 invocation.proceed() 方法
         * 進入源碼其實就是一個常見的反射調用 method.invoke(target, args)
         * target 對應 Executor對象
         * method 對應 Executor的update方法
         * args   對應 Executor的update方法的參數
         */

        return invocation.proceed();
    }

    /**
     * 這個方法也很好理解
     * 作用就只有一個:那就是Mybatis在創建攔截器代理時候會判斷一次,當前這個類 MyInterceptor 到底需不需要生成一個代理進行攔截,
     * 如果需要攔截,就生成一個代理對象,這個代理就是一個 {@link Plugin},它實現了jdk的動態代理接口 {@link InvocationHandler},
     * 如果不需要代理,則直接返回目標對象本身
     *
     * Mybatis為什么會判斷一次是否需要代理呢?
     * 默認情況下,Mybatis只能攔截四種類型的接口:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler
     * 通過 {@link Intercepts} 和 {@link Signature} 兩個注解共同完成
     * 試想一下,如果我們開發人員在自定義攔截器上沒有指明類型,或者隨便寫一個攔截點,比如Object,那Mybatis瘋了,難道所有對象都去攔截
     * 所以Mybatis會做一次判斷,攔截點看看是不是這四個接口里面的方法,不是則不攔截,直接返回目標對象,如果是則需要生成一個代理
     *
     *  該方法在 mybatis 加載核心配置文件時被調用
     */
    @Override
    public Object plugin(Object target) {
        /*
         * 看了這個方法注釋,就應該理解,這里的邏輯只有一個,就是讓mybatis判斷,要不要進行攔截,然后做出決定是否生成一個代理
         *
         * 下面代碼什么鬼,就這一句就搞定了?
         * Mybatis判斷依據是利用反射,獲取這個攔截器 MyInterceptor 的注解 Intercepts和Signature,然后解析里面的值,
         * 1 先是判斷要攔截的對象是四個類型中 Executor、StatementHandler、ParameterHandler、 ResultSetHandler 的哪一個
         * 2 然后根據方法名稱和參數(因為有重載)判斷對哪一個方法進行攔截  Note:mybatis可以攔截這四個接口里面的任一一個方法
         * 3 做出決定,是返回一個對象呢還是返回目標對象本身(目標對象本身就是四個接口的實現類,我們攔截的就是這四個類型)
         *
         * 好了,理解邏輯我們寫代碼吧~~~  What !!! 要使用反射,然后解析注解,然后根據參數類型,最后還要生成一個代理對象
         * 我一個小白我怎么會這么高大上的代碼嘛,怎么辦?
         *
         * 那就是使用下面這句代碼吧  哈哈
         * mybatis 早就考慮了這里的復雜度,所以提供這個靜態方法來實現上面的邏輯
         */
        return Plugin.wrap(target, this);
    }

    /**
     * 這個方法最好理解,如果我們攔截器需要用到一些變量參數,而且這個參數是支持可配置的,
     *  類似Spring中的@Value("${}")從application.properties文件獲取
     * 這個時候我們就可以使用這個方法
     *
     * 如何使用?
     * 只需要在 mybatis 配置文件中加入類似如下配置,然后 {@link Interceptor#setProperties(Properties)} 就可以獲取參數
     *      <plugin interceptor="liu.york.mybatis.study.plugin.MyInterceptor">
     *           <property name="username" value="LiuYork"/>
     *           <property name="password" value="123456"/>
     *      </plugin>
     *      方法中獲取參數:properties.getProperty("username");
     *
     * 問題:為什么要存在這個方法呢,比如直接使用 @Value("${}") 獲取不就得了?
     * 原因是 mybatis 框架本身就是一個可以獨立使用的框架,沒有像 Spring 這種做了很多依賴注入的功能
     *
     *  該方法在 mybatis 加載核心配置文件時被調用 
     */
    @Override
    public void setProperties(Properties properties) {
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
        // TODO: 2019/2/28  業務邏輯處理...
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM