偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

搞懂MyBatis攔截器的工作原理

開(kāi)發(fā) 前端
從本質(zhì)上講,MyBatis 中實(shí)現(xiàn)攔截的基本手段是構(gòu)建了一個(gè)攔截器鏈,這和設(shè)計(jì)模式中的責(zé)任鏈模式比較類似。而在底層原理上,攔截操作的實(shí)現(xiàn)還是基于動(dòng)態(tài)代理機(jī)制,通過(guò)獲取對(duì)應(yīng)方法的簽名、輸入的接口和參數(shù)等信息來(lái)生成代理,從而確保我們可以在代理對(duì)象中添加各種自定義的攔截邏輯。

在日常開(kāi)發(fā)過(guò)程中,我們經(jīng)常會(huì)碰到這樣一種場(chǎng)景,在對(duì)某一個(gè)請(qǐng)求的處理過(guò)程中添加一定的特殊邏輯,但又不想對(duì)整個(gè)處理流程中的其他步驟造成影響。比如,我們希望在兩個(gè)業(yè)務(wù)操作之間嵌入一個(gè)安全控制機(jī)制。

請(qǐng)求處理流程中嵌入定制化操作的示意圖請(qǐng)求處理流程中嵌入定制化操作的示意圖

顯然,想要實(shí)現(xiàn)這種效果的方式有很多種。今天,我們就來(lái)介紹一種非常實(shí)用的實(shí)現(xiàn)方法,也就是攔截器(Interceptor)?,F(xiàn)在,讓我們從攔截器的設(shè)計(jì)思想開(kāi)始講起。

攔截器的設(shè)計(jì)思想

對(duì)于攔截過(guò)程而言,我們首先要明確的是它的攔截點(diǎn)。在攔截器運(yùn)行過(guò)程中,攔截點(diǎn)表示應(yīng)用執(zhí)行過(guò)程中能夠插入攔截器的一個(gè)點(diǎn)。這種攔截點(diǎn)可以是普通的方法調(diào)用、類初始化或?qū)ο髮?shí)例化,也可以是針對(duì)異常的處理。

一旦捕獲了攔截點(diǎn),我們就可以通過(guò)反射機(jī)制獲取這個(gè)攔截點(diǎn)對(duì)應(yīng)的執(zhí)行方法、輸入?yún)?shù)、目標(biāo)返回值等元數(shù)據(jù),從而根據(jù)這些元數(shù)據(jù)來(lái)實(shí)現(xiàn)一系列自定義攔截操作。

攔截點(diǎn)結(jié)構(gòu)圖攔截點(diǎn)結(jié)構(gòu)圖

最后,將攔截點(diǎn)和攔截操作結(jié)合在一起就構(gòu)成了攔截器。本質(zhì)上,攔截器用于定義應(yīng)用程序中的業(yè)務(wù)邏輯及其執(zhí)行的位置。我們可以通過(guò)一張圖來(lái)展示攔截器的組成結(jié)構(gòu)。

攔截器組成結(jié)構(gòu)示意圖攔截器組成結(jié)構(gòu)示意圖

攔截器的設(shè)計(jì)思想非常通用,所以它在各大主流開(kāi)源框架中的應(yīng)用也非常廣泛。今天,我們就以常見(jiàn)的 ORM 框架——MyBatis 為例,詳細(xì)分析一下 ORM 框架中所具備的攔截器的工作原理。

MyBatis 中的攔截器工作原理

MyBatis 中內(nèi)置了一組常用的攔截器,而開(kāi)發(fā)人員也可以通過(guò) Plugin 配置項(xiàng),來(lái)嵌入各種定制化的攔截器。我們先來(lái)看一下在 MyBatis 中使用攔截器的方式。通常,就是在配置文件中添加類似如下所示的配置項(xiàng)??梢钥吹剑?Plugin 配置段中可以添加一個(gè)自定義的 interceptor 配置項(xiàng),并設(shè)置對(duì)應(yīng)的屬性。

<plugins>
     <plugin interceptor="com.xiaoyiran.Mybatis.interceptor.MyInterceptor”>
          <proper  ty name=”prop1″ value="prop1″/>
          <property name="prop2" value="prop2"/>
    </plugin>
</plugins>

MyBatis 中的 Configuration 類會(huì)根據(jù)配置的攔截器屬性,實(shí)例化 Interceptor 對(duì)象,并添加到 MyBatis 的運(yùn)行上下文中。我們跟蹤代碼,發(fā)現(xiàn) Configuration 中定義了一個(gè) InterceptorChain 對(duì)象。顯然,所有的 Interceptor 實(shí)例最終會(huì)被添加到這個(gè) InterceptorChain 中。

protected final InterceptorChain interceptorChain = new InterceptorChain();

這樣,MyBatis 中,代表攔截器的 Interceptor 和代表攔截器鏈的 InterceptorChain 的這兩個(gè)核心對(duì)象都出現(xiàn)了。其中 Interceptor 是個(gè)接口,InterceptorChain 是個(gè)實(shí)體類,它們的代碼看上去都不多。讓我們先來(lái)看一下 InterceptorChain 類:

public class InterceptorChain {
privatefinal List<Interceptor> 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<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

這段代碼中,InterceptorChain 提供了 addInterceptor 方法,用于將攔截器添加到鏈中。這個(gè)類持有一個(gè) interceptors 數(shù)組,用于把新加入的 Interceptor 保存起來(lái)。通過(guò)這種實(shí)現(xiàn)方式,在 pluginAll 方法中,我們就可以直接遍歷 interceptors 數(shù)組,并利用每個(gè) interceptor 執(zhí)行攔截邏輯。

這里我們不明確的就是 interceptor.plugin(target) 方法的邏輯,讓我們把思路回到 Interceptor 接口。

public interface Interceptor {
  Object intercept(Invocation invocation) throws Throwable;
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  default void setProperties(Properties properties) {
    // NOP
  }
}

可以看到,Interceptor 接口中的 plugin 方法實(shí)際上存在一個(gè)默認(rèn)實(shí)現(xiàn),這里它通過(guò) Plugin.wrap 方法完成了對(duì)目標(biāo)對(duì)象的攔截。Plugin.wrap 是一個(gè)靜態(tài)方法,實(shí)現(xiàn)過(guò)程如下所示:

public class Plugin implements InvocationHandler {
//省略變量定義和構(gòu)造函數(shù)
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) {
   //執(zhí)行動(dòng)態(tài)代理
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
  ...
}

這里我們看到了熟悉的 InvocationHandler 接口和 Proxy.newProxyInstance 實(shí)現(xiàn)方法,從而明白了,原來(lái)這里用到了 JDK 的動(dòng)態(tài)代理機(jī)制。我們通過(guò) getSignatureMap 方法從攔截器的注解中獲取攔截的類名和方法信息,然后,通過(guò) getAllInterfaces 方法獲取接口。最后,通過(guò)動(dòng)態(tài)代理機(jī)制產(chǎn)生代理。這樣使得只有是 Interceptor 注解的接口實(shí)現(xiàn)類才會(huì)產(chǎn)生代理。

講完 Interceptor 和 InterceptorChain 之后,讓我們?cè)俅位氐?Configuration 類,并找到以下代碼:

public ParameterHandler newParameterHandler(...) {
    ParameterHandler parameterHandler = ...;
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}
public ResultSetHandler newResultSetHandler(...) {
    ResultSetHandler resultSetHandler = ...;
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}
public StatementHandler newStatementHandler(...) {
    StatementHandler statementHandler = ...;
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    Executor executor  = ...;
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

講解這段代碼的目的是在說(shuō)明這樣一個(gè)事實(shí):在 MyBatis 中,攔截器只能攔截 ParameterHandler、StatementHandler、ResultSetHandler 和 Executor 這四種類型的接口,這點(diǎn)在 Configuration 類中是通過(guò)以上代碼預(yù)先定義好的。這些就構(gòu)成了 MyBatis 中針對(duì)攔截器的各個(gè)攔截點(diǎn)。如果我們想要實(shí)現(xiàn)自定義攔截器,也只能圍繞上述四種接口添加邏輯。這四個(gè)接口之間的關(guān)系和攔截順序如下圖所示:

MyBatis 中能夠累計(jì)額的四種接口類型及其順序MyBatis 中能夠累計(jì)額的四種接口類型及其順序

對(duì)于 SQL 的執(zhí)行過(guò)程而言,這四個(gè)環(huán)節(jié)的攔截機(jī)制基本可以滿足日常的定制化需求了。

自定義 MyBatis 攔截器的實(shí)現(xiàn)方法

雖然 MyBatis 已經(jīng)內(nèi)置了一組強(qiáng)大的攔截器,我們可以基于這組攔截器來(lái)應(yīng)對(duì)常見(jiàn)需求。但針對(duì)某些特定的應(yīng)用場(chǎng)景,有時(shí)候我們就需要自己來(lái)實(shí)現(xiàn)定制化的攔截器。接下來(lái),我們就來(lái)看一下如何在 MyBatis 中自定義一個(gè) Interceptor。

如果想要在 MyBatis 中實(shí)現(xiàn)一個(gè)自定義攔截器,我們要做的事情就是實(shí)現(xiàn)上面介紹的 Interceptor 接口,并在這個(gè)接口上指定相應(yīng)的 Signature 信息。一個(gè)空白的 Interceptor 實(shí)現(xiàn)類模版如下所示:

@Intercepts({@Signature(type = Executor.class, method ="update", args = {MappedStatement.class, Object.class})})
public class MyInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
        returnPlugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }
}

在上面這個(gè) MyInterceptor 類的 intercept 方法中,我們需要調(diào)用 invocation.proceed() 方法來(lái)完成 InterceptChain 的執(zhí)行流程,而我們可以在這個(gè)方法的前后添加定制化處理過(guò)程。

然后,我們來(lái)考慮一個(gè)攔截器的常見(jiàn)應(yīng)用場(chǎng)景。

在實(shí)現(xiàn)數(shù)據(jù)庫(kù)插入和更新操作時(shí),我們往往需要對(duì)這條記錄的更新時(shí)間進(jìn)行同步更新。我們當(dāng)然可以為每句 SQL 添加相應(yīng)的時(shí)間處理方法,但更好的一種方式是通過(guò)自定義攔截器的方式來(lái)自動(dòng)完成這一步操作。

顯然,這一步操作應(yīng)該是在 Executor 中進(jìn)行完成。根據(jù)上面對(duì) Plugin 類的介紹,我們首先需要明確 Executor 中需要攔截的方法,而這方法就是如下所示的 update 方法:

int update(MappedStatement ms, Object parameter) throws SQLException;

明確了 Signature 信息之后,我們就可以來(lái)著手實(shí)現(xiàn)整個(gè)流程了。為了方便起見(jiàn),我們可以提供一個(gè)如下所示的注解,專門用來(lái)標(biāo)識(shí)具體需要進(jìn)行攔截的字段:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface UpdateTimeStamp {
   String value() default "";
}

然后,我們創(chuàng)建一個(gè)業(yè)務(wù)領(lǐng)域類,把該注解作用于具體的更新時(shí)間字段上。

public class MyDomain {
 //省略其他字段定義
 @UpdateTimeStamp
 public D  ate updateTimeStamp;
}

完整的 UpdateTimeStampInterceptor 實(shí)現(xiàn)如下,我們對(duì)關(guān)鍵代碼都添加了注釋:

@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class UpdateTimeStampInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
     //獲取 MappedStatement
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        //獲取 SqlCommandType
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        //獲取 Parameter
        Object parameter = invocation.getArgs()[1];
        if (parameter != null) {
            Field[] declaredFields = parameter.getClass().getDeclaredFields();
            for (Field field : declaredFields) {
             //獲取 UpdateTimeStamp 注解
                if (field.getAnnotation(UpdateTimeStamp.class) != null) {
                 //如果是 Insert 或 Update 操作,則更新操作時(shí)間
                    if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
                        field.setAccessible(true);
                        if (field.get(parameter) == null) {
                         //設(shè)置參數(shù)值
                            field.set(parameter, new Date());
                        }
                    }
                }
            }
        }
        //繼續(xù)執(zhí)行攔截器鏈
        return invocation.proceed();
    }
  ...
}

上面這個(gè) UpdateTimeStampInterceptor 類的實(shí)現(xiàn)過(guò)程,展示了如何獲取與 Executor 相關(guān)的 Statement、SQL 類型以及所攜帶的參數(shù)。通過(guò)這種方法,我們可以實(shí)現(xiàn)在新增或者刪除數(shù)據(jù)庫(kù)記錄時(shí),動(dòng)態(tài)地添加所需要的字段值。同樣,這種處理方式可以擴(kuò)展到任何我們想要處理的字段和參數(shù)。

總結(jié)

從本質(zhì)上講,MyBatis 中實(shí)現(xiàn)攔截的基本手段是構(gòu)建了一個(gè)攔截器鏈,這和設(shè)計(jì)模式中的責(zé)任鏈模式比較類似。而在底層原理上,攔截操作的實(shí)現(xiàn)還是基于動(dòng)態(tài)代理機(jī)制,通過(guò)獲取對(duì)應(yīng)方法的簽名、輸入的接口和參數(shù)等信息來(lái)生成代理,從而確保我們可以在代理對(duì)象中添加各種自定義的攔截邏輯。

基于 MyBatis 中的攔截器機(jī)制,還針對(duì) Executor 的 update 方法給出了一個(gè)自定義的 Interceptor 實(shí)現(xiàn),用于動(dòng)態(tài)設(shè)置數(shù)據(jù)庫(kù)中某些數(shù)據(jù)項(xiàng)的值。這些做法都可以直接應(yīng)用到日常開(kāi)發(fā)過(guò)程中。

責(zé)任編輯:武曉燕 來(lái)源: 程序員技術(shù)充電站
相關(guān)推薦

2025-08-01 07:07:18

2023-09-05 08:58:07

2024-12-27 08:39:10

2025-01-02 10:10:51

2025-07-30 01:00:25

2009-06-04 08:01:25

Struts2攔截器原理

2024-02-28 09:35:52

2009-06-24 16:00:00

2025-05-09 08:20:50

2009-09-27 17:37:32

Hibernate攔截

2025-02-28 08:14:53

2025-08-07 07:36:06

2020-03-25 17:55:30

SpringBoot攔截器Java

2021-07-16 11:35:20

Java線程池代碼

2019-12-19 08:56:21

MybatisSQL執(zhí)行器

2011-05-16 10:14:11

Hibernate

2009-07-08 17:02:11

JDK實(shí)現(xiàn)調(diào)用攔截器

2011-11-21 14:21:26

SpringMVCJava框架

2009-06-25 15:54:42

Struts2教程攔截器

2009-06-25 15:59:21

Struts2教程攔截器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)