一、前言
在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先執行的原因了――越外層的越先執行嘛
多個插件的執行順序已經明了了,那么插件里面方法的執行順序呢?
當然是看這些方法什么時候被調用咯
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/qq_18433441/article/details/84036317