自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

建議收藏,MyBatis插件原理詳解

開發(fā) 前端
我將以 Executor 為例,分析 MyBatis 是如何為 Executor 實例植入插件的。Executor 實例是在開啟 SqlSession 時被創(chuàng)建的,因此,我們從源頭進行分析。先來看一下 SqlSession 開啟的過程。

 插件原理分析

mybatis插件涉及到的幾個類:

我將以 Executor 為例,分析 MyBatis 是如何為 Executor 實例植入插件的。Executor 實例是在開啟 SqlSession 時被創(chuàng)建的,因此,我們從源頭進行分析。先來看一下 SqlSession 開啟的過程。

  1. public SqlSession openSession() { 
  2.     return openSessionFromDataSource(configuration.getDefaultExecutorType(), nullfalse); 
  3.  
  4. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { 
  5.     Transaction tx = null
  6.     try { 
  7.         // 省略部分邏輯 
  8.          
  9.         // 創(chuàng)建 Executor 
  10.         final Executor executor = configuration.newExecutor(tx, execType); 
  11.         return new DefaultSqlSession(configuration, executor, autoCommit); 
  12.     }  
  13.     catch (Exception e) {...}  
  14.     finally {...} 

Executor 的創(chuàng)建過程封裝在 Configuration 中,我們跟進去看看看。

  1. // Configuration類中 
  2. public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 
  3.     executorType = executorType == null ? defaultExecutorType : executorType; 
  4.     executorType = executorType == null ? ExecutorType.SIMPLE : executorType; 
  5.     Executor executor; 
  6.      
  7.     // 根據(jù) executorType 創(chuàng)建相應的 Executor 實例 
  8.     if (ExecutorType.BATCH == executorType) {...}  
  9.     else if (ExecutorType.REUSE == executorType) {...}  
  10.     else { 
  11.         executor = new SimpleExecutor(this, transaction); 
  12.     } 
  13.     if (cacheEnabled) { 
  14.         executor = new CachingExecutor(executor); 
  15.     } 
  16.      
  17.     // 植入插件 
  18.     executor = (Executor) interceptorChain.pluginAll(executor); 
  19.     return executor; 

如上,newExecutor 方法在創(chuàng)建好 Executor 實例后,緊接著通過攔截器鏈 interceptorChain 為 Executor 實例植入代理邏輯。那下面我們看一下 InterceptorChain 的代碼是怎樣的。

  1. public class InterceptorChain { 
  2.     private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); 
  3.     public Object pluginAll(Object target) { 
  4.         // 遍歷攔截器集合 
  5.         for (Interceptor interceptor : interceptors) { 
  6.             // 調(diào)用攔截器的 plugin 方法植入相應的插件邏輯 
  7.             target = interceptor.plugin(target); 
  8.         } 
  9.         return target; 
  10.     } 
  11.     /** 添加插件實例到 interceptors 集合中 */ 
  12.     public void addInterceptor(Interceptor interceptor) { 
  13.         interceptors.add(interceptor); 
  14.     } 
  15.     /** 獲取插件列表 */ 
  16.     public List<Interceptor> getInterceptors() { 
  17.         return Collections.unmodifiableList(interceptors); 
  18.     } 

上面的for循環(huán)代表了只要是插件,都會以責任鏈的方式逐一執(zhí)行(別指望它能跳過某個節(jié)點),所謂插件,其實就類似于攔截器。

這里就用到了責任鏈設計模式,責任鏈設計模式就相當于我們在OA系統(tǒng)里發(fā)起審批,領導們一層一層進行審批。

以上是 InterceptorChain 的全部代碼,比較簡單。它的 pluginAll 方法會調(diào)用具體插件的 plugin 方法植入相應的插件邏輯。如果有多個插件,則會多次調(diào)用 plugin 方法,最終生成一個層層嵌套的代理類。形如下面:

當 Executor 的某個方法被調(diào)用的時候,插件邏輯會先行執(zhí)行。執(zhí)行順序由外而內(nèi),比如上圖的執(zhí)行順序為 plugin3 → plugin2 → Plugin1 → Executor。

plugin 方法是由具體的插件類實現(xiàn),不過該方法代碼一般比較固定,所以下面找個示例分析一下。

  1. // TianPlugin類 
  2. public Object plugin(Object target) { 
  3.     return Plugin.wrap(target, this); 
  4.  
  5. //Plugin 
  6. public static Object wrap(Object target, Interceptor interceptor) { 
  7.     /* 
  8.      * 獲取插件類 @Signature 注解內(nèi)容,并生成相應的映射結構。形如下面: 
  9.      * { 
  10.      *     Executor.class : [query, updatecommit], 
  11.      *     ParameterHandler.class : [getParameterObject, setParameters] 
  12.      * } 
  13.      */ 
  14.     Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); 
  15.     Class<?> type = target.getClass(); 
  16.     // 獲取目標類實現(xiàn)的接口 
  17.     Class<?>[] interfaces = getAllInterfaces(type, signatureMap); 
  18.     if (interfaces.length > 0) { 
  19.         // 通過 JDK 動態(tài)代理為目標類生成代理類 
  20.         return Proxy.newProxyInstance( 
  21.             type.getClassLoader(), 
  22.             interfaces, 
  23.             new Plugin(target, interceptor, signatureMap)); 
  24.     } 
  25.     return target; 

如上,plugin 方法在內(nèi)部調(diào)用了 Plugin 類的 wrap 方法,用于為目標對象生成代理。Plugin 類實現(xiàn)了 InvocationHandler 接口,因此它可以作為參數(shù)傳給 Proxy 的 newProxyInstance 方法。

到這里,關于插件植入的邏輯就分析完了。接下來,我們來看看插件邏輯是怎樣執(zhí)行的。

執(zhí)行插件邏輯

Plugin 實現(xiàn)了 InvocationHandler 接口,因此它的 invoke 方法會攔截所有的方法調(diào)用。invoke 方法會對所攔截的方法進行檢測,以決定是否執(zhí)行插件邏輯。該方法的邏輯如下:

  1. //在Plugin類中 
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  3.     try { 
  4.         /* 
  5.          * 獲取被攔截方法列表,比如: 
  6.          *    signatureMap.get(Executor.class),可能返回 [query, updatecommit
  7.          */ 
  8.         Set<Method> methods = signatureMap.get(method.getDeclaringClass()); 
  9.         // 檢測方法列表是否包含被攔截的方法 
  10.         if (methods != null && methods.contains(method)) { 
  11.             // 執(zhí)行插件邏輯 
  12.             return interceptor.intercept(new Invocation(target, method, args)); 
  13.         } 
  14.         // 執(zhí)行被攔截的方法 
  15.         return method.invoke(target, args); 
  16.     } catch (Exception e) { 
  17.         throw ExceptionUtil.unwrapThrowable(e); 
  18.     } 

invoke 方法的代碼比較少,邏輯不難理解。首先,invoke 方法會檢測被攔截方法是否配置在插件的 @Signature 注解中,若是,則執(zhí)行插件邏輯,否則執(zhí)行被攔截方法。插件邏輯封裝在 intercept 中,該方法的參數(shù)類型為 Invocation。Invocation 主要用于存儲目標類,方法以及方法參數(shù)列表。下面簡單看一下該類的定義。

  1. public class Invocation { 
  2.  
  3.     private final Object target; 
  4.     private final Method method; 
  5.     private final Object[] args; 
  6.  
  7.     public Invocation(Object target, Method method, Object[] args) { 
  8.         this.target = target; 
  9.         this.method = method; 
  10.         this.args = args; 
  11.     } 
  12.     // 省略部分代碼 
  13.     public Object proceed() throws InvocationTargetException, IllegalAccessException { 
  14.         //反射調(diào)用被攔截的方法 
  15.         return method.invoke(target, args); 
  16.     } 

關于插件的執(zhí)行邏輯就分析到這,整個過程不難理解,大家簡單看看即可。

自定義插件

下面為了讓大家更好的理解Mybatis的插件機制,我們來模擬一個慢sql監(jiān)控的插件。

 

  1. /** 
  2.  * 慢查詢sql 插件 
  3.  */ 
  4. @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) 
  5. public class SlowSqlPlugin implements Interceptor { 
  6.  
  7.     private long slowTime; 
  8.  
  9.     //攔截后需要處理的業(yè)務 
  10.     @Override 
  11.     public Object intercept(Invocation invocation) throws Throwable { 
  12.         //通過StatementHandler獲取執(zhí)行的sql 
  13.         StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); 
  14.         BoundSql boundSql = statementHandler.getBoundSql(); 
  15.         String sql = boundSql.getSql(); 
  16.  
  17.         long start = System.currentTimeMillis(); 
  18.         //結束攔截 
  19.         Object proceed = invocation.proceed(); 
  20.         long end = System.currentTimeMillis(); 
  21.         long f = end - start; 
  22.         System.out.println(sql); 
  23.         System.out.println("耗時=" + f); 
  24.         if (f > slowTime) { 
  25.             System.out.println("本次數(shù)據(jù)庫操作是慢查詢,sql是:"); 
  26.             System.out.println(sql); 
  27.         } 
  28.         return proceed; 
  29.     } 
  30.  
  31.     //獲取到攔截的對象,底層也是通過代理實現(xiàn)的,實際上是拿到一個目標代理對象 
  32.     @Override 
  33.     public Object plugin(Object target) { 
  34.         //觸發(fā)intercept方法 
  35.         return Plugin.wrap(target, this); 
  36.     } 
  37.  
  38.     //設置屬性 
  39.     @Override 
  40.     public void setProperties(Properties properties) { 
  41.         //獲取我們定義的慢sql的時間閾值slowTime 
  42.         this.slowTime = Long.parseLong(properties.getProperty("slowTime")); 
  43.     } 

然后把這個插件類注入到容器中。

然后我們來執(zhí)行查詢的方法。

耗時28秒的,大于我們定義的10毫秒,那這條SQL就是我們認為的慢SQL。

通過這個插件,我們就能很輕松的理解setProperties()方法是做什么的了。

回顧分頁插件

也是實現(xiàn)mybatis接口Interceptor。

  1. @SuppressWarnings({"rawtypes""unchecked"}) 
  2. @Intercepts( 
  3.     { 
  4.         @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), 
  5.         @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}), 
  6.     } 
  7. public class PageInterceptor implements Interceptor { 
  8.         @Override 
  9.     public Object intercept(Invocation invocation) throws Throwable { 
  10.         ... 
  11.     } 

intercept方法中

  1. //AbstractHelperDialect類中 
  2. @Override 
  3. public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) { 
  4.         String sql = boundSql.getSql(); 
  5.         Page page = getLocalPage(); 
  6.         //支持 order by 
  7.         String orderBy = page.getOrderBy(); 
  8.         if (StringUtil.isNotEmpty(orderBy)) { 
  9.             pageKey.update(orderBy); 
  10.             sql = OrderByParser.converToOrderBySql(sql, orderBy); 
  11.         } 
  12.         if (page.isOrderByOnly()) { 
  13.             return sql; 
  14.         } 
  15.         //獲取分頁sql 
  16.         return getPageSql(sql, page, pageKey); 
  17.  } 
  18. //模板方法模式中的鉤子方法 
  19.  public abstract String getPageSql(String sql, Page page, CacheKey pageKey); 

AbstractHelperDialect類的實現(xiàn)類有如下(也就是此分頁插件支持的數(shù)據(jù)庫就以下幾種):

我們用的是MySQL。這里也有與之對應的。

  1. @Override 
  2.    public String getPageSql(String sql, Page page, CacheKey pageKey) { 
  3.        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); 
  4.        sqlBuilder.append(sql); 
  5.        if (page.getStartRow() == 0) { 
  6.            sqlBuilder.append(" LIMIT ? "); 
  7.        } else { 
  8.            sqlBuilder.append(" LIMIT ?, ? "); 
  9.        } 
  10.        pageKey.update(page.getPageSize()); 
  11.        return sqlBuilder.toString(); 
  12.    } 

到這里我們就知道了,它無非就是在我們執(zhí)行的SQL上再拼接了Limit罷了。同理,Oracle也就是使用rownum來處理分頁了。下面是Oracle處理分頁

  1. @Override 
  2.     public String getPageSql(String sql, Page page, CacheKey pageKey) { 
  3.         StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120); 
  4.         if (page.getStartRow() > 0) { 
  5.             sqlBuilder.append("SELECT * FROM ( "); 
  6.         } 
  7.         if (page.getEndRow() > 0) { 
  8.             sqlBuilder.append(" SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM ( "); 
  9.         } 
  10.         sqlBuilder.append(sql); 
  11.         if (page.getEndRow() > 0) { 
  12.             sqlBuilder.append(" ) TMP_PAGE WHERE ROWNUM <= ? "); 
  13.         } 
  14.         if (page.getStartRow() > 0) { 
  15.             sqlBuilder.append(" ) WHERE ROW_ID > ? "); 
  16.         } 
  17.         return sqlBuilder.toString(); 
  18.     } 

其他數(shù)據(jù)庫分頁操作類似。關于具體原理分析,這里就沒必要贅述了,因為分頁插件源代碼里注釋基本上全是中文。

Mybatis插件應用場景

  • 水平分表
  • 權限控制
  • 數(shù)據(jù)的加解密

總結

Spring-Boot+Mybatis繼承了分頁插件,以及使用案例、插件的原理分析、源碼分析、如何自定義插件。

涉及到技術點:JDK動態(tài)代理、責任鏈設計模式、模板方法模式。

Mybatis插件關鍵對象總結:

  • Inteceptor接口:自定義攔截必須實現(xiàn)的類。
  • InterceptorChain:存放插件的容器。
  • Plugin:h對象,提供創(chuàng)建代理類的方法。
  • Invocation:對被代理對象的封裝。

本文轉載自微信公眾號「Java后端技術全?!梗梢酝ㄟ^以下二維碼關注。轉載本文請聯(lián)系Java后端技術全棧公眾號。

 

責任編輯:武曉燕 來源: Java后端技術全棧
相關推薦

2022-03-24 07:38:07

注解SpringBoot項目

2021-10-26 11:45:22

Vue面試前端

2019-11-25 16:05:20

MybatisPageHelpeJava

2024-12-04 15:55:19

2021-01-26 09:25:02

Nginx開源軟件服務器

2019-09-02 14:53:53

JVM內(nèi)存布局GC

2022-08-24 11:54:10

Pandas可視化

2021-05-27 05:34:22

Git開源控制系統(tǒng)

2020-12-17 08:02:42

MyBatis插件框架

2013-07-16 15:35:54

Eclipse插件Android開發(fā)學習

2023-04-28 08:30:56

MyBatis架構API

2020-12-09 16:57:15

數(shù)據(jù)分析大數(shù)據(jù)

2022-05-23 10:55:19

華為數(shù)字化轉型架構藍圖

2013-05-27 15:07:36

Eclipse插件

2022-05-18 11:35:17

Python字符串

2021-10-12 13:35:30

C++Set紅黑樹

2012-05-10 13:45:45

jQuery

2025-02-26 08:50:00

2019-09-03 10:55:20

Python函數(shù)lambad

2022-07-20 00:15:48

SQL數(shù)據(jù)庫編程語言
點贊
收藏

51CTO技術棧公眾號