一、前言
在mybatis官網中,有插件一說 mybatis plugins . 如果同時有多個插件,那麼他們的執行順序是怎樣的?
二、準備工作、代碼準備
1、 項目結構
2、TestDAO
public interface TestDAO {
Test selectById(Integer id);
default void testDefaultMethod(){
System.out.println("===調用接口中的默認方法,用來驗證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、運行結果
==== 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、我們發現在newExecutor中,被攔住了
這裏的interceptorChain是什麼東西?我們往上找一找,發現它是在Configuration類中new出來的。它等價於mybatis-config中的<plugins></plugins>
7、我們已經知道interceptorChain是什麼了,那麼進入它的pluginAll方法
我們可以看到它是遍歷interceptors的plugin方法。而interceptors是ArrayList,是有序的。那麼在配置文件中,哪個plugin在前,這裏它就在前面
8、進入interceptor的plugin方法,發現我們來到了我們自己寫的ExamplePlugin類的plugin方法
9、它又繼續調用了Plugin的靜態方法wrap
1) 第一步獲取@Signature註解中的type和method,也就是我們在ExamplePlugin中使用的註解。
2)第二步,用動態代理,生成代理類。其中Plugin作爲InvocationHandler
10、UML圖
最終Executor不再是原來的類,而是它的代理類。newStatementHandler方法和newResultSetHandler方法的流程,也差不多,最終也是生成代理類。
當Executor、StatementHandler、ParameterHandler、ResultSetHandler執行他們自己的方法時,實際上調用他們的代理類Plugin中的invoke方法。
也就是在interceptor.intercept(new Invocation(target, method, args));這一句中,回到了我們ExamplePlugin的intercept方法
整個流程中Executor的代理。(這裏只拿Executor來舉例)
四、結論
上面只是代理一次,還記得pluginAll嗎?
多個interceptor呢?當然是代理類又被代理了。
所以,後面的將會代理前面的,這也就是爲什麼SecondExamplePlugin先執行的原因了——越外層的越先執行嘛
多個插件的執行順序已經明瞭了,那麼插件裏面方法的執行順序呢?
當然是看這些方法什麼時候被調用咯