網站首頁 編程語言 正文
攔截器的作用就是我們可以攔截某些方法的調用,在目標方法前后加上我們自己邏輯
Mybatis攔截器設計的一個初衷是為了供用戶在某些時候可以實現自己的邏輯而不必去動Mybatis固有的邏輯。
- mybatis 自定義攔截器
- 1、實現Interceptor 接口,并添加攔截注解 @Intercepts
- 2、配置文件中添加攔截器
1、實現Interceptor 接口,并添加攔截注解 @Intercepts
- mybatis 攔截器默認可攔截的類型只有四種,
- Executor:攔截執行器的方法。
- ParameterHandler:攔截參數的處理。
- ResultHandler:攔截結果集的處理。
- StatementHandler:攔截Sql語法構建的處理。
- 對于我們的自定義攔截器必須使用 mybatis 提供的注解來指明我們要攔截的是四類中的哪一個類接口,具體規則如下:
- a:Intercepts 攔截器: 標識我的類是一個攔截器
- b:Signature 署名: 則是指明我們的攔截器需要攔截哪一個接口的哪一個方法
- type 對應四類接口中的某一個,比如是 Executor
- method 對應接口中的哪類方法,比如 Executor 的 update 方法
- args 對應接口中的哪一個方法,比如 Executor 中 query 因為重載原因,方法有多個,args 就是指明參數類型,從而確定是哪一個方法
@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 業務邏輯處理...
}
}
三個核心方法都加了詳細的注釋,而且結合案例需求說明問題
那么多文字不想行看,沒關系有概括
總結:
1.在mybatis中可被攔截的類型有四種(按照攔截順序):
Executor:攔截執行器的方法。
ParameterHandler:攔截參數的處理。
ResultHandler:攔截結果集的處理。
StatementHandler:攔截Sql語法構建的處理。
2.各個參數的含義:
@Intercepts:標識該類是一個攔截器;
@Signature:指明自定義攔截器需要攔截哪一個類型,哪一個方法;
2.1 type:對應四種類型中的一種;
2.2 method:對應接口中的哪個方法;
2.3 args:對應哪一個方法參數類型(因為可能存在重載方法);
接下來我們看看 Plugin 類
package org.apache.ibatis.plugin;
/**
* Plugin 類其實就是一個代理類,因為它實現了jdk動態代理接口 InvocationHandler
* 我們核心只需要關注兩個方法
* wrap:
* 如果看懂了代碼案例1的例子,那么這個方法很理解,這個方法就是 mybatis 提供給開發人員使用的一個工具類方法,
* 目的就是幫助開發人員省略掉 反射解析注解 Intercepts 和 Signature,有興趣的可以去看看源碼 Plugin#getSignatureMap 方法
*
* invoke:
* 這個方法就是根據 wrap 方法的解析結果,判斷當前攔截器是否需要進行攔截,
* 如果需要攔截:將 目標對象+目標方法+目標參數 封裝成一個 Invocation 對象,給我們自定義的攔截器 MyInterceptor 的 intercept 方法
* 這個時候就剛好對應上了上面案例1中對 intercept 方法的解釋了,它就是我們要處理自己邏輯的方法,
* 處理好了之后是否需要調用目標對象的方法,比如上面說的 打印了sql語句,是否還要查詢數據庫呢?答案是肯定的
* 如果不需要攔截:則直接調用目標對象的方法
* 比如直接調用 Executor 的 update 方法進行更新數據庫
*
*/
class Plugin implements InvocationHandler {
public static Object wrap(Object target, Interceptor interceptor) {
// 省略
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 省略
}
}
貼一段網上的通用解釋吧:
Plugin的wrap方法,它根據當前的Interceptor上面的注解定義哪些接口需要攔截,然后判斷當前目標對象是否有實現對應需要攔截的接口,如果沒有則返回目標對象本身,如果有則返回一個代理對象。而這個代理對象的InvocationHandler正是一個Plugin。所以當目標對象在執行接口方法時,如果是通過代理對象執行的,則會調用對應InvocationHandler的invoke方法,也就是Plugin的invoke方法。所以接著我們來看一下該invoke方法的內容。這里invoke方法的邏輯是:如果當前執行的方法是定義好的需要攔截的方法,則把目標對象、要執行的方法以及方法參數封裝成一個Invocation對象,再把封裝好的Invocation作為參數傳遞給當前攔截器的intercept方法。如果不需要攔截,則直接調用當前的方法。Invocation中定義了定義了一個proceed方法,其邏輯就是調用當前方法,所以如果在intercept中需要繼續調用當前方法的話可以調用invocation的procced方法。
這就是Mybatis中實現Interceptor攔截的一個思想
2、在配置文件中添加攔截器
在springboot中要給mybatis加上這個攔截器,有三種方法,前兩種方法在啟動項目時不會自動調用自定義攔截器的setProperties方法。
- 攔截器順序
- 1、不同攔截器順序
- Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
- 2、對于同一個類型的攔截器的不同對象攔截順序:
- 在 mybatis 核心配置文件根據配置的位置,攔截順序是 從上往下
- 1、不同攔截器順序
(1)第一種
直接給自定義攔截器添加一個 @Component注解,當調用sql時結果如下,可以看到攔截器生效了,但是啟動時候并沒有自動調用setProperties方法。
@Component
@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
public class MybatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//業務代碼
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
}
}
(2)第二種
在配置類里添加攔截器,這種方法結果同上,也不會自動調用setProperties方法。
@Configuration
public class MybatisConfig {
@Bean
public ConfigurationCustomizer mybatisConfigurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(Configuration configuration) {
configuration.addInterceptor(new MybatisInterceptor());
}
};
}
}
(3)第三種
這種方法就是跟以前的配置方法類似,在yml配置文件中指定mybatis的xml配置文件,
注意:config-location屬性和configuration屬性不能同時指定
mybatis:
config-location: classpath:mybatis.xml
type-aliases-package: me.zingon.pagehelper.model
mapper-locations: classpath:mapper/*.xml
mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="me.zingon.pacargle.model"/>
</typeAliases>
<plugins>
<plugin interceptor="me.zingon.pagehelper.interceptor.MyPageInterceptor">
<property name="dialect" value="oracle"/>
</plugin>
</plugins>
</configuration>
可以看到,在啟動項目的時候setProperties被自動調用了
總結:前兩種方法可以在初始化自定義攔截器的時候通過 @Value 注解直接初始化需要的參數。
原文鏈接:https://blog.csdn.net/qq_43985303/article/details/130378043
- 上一篇:沒有了
- 下一篇:沒有了
相關推薦
- 2022-08-15 數據結構之鏈式棧的實現與簡單運用
- 2022-06-22 android選項卡TabHost功能用法詳解_Android
- 2022-12-09 C#調用Windows的API實現窗體動畫_C#教程
- 2023-03-17 Python控制windows系統音量實現實例_python
- 2022-06-10 FreeRTOS實時操作系統之可視化追蹤調試_操作系統
- 2022-09-30 react中useState使用:如何實現在當前表格直接更改數據_React
- 2022-06-06 typescript中的泛型(genericParadigm)、interface、extends、
- 2022-05-06 python使用xlrd模塊讀取excel的方法實例_python
- 欄目分類
-
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支