mybatis多個plugins的執(zhí)行順序解析
一、前言
在mybatis官網(wǎng)中,有插件一說 mybatis plugins 如果同時有多個插件,那么他們的執(zhí)行順序是怎樣的?

二、準(zhǔn)備工作、代碼準(zhǔn)備
1、 項目結(jié)構(gòu)

2、TestDAO
public interface TestDAO {
Test selectById(Integer id);
default void testDefaultMethod(){
System.out.println("===調(diào)用接口中的默認(rèn)方法,用來驗證MapperProxy中的isDefaultMethod方法===");
}
}
3、Test
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Test {
private Integer id;
private String name;
}
4、ExamplePlugin
@Intercepts({@Signature(
type= Executor.class, method = "query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
@Signature(
type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}
),
@Signature(
type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}
),
@Signature(
type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}
)
})
public class ExamplePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("==== ExamplePlugin 開始搞事情:" + invocation.getMethod().getName() + " ====");
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
5、SecondExamplePlugin
@Intercepts({@Signature(
type= Executor.class, method = "query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
@Signature(
type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}
),
@Signature(
type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}
),
@Signature(
type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}
)
})
public class SecondExamplePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("==== SecondExamplePlugin 開始搞事情:" + invocation.getMethod().getName() + " ====");
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
6、Main
public class Main {
public static SqlSession getSqlSession() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory.openSession();
}
public static void main(String[] args) throws IOException {
TestDAO testDAO = getSqlSession().getMapper(TestDAO.class);
Test test = testDAO.selectById(1);
// testDAO.testDefaultMethod();
//類文件是緩存在java虛擬機中,我們將類文件打印到文件中,便于查看
// generateProxyFile("F:/TestDAOProxy.class");
}
private static void generateProxyFile(String path){
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", new Class<?>[]{TestDAO.class});
try(FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println("代理類class文件寫入成功");
} catch (Exception e) {
System.out.println("寫文件錯誤");
}
}
}
7、 TestMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.me.mybatis.dao.TestDAO">
<resultMap id="testMap" type="com.me.mybatis.domain.Test">
<result property="id" column="id" />
<result property="name" column="name" />
</resultMap>
<sql id="allColumn">
id, name
</sql>
<select id="selectById" resultMap="testMap">
SELECT <include refid="allColumn"/>
FROM test
WHERE id = #{id}
</select>
</mapper>
8、mybatis-confi.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>
<plugins>
<plugin interceptor="com.me.mybatis.plugin.ExamplePlugin">
<property name="someProperty" value="200" />
</plugin>
<plugin interceptor="com.me.mybatis.plugin.SecondExamplePlugin">
<property name="someProperty" value="200" />
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="NewPwd@123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/TestMapper.xml"/>
</mappers>
</configuration>
9、POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.me</groupId>
<artifactId>mybatis-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.14</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
</dependencies>
</project>
三、開始探索
1、運行結(jié)果
==== SecondExamplePlugin 開始搞事情:query ====
==== ExamplePlugin 開始搞事情:query ====
==== SecondExamplePlugin 開始搞事情:prepare ====
==== ExamplePlugin 開始搞事情:prepare ====
==== SecondExamplePlugin 開始搞事情:setParameters ====
==== ExamplePlugin 開始搞事情:setParameters ====
==== SecondExamplePlugin 開始搞事情:handleResultSets ====
==== ExamplePlugin 開始搞事情:handleResultSets ====
2、疑問:為什么是這樣的順序?
和我們在mybatis-config.xml文件中的順序相反,為什么?

3、注釋掉一個,我們從一個plugin開始debug,看看做了什么

4、如圖,在Configuration的四個方法newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor中打上斷點

5、debug Main類的main方法

6、我們發(fā)現(xiàn)在newExecutor中,被攔住了

這里的interceptorChain是什么東西?我們往上找一找,發(fā)現(xiàn)它是在Configuration類中new出來的。它等價于mybatis-config中的<plugins></plugins>

7、我們已經(jīng)知道interceptorChain是什么了,那么進入它的pluginAll方法


我們可以看到它是遍歷interceptors的plugin方法。而interceptors是ArrayList,是有序的。那么在配置文件中,哪個plugin在前,這里它就在前面
8、進入interceptor的plugin方法,發(fā)現(xiàn)我們來到了我們自己寫的ExamplePlugin類的plugin方法

9、它又繼續(xù)調(diào)用了Plugin的靜態(tài)方法wrap

1) 第一步獲取@Signature注解中的type和method,也就是我們在ExamplePlugin中使用的注解。

2)第二步,用動態(tài)代理,生成代理類。其中Plugin作為InvocationHandler

10、UML圖

最終Executor不再是原來的類,而是它的代理類。newStatementHandler方法和newResultSetHandler方法的流程,也差不多,最終也是生成代理類。
當(dāng)Executor、StatementHandler、ParameterHandler、ResultSetHandler執(zhí)行他們自己的方法時,實際上調(diào)用他們的代理類Plugin中的invoke方法。

也就是在interceptor.intercept(new Invocation(target, method, args));這一句中,回到了我們ExamplePlugin的intercept方法

整個流程中Executor的代理。(這里只拿Executor來舉例)

四、結(jié)論
上面只是代理一次,還記得pluginAll嗎?

多個interceptor呢?當(dāng)然是代理類又被代理了。

所以,后面的將會代理前面的,這也就是為什么SecondExamplePlugin先執(zhí)行的原因了——越外層的越先執(zhí)行嘛
多個插件的執(zhí)行順序已經(jīng)明了了,那么插件里面方法的執(zhí)行順序呢?
當(dāng)然是看這些方法什么時候被調(diào)用咯

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Java中Comparable和Comparator接口的區(qū)別
這篇文章主要介紹了詳解Java中Comparable和Comparator接口的區(qū)別的相關(guān)資料,希望通過本文大家能徹底掌握這部分內(nèi)容,需要的朋友可以參考下2017-09-09
JAVA SpringBoot jar程序 Systemctl生產(chǎn)環(huán)境部署方案
這篇文章主要介紹了JAVA SpringBoot jar程序 Systemctl生產(chǎn)環(huán)境部署方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03
詳解spring mvc 請求轉(zhuǎn)發(fā)和重定向
這篇文章主要介紹了詳解spring mvc 請求轉(zhuǎn)發(fā)和重定向,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02
MyBatis/mybatis-plus項目打印SQL的方法實現(xiàn)
SpringBoot項目中,經(jīng)常需要打印SQL語句及其參數(shù),本文就來介紹一下MyBatis/mybatis-plus項目打印SQL的方法實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-07-07
SpringSecurity實現(xiàn)權(quán)限認(rèn)證與授權(quán)的使用示例
本文主要介紹了SpringSecurity實現(xiàn)權(quán)限認(rèn)證與授權(quán)的使用示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11
java web SpringMVC后端傳json數(shù)據(jù)到前端頁面實例代碼
本篇文章主要介紹了java web SpringMVC后端傳json數(shù)據(jù)到前端頁面實例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03

