Mybatis:攔截器原理

簡(jiǎn)要闡述一下Mybatis執(zhí)行數(shù)據(jù)庫查詢操作過程,使用Executor作為執(zhí)行器調(diào)用StatementHandler對(duì)Statement進(jìn)行處理,如果是PreparedStatement會(huì)通過PreparedStatementHandler為條件占位符設(shè)置條件值,底層還是通過JDBC原生Statement執(zhí)行數(shù)據(jù)庫操作,通過ResultSetHandler返回處理返回的ResultSet結(jié)果集封裝成ResultMap對(duì)象返回

Mybatis架構(gòu)

簡(jiǎn)單了解了Mybatis執(zhí)行的主要流程,明白StatementHandler、ParameterHandler、ResultSetHandler的角色定位,鋪墊了學(xué)習(xí)攔截器使用場(chǎng)景和原理的基礎(chǔ)

在Mybatis的核心配置類Configuration中,可以看到在創(chuàng)建ParameterHandler、ResultSetHandler、StatementHandler時(shí)為它們進(jìn)行了攔截操作

public class Configuration {
    // 攔截器注冊(cè)中心
    protected final InterceptorChain interceptorChain = new InterceptorChain();
    /*
    * 創(chuàng)建ParameterHandler
    */
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, 
            Object parameterObject, BoundSql boundSql) {

        ParameterHandler parameterHandler = mappedStatement.getLang()
                .createParameterHandler(mappedStatement, parameterObject, boundSql);
        // 為ParameterHandler應(yīng)用攔截器
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
    }
    /*
    * 創(chuàng)建ResultSetHandler 
    */
    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應(yīng)用攔截器
        resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
    }
    /*
    * 創(chuàng)建StatementHandler 
    */
    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應(yīng)用攔截器
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }
}

InterceptorChain顧名思義為攔截器鏈,也可以簡(jiǎn)單地理解為攔截器的注冊(cè)中心,Mybatis所有攔截器都注冊(cè)到了攔截器鏈中

public class InterceptorChain {

    private final List<Interceptor> interceptors = new ArrayList<>();
    // 每個(gè)攔截器都會(huì)嘗試攔截所有的處理器
    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<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }
}

Interceptor是攔截器接口,重點(diǎn)提供了一個(gè)調(diào)用攔截器的plugin方法,以及攔截器攔截邏輯處理intercept方法

public interface Interceptor {
    // 應(yīng)用攔截器邏輯
    Object intercept(Invocation invocation) throws Throwable;
    // 執(zhí)行當(dāng)前攔截器
    Object plugin(Object target);
    void setProperties(Properties properties);
}

以最常用的分頁處理插件,分析如何應(yīng)用攔截器

PaginationInterceptor攔截StatementHandlerprepare方法執(zhí)行,該方法入?yún)?code>Connection、Integer

它的核心原理是為StatementHandler生成代理對(duì)象,當(dāng)StatementHandler執(zhí)行prepare方法的時(shí)候,執(zhí)行攔截方法添加分頁邏輯

當(dāng)通過Configuration創(chuàng)建StatementHandler的時(shí)候,會(huì)觸發(fā)攔截器鏈的執(zhí)行,PaginationInterceptorplugin方法會(huì)被調(diào)用,此時(shí)它為StatementHandler生成代理對(duì)象,當(dāng)代理對(duì)象執(zhí)行prepare方法的時(shí)候會(huì)觸發(fā)代理對(duì)象的invoke方法,此時(shí)會(huì)校驗(yàn)當(dāng)前執(zhí)行的方法是否為攔截方法,若是則執(zhí)行PaginationInterceptor的分頁邏輯interceptor方法

@Intercepts({
    @Signature(
        type = StatementHandler.class, 
        method = "prepare", 
        args = {Connection.class, Integer.class})})
public class PaginationInterceptor extends SqlParserHandler implements Interceptor {

    /**
     * 這個(gè)方法會(huì)被Mybatis的攔截器鏈調(diào)用
     * 在這里為StatementHandler生成代理對(duì)象
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    // 分頁攔截器添加分頁邏輯攔截方法
    public Object intercept(Invocation invocation) throws Throwable {
        // ...
        return invocation.proceed();  // 處理完成調(diào)用被代理對(duì)象的目標(biāo)方法,放行
    }

}

Plugin為攔截器提供了工具方法,可以為攔截目標(biāo)生成代理對(duì)象,記錄攔截目標(biāo)被攔截的方法信息,以及作為InvocationHandler實(shí)現(xiàn)類提供了代理增強(qiáng)方法invoke

public class Plugin implements InvocationHandler {
    /**
     * @param target 被攔截的對(duì)象
     * @param interceptor 攔截器
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        // 記錄要攔截的方法
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
            // 利用JDK動(dòng)態(tài)代理生成代理對(duì)象
            return Proxy.newProxyInstance(
                    type.getClassLoader(),
                    interfaces,
                    // 當(dāng)前類實(shí)現(xiàn)了動(dòng)態(tài)代理InvocationHandler接口,
                    // 封裝被代理的目標(biāo),攔截器,以及攔截的目標(biāo)方法
                    new Plugin(target, interceptor, signatureMap));  
        }
        return target;
    }

    /*
     * 解析攔截器類上的Intercepts注解,緩存要攔截的方法信息
     */
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        if (interceptsAnnotation == null) {
            // ...省略異常輸出
        }
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
        for (Signature sig : sigs) {
            Set<Method> methods = signatureMap.get(sig.type());
            if (methods == null) {
                methods = new HashSet<Method>();
                signatureMap.put(sig.type(), methods);
            }
            try {
                Method method = sig.type().getMethod(sig.method(), sig.args());
                methods.add(method);
                } catch (NoSuchMethodException e) {
                    // ...
            }
        }
        return signatureMap;
    }  

    /**
     * 這個(gè)方法是JDK動(dòng)態(tài)代理對(duì)象調(diào)用目標(biāo)方法之后,會(huì)被調(diào)用的方法
     * 在這里首先判斷當(dāng)前當(dāng)前代理對(duì)象執(zhí)行的方法是否為攔截方法,
     * 如果不是使用被代理對(duì)象調(diào)用原先方法
     * 如果是則使用調(diào)用代理對(duì)象的攔截方法
     */ 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Set<Method> methods = signatureMap.get(method.getDeclaringClass());
            // 執(zhí)行被攔截的方法,調(diào)用攔截器的連接方法
            if (methods != null && methods.contains(method)) {
                // 注意這里會(huì)通過Invocation封裝被代理對(duì)象、調(diào)用方法和調(diào)用參數(shù)傳遞給攔截器
                return interceptor.intercept(new Invocation(target, method, args));
            }
            // 調(diào)用的不是攔截方法,使用被代理對(duì)象調(diào)用原先方法
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }
}

Invocation封裝了被代理的目標(biāo)對(duì)象和被調(diào)用的方法,作為入?yún)鬟f給攔截器的interceptor方法,當(dāng)攔截器執(zhí)行完邏輯之后,可以通過proceed方法調(diào)用被代理對(duì)象的原方法邏輯

public class Invocation {

    private final Object target;
    private final Method method;
    private final Object[] args;

    public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }
    // ...
    // 調(diào)用被代理對(duì)象的目標(biāo)方法,結(jié)束攔截器的使用
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容