成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

搞懂MyBatis攔截器的工作原理

開發 前端
從本質上講,MyBatis 中實現攔截的基本手段是構建了一個攔截器鏈,這和設計模式中的責任鏈模式比較類似。而在底層原理上,攔截操作的實現還是基于動態代理機制,通過獲取對應方法的簽名、輸入的接口和參數等信息來生成代理,從而確保我們可以在代理對象中添加各種自定義的攔截邏輯。

在日常開發過程中,我們經常會碰到這樣一種場景,在對某一個請求的處理過程中添加一定的特殊邏輯,但又不想對整個處理流程中的其他步驟造成影響。比如,我們希望在兩個業務操作之間嵌入一個安全控制機制。

請求處理流程中嵌入定制化操作的示意圖請求處理流程中嵌入定制化操作的示意圖

顯然,想要實現這種效果的方式有很多種。今天,我們就來介紹一種非常實用的實現方法,也就是攔截器(Interceptor)?,F在,讓我們從攔截器的設計思想開始講起。

攔截器的設計思想

對于攔截過程而言,我們首先要明確的是它的攔截點。在攔截器運行過程中,攔截點表示應用執行過程中能夠插入攔截器的一個點。這種攔截點可以是普通的方法調用、類初始化或對象實例化,也可以是針對異常的處理。

一旦捕獲了攔截點,我們就可以通過反射機制獲取這個攔截點對應的執行方法、輸入參數、目標返回值等元數據,從而根據這些元數據來實現一系列自定義攔截操作。

攔截點結構圖攔截點結構圖

最后,將攔截點和攔截操作結合在一起就構成了攔截器。本質上,攔截器用于定義應用程序中的業務邏輯及其執行的位置。我們可以通過一張圖來展示攔截器的組成結構。

攔截器組成結構示意圖攔截器組成結構示意圖

攔截器的設計思想非常通用,所以它在各大主流開源框架中的應用也非常廣泛。今天,我們就以常見的 ORM 框架——MyBatis 為例,詳細分析一下 ORM 框架中所具備的攔截器的工作原理。

MyBatis 中的攔截器工作原理

MyBatis 中內置了一組常用的攔截器,而開發人員也可以通過 Plugin 配置項,來嵌入各種定制化的攔截器。我們先來看一下在 MyBatis 中使用攔截器的方式。通常,就是在配置文件中添加類似如下所示的配置項??梢钥吹剑?Plugin 配置段中可以添加一個自定義的 interceptor 配置項,并設置對應的屬性。

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

MyBatis 中的 Configuration 類會根據配置的攔截器屬性,實例化 Interceptor 對象,并添加到 MyBatis 的運行上下文中。我們跟蹤代碼,發現 Configuration 中定義了一個 InterceptorChain 對象。顯然,所有的 Interceptor 實例最終會被添加到這個 InterceptorChain 中。

protected final InterceptorChain interceptorChain = new InterceptorChain();

這樣,MyBatis 中,代表攔截器的 Interceptor 和代表攔截器鏈的 InterceptorChain 的這兩個核心對象都出現了。其中 Interceptor 是個接口,InterceptorChain 是個實體類,它們的代碼看上去都不多。讓我們先來看一下 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 方法,用于將攔截器添加到鏈中。這個類持有一個 interceptors 數組,用于把新加入的 Interceptor 保存起來。通過這種實現方式,在 pluginAll 方法中,我們就可以直接遍歷 interceptors 數組,并利用每個 interceptor 執行攔截邏輯。

這里我們不明確的就是 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 方法實際上存在一個默認實現,這里它通過 Plugin.wrap 方法完成了對目標對象的攔截。Plugin.wrap 是一個靜態方法,實現過程如下所示:

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

這里我們看到了熟悉的 InvocationHandler 接口和 Proxy.newProxyInstance 實現方法,從而明白了,原來這里用到了 JDK 的動態代理機制。我們通過 getSignatureMap 方法從攔截器的注解中獲取攔截的類名和方法信息,然后,通過 getAllInterfaces 方法獲取接口。最后,通過動態代理機制產生代理。這樣使得只有是 Interceptor 注解的接口實現類才會產生代理。

講完 Interceptor 和 InterceptorChain 之后,讓我們再次回到 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;
}

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

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

對于 SQL 的執行過程而言,這四個環節的攔截機制基本可以滿足日常的定制化需求了。

自定義 MyBatis 攔截器的實現方法

雖然 MyBatis 已經內置了一組強大的攔截器,我們可以基于這組攔截器來應對常見需求。但針對某些特定的應用場景,有時候我們就需要自己來實現定制化的攔截器。接下來,我們就來看一下如何在 MyBatis 中自定義一個 Interceptor。

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

@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) {
    }
}

在上面這個 MyInterceptor 類的 intercept 方法中,我們需要調用 invocation.proceed() 方法來完成 InterceptChain 的執行流程,而我們可以在這個方法的前后添加定制化處理過程。

然后,我們來考慮一個攔截器的常見應用場景。

在實現數據庫插入和更新操作時,我們往往需要對這條記錄的更新時間進行同步更新。我們當然可以為每句 SQL 添加相應的時間處理方法,但更好的一種方式是通過自定義攔截器的方式來自動完成這一步操作。

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

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

明確了 Signature 信息之后,我們就可以來著手實現整個流程了。為了方便起見,我們可以提供一個如下所示的注解,專門用來標識具體需要進行攔截的字段:

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

然后,我們創建一個業務領域類,把該注解作用于具體的更新時間字段上。

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

完整的 UpdateTimeStampInterceptor 實現如下,我們對關鍵代碼都添加了注釋:

@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 操作,則更新操作時間
                    if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
                        field.setAccessible(true);
                        if (field.get(parameter) == null) {
                         //設置參數值
                            field.set(parameter, new Date());
                        }
                    }
                }
            }
        }
        //繼續執行攔截器鏈
        return invocation.proceed();
    }
  ...
}

上面這個 UpdateTimeStampInterceptor 類的實現過程,展示了如何獲取與 Executor 相關的 Statement、SQL 類型以及所攜帶的參數。通過這種方法,我們可以實現在新增或者刪除數據庫記錄時,動態地添加所需要的字段值。同樣,這種處理方式可以擴展到任何我們想要處理的字段和參數。

總結

從本質上講,MyBatis 中實現攔截的基本手段是構建了一個攔截器鏈,這和設計模式中的責任鏈模式比較類似。而在底層原理上,攔截操作的實現還是基于動態代理機制,通過獲取對應方法的簽名、輸入的接口和參數等信息來生成代理,從而確保我們可以在代理對象中添加各種自定義的攔截邏輯。

基于 MyBatis 中的攔截器機制,還針對 Executor 的 update 方法給出了一個自定義的 Interceptor 實現,用于動態設置數據庫中某些數據項的值。這些做法都可以直接應用到日常開發過程中。

責任編輯:武曉燕 來源: 程序員技術充電站
相關推薦

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

2025-05-09 08:20:50

2009-06-24 16:00:00

2009-09-27 17:37:32

Hibernate攔截

2025-02-28 08:14:53

2020-03-25 17:55:30

SpringBoot攔截器Java

2021-07-16 11:35:20

Java線程池代碼

2019-12-19 08:56:21

MybatisSQL執行器

2011-05-16 10:14:11

Hibernate

2011-11-21 14:21:26

SpringMVCJava框架

2009-07-08 17:02:11

JDK實現調用攔截器

2009-06-25 15:54:42

Struts2教程攔截器

2021-11-03 17:04:11

攔截器操作Servlet

2009-06-25 15:59:21

Struts2教程攔截器

2022-01-05 14:30:44

容器Linux網絡
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 香蕉视频一区二区三区 | 97久久精品人人澡人人爽 | 五月天黄色网址 | 日韩欧美国产高清91 | 欧美日韩小视频 | 日本少妇一区二区 | 久草黄色 | 日韩久久综合 | 在线中文字幕视频 | 亚洲午夜久久 | 久久久精品国产sm调教网站 | 欧美成视频 | 日韩国产一区二区 | 欧美日韩精品一区二区在线播放 | av黄色片| av免费观看网站 | 91国内在线| 五月婷婷激情综合 | 成人三级视频 | 午夜精品视频 | 天天干狠狠干 | 成人免费在线观看 | 亚洲网在线 | 五月婷婷六月激情 | 人人干人人草 | 国产日韩视频 | 日本黄色三级视频 | 欧美日韩成人一区二区三区 | 99视频网站 | 青草视频在线观看免费 | 国产精品免费人成网站酒店 | 欧美激情国产精品 | 亚洲国产三级 | 国产美女一区二区三区 | 午夜aaa| 午夜看看 | 三级网站视频 | 色婷婷免费视频 | 精品成人在线 | 黄色成人在线观看 | 免费精品视频 |