在springboot中如何給mybatis加攔截器
攔截器的作用就是我們可以攔截某些方法的調(diào)用,在目標方法前后加上我們自己邏輯
Mybatis攔截器設(shè)計的一個初衷是為了供用戶在某些時候可以實現(xiàn)自己的邏輯而不必去動Mybatis固有的邏輯。
mybatis 自定義攔截器
1、實現(xiàn)Interceptor 接口,并添加攔截注解 @Intercepts
2、配置文件中添加攔截器
1、實現(xiàn)Interceptor接口,并添加攔截注解 @Intercepts
mybatis 攔截器默認可攔截的類型只有四種
Executor
:攔截執(zhí)行器的方法。ParameterHandler
:攔截參數(shù)的處理。ResultHandler
:攔截結(jié)果集的處理。StatementHandler
:攔截Sql語法構(gòu)建的處理。
對于我們的自定義攔截器必須使用 mybatis 提供的注解來指明我們要攔截的是四類中的哪一個類接口
具體規(guī)則如下:
a:Intercepts 攔截器:標識我的類是一個攔截器
b:Signature 署名:則是指明我們的攔截器需要攔截哪一個接口的哪一個方法
type
對應(yīng)四類接口中的某一個,比如是 Executormethod
對應(yīng)接口中的哪類方法,比如 Executor 的 update 方法args
對應(yīng)接口中的哪一個方法,比如 Executor 中 query 因為重載原因,方法有多個,args 就是指明參數(shù)類型,從而確定是哪一個方法
@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 { /** * 這個方法很好理解 * 作用只有一個:我們不是攔截方法嗎,攔截之后我們要做什么事情呢? * 這個方法里面就是我們要做的事情 * * 解釋這個方法前,我們一定要理解方法參數(shù) {@link Invocation} 是個什么鬼? * 1 我們知道,mybatis攔截器默認只能攔截四種類型 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler * 2 不管是哪種代理,代理的目標對象就是我們要攔截對象,舉例說明: * 比如我們要攔截 {@link Executor#update(MappedStatement ms, Object parameter)} 方法, * 那么 Invocation 就是這個對象,Invocation 里面有三個參數(shù) target method args * target 就是 Executor * method 就是 update * args 就是 MappedStatement ms, Object parameter * * 如果還是不能理解,我再舉一個需求案例:看下面方法代碼里面的需求 * * 該方法在運行時調(diào)用 */ @Override public Object intercept(Invocation invocation) throws Throwable { /* * 需求:我們需要對所有更新操作前打印查詢語句的 sql 日志 * 那我就可以讓我們的自定義攔截器 MyInterceptor 攔截 Executor 的 update 方法,在 update 執(zhí)行前打印sql日志 * 比如我們攔截點是 Executor 的 update 方法 : int update(MappedStatement ms, Object parameter) * * 那當我們?nèi)罩敬蛴〕晒χ螅覀兪遣皇沁€需要調(diào)用這個query方法呢,如何如調(diào)用呢? * 所以就出現(xiàn)了 Invocation 對象,它這個時候其實就是一個 Executor,而且 method 對應(yīng)的就是 query 方法,我們 * 想要調(diào)用這個方法,只需要執(zhí)行 invocation.proceed() */ /* 因為我攔截的就是Executor,所以我可以強轉(zhuǎn)為 Executor,默認情況下,這個Executor 是個 SimpleExecutor */ Executor executor = (Executor)invocation.getTarget(); /* * Executor 的 update 方法里面有一個參數(shù) 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); /* * 現(xiàn)在日志已經(jīng)打印,需要調(diào)用目標對象的方法完成 update 操作 * 我們直接調(diào)用 invocation.proceed() 方法 * 進入源碼其實就是一個常見的反射調(diào)用 method.invoke(target, args) * target 對應(yīng) Executor對象 * method 對應(yīng) Executor的update方法 * args 對應(yīng) Executor的update方法的參數(shù) */ return invocation.proceed(); } /** * 這個方法也很好理解 * 作用就只有一個:那就是Mybatis在創(chuàng)建攔截器代理時候會判斷一次,當前這個類 MyInterceptor 到底需不需要生成一個代理進行攔截, * 如果需要攔截,就生成一個代理對象,這個代理就是一個 {@link Plugin},它實現(xiàn)了jdk的動態(tài)代理接口 {@link InvocationHandler}, * 如果不需要代理,則直接返回目標對象本身 * * Mybatis為什么會判斷一次是否需要代理呢? * 默認情況下,Mybatis只能攔截四種類型的接口:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler * 通過 {@link Intercepts} 和 {@link Signature} 兩個注解共同完成 * 試想一下,如果我們開發(fā)人員在自定義攔截器上沒有指明類型,或者隨便寫一個攔截點,比如Object,那Mybatis瘋了,難道所有對象都去攔截 * 所以Mybatis會做一次判斷,攔截點看看是不是這四個接口里面的方法,不是則不攔截,直接返回目標對象,如果是則需要生成一個代理 * * 該方法在 mybatis 加載核心配置文件時被調(diào)用 */ @Override public Object plugin(Object target) { /* * 看了這個方法注釋,就應(yīng)該理解,這里的邏輯只有一個,就是讓mybatis判斷,要不要進行攔截,然后做出決定是否生成一個代理 * * 下面代碼什么鬼,就這一句就搞定了? * Mybatis判斷依據(jù)是利用反射,獲取這個攔截器 MyInterceptor 的注解 Intercepts和Signature,然后解析里面的值, * 1 先是判斷要攔截的對象是四個類型中 Executor、StatementHandler、ParameterHandler、 ResultSetHandler 的哪一個 * 2 然后根據(jù)方法名稱和參數(shù)(因為有重載)判斷對哪一個方法進行攔截 Note:mybatis可以攔截這四個接口里面的任一一個方法 * 3 做出決定,是返回一個對象呢還是返回目標對象本身(目標對象本身就是四個接口的實現(xiàn)類,我們攔截的就是這四個類型) * * 好了,理解邏輯我們寫代碼吧~~~ What !!! 要使用反射,然后解析注解,然后根據(jù)參數(shù)類型,最后還要生成一個代理對象 * 我一個小白我怎么會這么高大上的代碼嘛,怎么辦? * * 那就是使用下面這句代碼吧 哈哈 * mybatis 早就考慮了這里的復(fù)雜度,所以提供這個靜態(tài)方法來實現(xiàn)上面的邏輯 */ return Plugin.wrap(target, this); } /** * 這個方法最好理解,如果我們攔截器需要用到一些變量參數(shù),而且這個參數(shù)是支持可配置的, * 類似Spring中的@Value("${}")從application.properties文件獲取 * 這個時候我們就可以使用這個方法 * * 如何使用? * 只需要在 mybatis 配置文件中加入類似如下配置,然后 {@link Interceptor#setProperties(Properties)} 就可以獲取參數(shù) * <plugin interceptor="liu.york.mybatis.study.plugin.MyInterceptor"> * <property name="username" value="LiuYork"/> * <property name="password" value="123456"/> * </plugin> * 方法中獲取參數(shù):properties.getProperty("username"); * * 問題:為什么要存在這個方法呢,比如直接使用 @Value("${}") 獲取不就得了? * 原因是 mybatis 框架本身就是一個可以獨立使用的框架,沒有像 Spring 這種做了很多依賴注入的功能 * * 該方法在 mybatis 加載核心配置文件時被調(diào)用 */ @Override public void setProperties(Properties properties) { String username = properties.getProperty("username"); String password = properties.getProperty("password"); // TODO: 2019/2/28 業(yè)務(wù)邏輯處理... } }
三個核心方法都加了詳細的注釋,而且結(jié)合案例需求說明問題
那么多文字不想行看,沒關(guān)系有概括
總結(jié):
1.在mybatis中可被攔截的類型有四種(按照攔截順序)
Executor
:攔截執(zhí)行器的方法。ParameterHandler
:攔截參數(shù)的處理。ResultHandler
:攔截結(jié)果集的處理。StatementHandler
:攔截Sql語法構(gòu)建的處理。
2.各個參數(shù)的含義
@Intercepts
:標識該類是一個攔截器;@Signature
:指明自定義攔截器需要攔截哪一個類型,哪一個方法;
2.1 type:對應(yīng)四種類型中的一種;
2.2 method:對應(yīng)接口中的哪個方法;
2.3 args:對應(yīng)哪一個方法參數(shù)類型(因為可能存在重載方法);
接下來我們看看 Plugin 類
package org.apache.ibatis.plugin; /** * Plugin 類其實就是一個代理類,因為它實現(xiàn)了jdk動態(tài)代理接口 InvocationHandler * 我們核心只需要關(guān)注兩個方法 * wrap: * 如果看懂了代碼案例1的例子,那么這個方法很理解,這個方法就是 mybatis 提供給開發(fā)人員使用的一個工具類方法, * 目的就是幫助開發(fā)人員省略掉 反射解析注解 Intercepts 和 Signature,有興趣的可以去看看源碼 Plugin#getSignatureMap 方法 * * invoke: * 這個方法就是根據(jù) wrap 方法的解析結(jié)果,判斷當前攔截器是否需要進行攔截, * 如果需要攔截:將 目標對象+目標方法+目標參數(shù) 封裝成一個 Invocation 對象,給我們自定義的攔截器 MyInterceptor 的 intercept 方法 * 這個時候就剛好對應(yīng)上了上面案例1中對 intercept 方法的解釋了,它就是我們要處理自己邏輯的方法, * 處理好了之后是否需要調(diào)用目標對象的方法,比如上面說的 打印了sql語句,是否還要查詢數(shù)據(jù)庫呢?答案是肯定的 * 如果不需要攔截:則直接調(diào)用目標對象的方法 * 比如直接調(diào)用 Executor 的 update 方法進行更新數(shù)據(jù)庫 * */ class Plugin implements InvocationHandler { public static Object wrap(Object target, Interceptor interceptor) { // 省略 } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 省略 } }
貼一段網(wǎng)上的通用解釋吧:
- Plugin的wrap方法,它根據(jù)當前的Interceptor上面的注解定義哪些接口需要攔截,然后判斷當前目標對象是否有實現(xiàn)對應(yīng)需要攔截的接口,如果沒有則返回目標對象本身,如果有則返回一個代理對象。
- 而這個代理對象的InvocationHandler正是一個Plugin。所以當目標對象在執(zhí)行接口方法時,如果是通過代理對象執(zhí)行的,則會調(diào)用對應(yīng)InvocationHandler的invoke方法,也就是Plugin的invoke方法。
- 所以接著我們來看一下該invoke方法的內(nèi)容。
- 這里invoke方法的邏輯是:如果當前執(zhí)行的方法是定義好的需要攔截的方法,則把目標對象、要執(zhí)行的方法以及方法參數(shù)封裝成一個Invocation對象,再把封裝好的Invocation作為參數(shù)傳遞給當前攔截器的intercept方法。
- 如果不需要攔截,則直接調(diào)用當前的方法。
- Invocation中定義了定義了一個proceed方法,其邏輯就是調(diào)用當前方法,所以如果在intercept中需要繼續(xù)調(diào)用當前方法的話可以調(diào)用invocation的procced方法。
這就是Mybatis中實現(xiàn)Interceptor攔截的一個思想
2、在配置文件中添加攔截器
在springboot中要給mybatis加上這個攔截器,有三種方法,前兩種方法在啟動項目時不會自動調(diào)用自定義攔截器的setProperties方法。
攔截器順序
1、不同攔截器順序
Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
2、對于同一個類型的攔截器的不同對象攔截順序:
在 mybatis 核心配置文件根據(jù)配置的位置,攔截順序是 從上往下
(1)第一種
直接給自定義攔截器添加一個 @Component注解,當調(diào)用sql時結(jié)果如下,可以看到攔截器生效了,但是啟動時候并沒有自動調(diào)用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 { //業(yè)務(wù)代碼 } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // TODO Auto-generated method stub } }
(2)第二種
在配置類里添加攔截器,這種方法結(jié)果同上,也不會自動調(diào)用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被自動調(diào)用了
總結(jié):前兩種方法可以在初始化自定義攔截器的時候通過 @Value 注解直接初始化需要的參數(shù)。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java搭建一個Socket服務(wù)器響應(yīng)多用戶訪問
本篇文章主要介紹了java搭建一個Socket服務(wù)器響應(yīng)多用戶訪問,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02基于SpringBoot接口+Redis解決用戶重復(fù)提交問題
當網(wǎng)絡(luò)延遲的情況下用戶多次點擊submit按鈕導(dǎo)致表單重復(fù)提交,用戶提交表單后,點擊瀏覽器的【后退】按鈕回退到表單頁面后進行再次提交也會出現(xiàn)用戶重復(fù)提交,辦法有很多,我這里只說一種,利用Redis的set方法搞定,需要的朋友可以參考下2023-10-10Java方法重寫_動力節(jié)點Java學(xué)院整理
在Java和其他一些高級面向?qū)ο蟮木幊陶Z言中,子類可繼承父類中的方法,而不需要重新編寫相同的方法。但有時子類并不想原封不動地繼承父類的方法,而是想作一定的修改,這就需要采用方法的重寫。方法重寫又稱方法覆蓋,下文給大家介紹java方法重寫及重寫規(guī)則,一起學(xué)習(xí)吧2017-04-04Java使用@Validated注解進行參數(shù)驗證的方法
這篇文章主要介紹了Java使用@Validated注解進行參數(shù)驗證的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08web.xml詳解_動力節(jié)點Java學(xué)院整理
這篇文章給大家詳細介紹了web.xml的相關(guān)知識,需要的朋友可以參考下2017-07-07基于JAVA中Jersey處理Http協(xié)議中的Multipart的詳解
之前在基于C#開發(fā)彩信用最原始的StringBuilder拼接字符串方式處理過Multipart?,F(xiàn)在在做一個項目的時候,由于之前的技術(shù)路線都是使用Jersey處理Http這塊,為了保持技術(shù)路線一致,研究了一下如何使用Jersey處理Http協(xié)議中的Multipart2013-05-05Mybatis中一條SQL使用兩個foreach的問題及解決
這篇文章主要介紹了Mybatis中一條SQL使用兩個foreach的問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02