一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 通過源代碼分析Mybatis的功能流程詳解

通過源代碼分析Mybatis的功能流程詳解

2020-08-03 14:51逆流而上 Java教程

這篇文章主要介紹了通過源代碼分析Mybatis的功能,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

SQL解析

Mybatis在初始化的時候,會讀取xml中的SQL,解析后會生成SqlSource對象,SqlSource對象分為兩種。

  • DynamicSqlSource,動態SQL,獲取SQL(getBoundSQL方法中)的時候生成參數化SQL。
  • RawSqlSource,原始SQL,創建對象時直接生成參數化SQL。

因為RawSqlSource不會重復去生成參數化SQL,調用的時候直接傳入參數并執行,而DynamicSqlSource則是每次執行的時候參數化SQL,所以RawSqlSourceDynamicSqlSource的性能要好的。

解析的時候會先解析include標簽和selectkey標簽,然后判斷是否是動態SQL,判斷取決于以下兩個條件:

  • SQL中有動態拼接字符串,簡單來說就是是否使用了${}表達式。注意這種方式存在SQL注入,謹慎使用。
  • SQL中有trim、where、set、foreachifchoosewhen、otherwisebind標簽

相關代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
protected MixedSqlNode parseDynamicTags(XNode node) {
 // 創建 SqlNode 數組
 List<SqlNode> contents = new ArrayList<>();
 // 遍歷 SQL 節點的所有子節點
 NodeList children = node.getNode().getChildNodes();
 for (int i = 0; i < children.getLength(); i++) {
  // 當前子節點
  XNode child = node.newXNode(children.item(i));
  // 如果類型是 Node.CDATA_SECTION_NODE 或者 Node.TEXT_NODE 時
  if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
   // 獲得內容
   String data = child.getStringBody("");
   // 創建 TextSqlNode 對象
   TextSqlNode textSqlNode = new TextSqlNode(data);
   // 如果是動態的 TextSqlNode 對象(是否使用了${}表達式)
   if (textSqlNode.isDynamic()) {
    // 添加到 contents 中
    contents.add(textSqlNode);
    // 標記為動態 SQL
    isDynamic = true;
    // 如果是非動態的 TextSqlNode 對象
   } else {
    // 創建 StaticTextSqlNode 添加到 contents 中
    contents.add(new StaticTextSqlNode(data));
   }
   // 如果類型是 Node.ELEMENT_NODE,其實就是XMl中<where>等那些動態標簽
  } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
   // 根據子節點的標簽,獲得對應的 NodeHandler 對象
   String nodeName = child.getNode().getNodeName();
   NodeHandler handler = nodeHandlerMap.get(nodeName);
   if (handler == null) { // 獲得不到,說明是未知的標簽,拋出 BuilderException 異常
    throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
   }
   // 執行 NodeHandler 處理
   handler.handleNode(child, contents);
   // 標記為動態 SQL
   isDynamic = true;
  }
 }
 // 創建 MixedSqlNode 對象
 return new MixedSqlNode(contents);
}

參數解析

Mybais中用于解析Mapper方法的參數的類是ParamNameResolver,它主要做了這些事情:

  • 每個Mapper方法第一次運行時會去創建ParamNameResolver,之后會緩存
  • 創建時會根據方法簽名,解析出參數名,解析的規則順序是

如果參數類型是RowBounds或者ResultHandler類型或者他們的子類,則不處理。

如果參數中有Param注解,則使用Param中的值作為參數名

如果配置項useActualParamName=true,argn(n>=0)標作為參數名,如果你是Java8以上并且開啟了-parameters`,則是實際的參數名

如果配置項useActualParamName=false,則使用n(n>=0)作為參數名

相關源代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public ParamNameResolver(Configuration config, Method method) {
 final Class<?>[] paramTypes = method.getParameterTypes();
 final Annotation[][] paramAnnotations = method.getParameterAnnotations();
 final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
 int paramCount = paramAnnotations.length;
 // 獲取方法中每個參數在SQL中的參數名
 for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
  // 跳過RowBounds、ResultHandler類型
  if (isSpecialParameter(paramTypes[paramIndex])) {
   continue;
  }
  String name = null;
  // 遍歷參數上面的所有注解,如果有Param注解,使用它的值作為參數名
  for (Annotation annotation : paramAnnotations[paramIndex]) {
   if (annotation instanceof Param) {
    hasParamAnnotation = true;
    name = ((Param) annotation).value();
    break;
   }
  }
  // 如果沒有指定注解
  if (name == null) {
   // 如果開啟了useActualParamName配置,則參數名為argn(n>=0),如果是Java8以上并且開啟-parameters,則為實際的參數名
   if (config.isUseActualParamName()) {
    name = getActualParamName(method, paramIndex);
   }
   // 否則為下標
   if (name == null) {
    name = String.valueOf(map.size());
   }
  }
  map.put(paramIndex, name);
 }
 names = Collections.unmodifiableSortedMap(map);
}

而在使用這個names構建xml中參數對象和值的映射時,還進行了進一步的處理。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public Object getNamedParams(Object[] args) {
 final int paramCount = names.size();
 // 無參數,直接返回null
 if (args == null || paramCount == 0) {
  return null;
 } else if (!hasParamAnnotation && paramCount == 1) {
  // 一個參數,并且沒有注解,直接返回這個對象
  return args[names.firstKey()];
 } else {
  // 其他情況則返回一個Map對象
  final Map<String, Object> param = new ParamMap<Object>();
  int i = 0;
  for (Map.Entry<Integer, String> entry : names.entrySet()) {
   // 先直接放入name的鍵和對應位置的參數值,其實就是構造函數中存入的值
   param.put(entry.getValue(), args[entry.getKey()]);
   // add generic param names (param1, param2, ...)
   final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
   // 防止覆蓋 @Param 的參數值
   if (!names.containsValue(genericParamName)) {
    // 然后放入GENERIC_NAME_PREFIX + index + 1,其實就是param1,params2,paramn
    param.put(genericParamName, args[entry.getKey()]);
   }
   i++;
  }
  return param;
 }
}

另外值得一提的是,對于集合類型,最后還有一個特殊處理

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private Object wrapCollection(final Object object) {
 // 如果對象是集合屬性
 if (object instanceof Collection) {
  StrictMap<Object> map = new StrictMap<Object>();
  // 加入一個collection參數
  map.put("collection", object);
  // 如果是一個List集合
  if (object instanceof List) {
   // 額外加入一個list屬性使用
   map.put("list", object);
  }
  return map;
 } else if (object != null && object.getClass().isArray()) {
  // 數組使用array
  StrictMap<Object> map = new StrictMap<Object>();
  map.put("array", object);
  return map;
 }
 return object;
}

由此我們可以得出使用參數的結論:

  • 如果參數加了@Param注解,則使用注解的值作為參數
  • 如果只有一個參數,并且不是集合類型和數組,且沒有加注解,則使用對象的屬性名作為參數如果只有一個參數,并且是集合類型,則使用collection參數,如果是List對象,可以額外使用list參數。
  • 如果只有一個參數,并且是數組,則可以使用array參數如果有多個參數,沒有加@Param注解的可以使用argn或者n(n>=0,取決于useActualParamName配置項)作為參數,加了注解的使用注解的值。
  • 如果有多個參數,任意參數只要不是和@Param中的值覆蓋,都可以使用paramn(n>=1)

延遲加載

Mybatis是支持延遲加載的,具體的實現方式根據resultMap創建返回對象時,發現fetchType=“lazy”,則使用代理對象,默認使用Javassist(MyBatis 3.3 以上,可以修改為使用CgLib)。代碼處理邏輯在處理返回結果集時,具體代碼調用關系如下:

PreparedStatementHandler.query=> handleResultSets =>handleResultSet=>handleRowValues=>handleRowValuesForNestedResultMap=>getRowValue

getRowValue中,有一個方法createResultObject創建返回對象,其中的關鍵代碼創建了代理對象:

?
1
2
3
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}

另一方面,getRowValue會調用applyPropertyMappings方法,其內部會調用getPropertyMappingValue,繼續追蹤到getNestedQueryMappingValue方法,在這里,有幾行關鍵代碼:

?
1
2
3
4
5
6
7
8
9
10
// 如果要求延遲加載,則延遲加載
if (propertyMapping.isLazy()) {
 // 如果該屬性配置了延遲加載,則將其添加到 `ResultLoader.loaderMap` 中,等待真正使用時再執行嵌套查詢并得到結果對象。
 lazyLoader.addLoader(property, metaResultObject, resultLoader);
 // 返回已定義
 value = DEFERED;
 // 如果不要求延遲加載,則直接執行加載對應的值
} else {
 value = resultLoader.loadResult();
}

這幾行的目的是跳過屬性值的加載,等真正需要值的時候,再獲取值。

Executor

Executor是一個接口,其直接實現的類是BaseExecutorCachingExecutor,BaseExecutor又派生了BatchExecutorReuseExecutor、SimpleExecutor、ClosedExecutor。其繼承結構如圖:

通過源代碼分析Mybatis的功能流程詳解

其中ClosedExecutor是一個私有類,用戶不直接使用它。

  • BaseExecutor:模板類,里面有各個Executor的公用的方法。
  • SimpleExecutor:最常用的Executor,默認是使用它去連接數據庫,執行SQL語句,沒有特殊行為。ReuseExecutor:SQL語句執行后會進行緩存,不會關閉Statement,下次執行時會復用,緩存的key值是BoundSql解析后SQL,清空緩存使用doFlushStatements。其他與SimpleExecutor相同。
  • BatchExecutor:當有連續的Insert、Update、Delete的操作語句,并且語句的BoundSql相同,則這些語句會批量執行。使用doFlushStatements方法獲取批量操作的返回值。
  • CachingExecutor:當你開啟二級緩存的時候,會使用CachingExecutor裝飾SimpleExecutor、ReuseExecutorBatchExecutor,Mybatis通過CachingExecutor來實現二級緩存。

緩存

一級緩存

Mybatis一級緩存的實現主要是在BaseExecutor中,在它的查詢方法里,會優先查詢緩存中的值,如果不存在,再查詢數據庫,查詢部分的代碼如下,關鍵代碼在17-24行:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
 // 已經關閉,則拋出 ExecutorException 異常
 if (closed) {
  throw new ExecutorException("Executor was closed.");
 }
 // 清空本地緩存,如果 queryStack 為零,并且要求清空本地緩存。
 if (queryStack == 0 && ms.isFlushCacheRequired()) {
  clearLocalCache();
 }
 List<E> list;
 try {
  // queryStack + 1
  queryStack++;
  // 從一級緩存中,獲取查詢結果
  list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
  // 獲取到,則進行處理
  if (list != null) {
   handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
   // 獲得不到,則從數據庫中查詢
  } else {
   list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }
 } finally {
  // queryStack - 1
  queryStack--;
 }
 if (queryStack == 0) {
  // 執行延遲加載
  for (DeferredLoad deferredLoad : deferredLoads) {
   deferredLoad.load();
  }
  // issue #601
  // 清空 deferredLoads
  deferredLoads.clear();
  // 如果緩存級別是 LocalCacheScope.STATEMENT ,則進行清理
  if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
   // issue #482
   clearLocalCache();
  }
 }
 return list;
}

而在queryFromDatabase中,則會將查詢出來的結果放到緩存中。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 從數據庫中讀取操作
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 List<E> list;
 // 在緩存中,添加占位對象。此處的占位符,和延遲加載有關,可見 `DeferredLoad#canLoad()` 方法
 localCache.putObject(key, EXECUTION_PLACEHOLDER);
 try {
  // 執行讀操作
  list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
 } finally {
  // 從緩存中,移除占位對象
  localCache.removeObject(key);
 }
 // 添加到緩存中
 localCache.putObject(key, list);
 // 暫時忽略,存儲過程相關
 if (ms.getStatementType() == StatementType.CALLABLE) {
  localOutputParameterCache.putObject(key, parameter);
 }
 return list;
}

而一級緩存的Key,從方法的參數可以看出,與調用方法、參數、rowBounds分頁參數、最終生成的sql有關。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
 if (closed) {
  throw new ExecutorException("Executor was closed.");
 }
 // 創建 CacheKey 對象
 CacheKey cacheKey = new CacheKey();
 // 設置 id、offset、limit、sql 到 CacheKey 對象中
 cacheKey.update(ms.getId());
 cacheKey.update(rowBounds.getOffset());
 cacheKey.update(rowBounds.getLimit());
 cacheKey.update(boundSql.getSql());
 // 設置 ParameterMapping 數組的元素對應的每個 value 到 CacheKey 對象中
 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
 TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
 // mimic DefaultParameterHandler logic 這塊邏輯,和 DefaultParameterHandler 獲取 value 是一致的。
 for (ParameterMapping parameterMapping : parameterMappings) {
  if (parameterMapping.getMode() != ParameterMode.OUT) {
   Object value;
   String propertyName = parameterMapping.getProperty();
   if (boundSql.hasAdditionalParameter(propertyName)) {
    value = boundSql.getAdditionalParameter(propertyName);
   } else if (parameterObject == null) {
    value = null;
   } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    value = parameterObject;
   } else {
    MetaObject metaObject = configuration.newMetaObject(parameterObject);
    value = metaObject.getValue(propertyName);
   }
   cacheKey.update(value);
  }
 }
 // 設置 Environment.id 到 CacheKey 對象中
 if (configuration.getEnvironment() != null) {
  // issue #176
  cacheKey.update(configuration.getEnvironment().getId());
 }
 return cacheKey;
}

通過查看一級緩存類的實現,可以看出一級緩存是通過HashMap結構存儲的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * 一級緩存的實現類,部分源代碼
 */
public class PerpetualCache implements Cache {
 /**
  * 緩存容器
  */
 private Map<Object, Object> cache = new HashMap<>();
 
 @Override
 public void putObject(Object key, Object value) {
  cache.put(key, value);
 }
 
 @Override
 public Object getObject(Object key) {
  return cache.get(key);
 }
 
 @Override
 public Object removeObject(Object key) {
  return cache.remove(key);
 }
}

通過配置項,我們可以控制一級緩存的使用范圍,默認是Session級別的,也就是SqlSession的范圍內有效。也可以配制成Statement級別,當本次查詢結束后立即清除緩存。

當進行插入、更新、刪除操作時,也會在執行SQL之前清空以及緩存。

二級緩存

Mybatis二級緩存的實現是依靠CachingExecutor裝飾其他的Executor實現。原理是在查詢的時候先根據CacheKey查詢緩存中是否存在值,如果存在則返回緩存的值,沒有則查詢數據庫。

CachingExecutorquery方法中,就有緩存的使用:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
  throws SQLException {
 Cache cache = ms.getCache();
 if (cache != null) {
  // 如果需要清空緩存,則進行清空
  flushCacheIfRequired(ms);
  if (ms.isUseCache() && resultHandler == null) {
   // 暫時忽略,存儲過程相關
   ensureNoOutParams(ms, boundSql);
   @SuppressWarnings("unchecked")
   // 從二級緩存中,獲取結果
   List<E> list = (List<E>) tcm.getObject(cache, key);
   if (list == null) {
    // 如果不存在,則從數據庫中查詢
    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    // 緩存結果到二級緩存中
    tcm.putObject(cache, key, list); // issue #578 and #116
   }
   // 如果存在,則直接返回結果
   return list;
  }
 }
 // 不使用緩存,則從數據庫中查詢
 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

那么這個Cache是在哪里創建的呢?通過調用的追溯,可以找到它的創建:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Cache useNewCache(Class<? extends Cache> typeClass,
       Class<? extends Cache> evictionClass,
       Long flushInterval,
       Integer size,
       boolean readWrite,
       boolean blocking,
       Properties props) {
 // 創建 Cache 對象
 Cache cache = new CacheBuilder(currentNamespace)
  .implementation(valueOrDefault(typeClass, PerpetualCache.class))
  .addDecorator(valueOrDefault(evictionClass, LruCache.class))
  .clearInterval(flushInterval)
  .size(size)
  .readWrite(readWrite)
  .blocking(blocking)
  .properties(props)
  .build();
 // 添加到 configuration 的 caches 中
 configuration.addCache(cache);
 // 賦值給 currentCache
 currentCache = cache;
 return cache;
}

從方法的第一行可以看出,Cache對象的范圍是namespace,同一個namespace下的所有mapper方法共享Cache對象,也就是說,共享這個緩存。

另一個創建方法是通過CacheRef里面的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Cache useCacheRef(String namespace) {
 if (namespace == null) {
  throw new BuilderException("cache-ref element requires a namespace attribute.");
 }
 try {
  unresolvedCacheRef = true; // 標記未解決
  // 獲得 Cache 對象
  Cache cache = configuration.getCache(namespace);
  // 獲得不到,拋出 IncompleteElementException 異常
  if (cache == null) {
   throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
  }
  // 記錄當前 Cache 對象
  currentCache = cache;
  unresolvedCacheRef = false; // 標記已解決
  return cache;
 } catch (IllegalArgumentException e) {
  throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
 }
}

這里的話會通過CacheRef中的參數namespace,找到那個Cache對象,且這里使用了unresolvedCacheRef,因為Mapper文件的加載是有順序的,可能當前加載時引用的那個namespace的Mapper文件還沒有加載,所以用這個標記一下,延后加載。

二級緩存通過TransactionalCache來管理,內部使用的是一個HashMap。Key是Cache對象,默認的實現是PerpetualCache,一個namespace下共享這個對象。Value是另一個Cache的對象,默認實現是TransactionalCache,是前面那個Key值的裝飾器,擴展了事務方面的功能。

通過查看TransactionalCache的源碼我們可以知道,默認查詢后添加的緩存保存在待提交對象里。

?
1
2
3
4
public void putObject(Object key, Object object) {
 // 暫存 KV 到 entriesToAddOnCommit 中
 entriesToAddOnCommit.put(key, object);
}

只有等到commit的時候才會去刷入緩存。

?
1
2
3
4
5
6
7
8
9
10
public void commit() {
 // 如果 clearOnCommit 為 true ,則清空 delegate 緩存
 if (clearOnCommit) {
  delegate.clear();
 }
 // 將 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate 中
 flushPendingEntries();
 // 重置
 reset();
}

查看clear代碼,只是做了標記,并沒有真正釋放對象。在查詢時根據標記直接返回空,在commit才真正釋放對象:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void clear() {
 // 標記 clearOnCommit 為 true
 clearOnCommit = true;
 // 清空 entriesToAddOnCommit
 entriesToAddOnCommit.clear();
}
 
public Object getObject(Object key) {
 // issue #116
 // 從 delegate 中獲取 key 對應的 value
 Object object = delegate.getObject(key);
 // 如果不存在,則添加到 entriesMissedInCache 中
 if (object == null) {
  entriesMissedInCache.add(key);
 }
 // issue #146
 // 如果 clearOnCommit 為 true ,表示處于持續清空狀態,則返回 null
 if (clearOnCommit) {
  return null;
  // 返回 value
 } else {
  return object;
 }
}

rollback會清空這些臨時緩存:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void rollback() {
 // 從 delegate 移除出 entriesMissedInCache
 unlockMissedEntries();
 // 重置
 reset();
}
 
private void reset() {
 // 重置 clearOnCommit 為 false
 clearOnCommit = false;
 // 清空 entriesToAddOnCommit、entriesMissedInCache
 entriesToAddOnCommit.clear();
 entriesMissedInCache.clear();
}

根據二級緩存代碼可以看出,二級緩存是基于namespace的,可以跨SqlSession。也正是因為基于namespace,如果在不同的namespace中修改了同一個表的數據,會導致臟讀的問題。

插件

Mybatis的插件是通過代理對象實現的,可以代理的對象有:

  • Executor:執行器,執行器是執行過程中第一個代理對象,它內部調用StatementHandler返回SQL結果。
  • StatementHandler:語句處理器,執行SQL前調用ParameterHandler處理參數,執行SQL后調用ResultSetHandler處理返回結果
  • ParameterHandler:參數處理器
  • ResultSetHandler:返回對象處理器

這四個對象的接口的所有方法都可以用插件攔截。

插件的實現代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 創建 ParameterHandler 對象
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
 // 創建 ParameterHandler 對象
 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
 // 應用插件
 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
 return parameterHandler;
}
 
// 創建 ResultSetHandler 對象
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
           ResultHandler resultHandler, BoundSql boundSql) {
 // 創建 DefaultResultSetHandler 對象
 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
 // 應用插件
 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
 return resultSetHandler;
}
 
// 創建 StatementHandler 對象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 // 創建 RoutingStatementHandler 對象
 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
 // 應用插件
 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
 return statementHandler;
}
 
/**
  * 創建 Executor 對象
  *
  * @param transaction 事務對象
  * @param executorType 執行器類型
  * @return Executor 對象
  */
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
 // 獲得執行器類型
 executorType = executorType == null ? defaultExecutorType : executorType; // 使用默認
 executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 使用 ExecutorType.SIMPLE
 // 創建對應實現的 Executor 對象
 Executor executor;
 if (ExecutorType.BATCH == executorType) {
  executor = new BatchExecutor(this, transaction);
 } else if (ExecutorType.REUSE == executorType) {
  executor = new ReuseExecutor(this, transaction);
 } else {
  executor = new SimpleExecutor(this, transaction);
 }
 // 如果開啟緩存,創建 CachingExecutor 對象,進行包裝
 if (cacheEnabled) {
  executor = new CachingExecutor(executor);
 }
 // 應用插件
 executor = (Executor) interceptorChain.pluginAll(executor);
 return executor;
}

可以很明顯的看到,四個方法內都有interceptorChain.pluginAll()方法的調用,繼續查看這個方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 應用所有插件
 *
 * @param target 目標對象
 * @return 應用結果
 */
public Object pluginAll(Object target) {
 for (Interceptor interceptor : interceptors) {
  target = interceptor.plugin(target);
 }
 return target;
}

這個方法比較簡單,就是遍歷interceptors列表,然后調用器plugin方法。interceptors是在解析XML配置文件是通過反射創建的,而創建后會立即調用setProperties方法

我們通常配置插件時,會在interceptor.plugin調用Plugin.wrap,這里面通過Java的動態代理,攔截方法的實現:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * 創建目標類的代理對象
 *
 * @param target 目標類
 * @param interceptor 攔截器對象
 * @return 代理對象
 */
public static Object wrap(Object target, Interceptor interceptor) {
 // 獲得攔截的方法映射
 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
 // 獲得目標類的類型
 Class<?> type = target.getClass();
 // 獲得目標類的接口集合
 Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
 // 若有接口,則創建目標對象的 JDK Proxy 對象
 if (interfaces.length > 0) {
  return Proxy.newProxyInstance(
   type.getClassLoader(),
   interfaces,
   new Plugin(target, interceptor, signatureMap)); // 因為 Plugin 實現了 InvocationHandler 接口,所以可以作為 JDK 動態代理的調用處理器
 }
 // 如果沒有,則返回原始的目標對象
 return target;
}
 
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 try {
  // 獲得目標方法是否被攔截
  Set<Method> methods = signatureMap.get(method.getDeclaringClass());
  if (methods != null && methods.contains(method)) {
   // 如果是,則攔截處理該方法
   return interceptor.intercept(new Invocation(target, method, args));
  }
  // 如果不是,則調用原方法
  return method.invoke(target, args);
 } catch (Exception e) {
  throw ExceptionUtil.unwrapThrowable(e);
 }
}

而攔截的參數傳了Plugin對象,Plugin本身是實現了InvocationHandler接口,其invoke方法里面調用了interceptor.intercept,這個方法就是我們實現攔截處理的地方。

注意到里面有個getSignatureMap方法,這個方法實現的是查找我們自定義攔截器的注解,通過注解確定哪些方法需要被攔截:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
 // issue #251
 if (interceptsAnnotation == null) {
  throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
 }
 Signature[] sigs = interceptsAnnotation.value();
 Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
 for (Signature sig : sigs) {
  Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
  try {
   Method method = sig.type().getMethod(sig.method(), sig.args());
   methods.add(method);
  } catch (NoSuchMethodException e) {
   throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
  }
 }
 return signatureMap;
}

通過源代碼我們可以知道,創建一個插件需要做以下事情:

  • 創建一個類,實現Interceptor接口。
  • 這個類必須使用@Intercepts、@Signature來表明要攔截哪個對象的哪些方法。
  • 這個類的plugin方法中調用Plugin.wrap(target, this)。
  • (可選)這個類的setProperties方法設置一些參數。
  • XML中<plugins>節點配置<plugin interceptor="你的自定義類的全名稱"></plugin>。

可以在第三點中根據具體的業務情況不進行本次SQL操作的代理,畢竟動態代理還是有性能損耗的。

到此這篇關于通過源代碼分析Mybatis的功能的文章就介紹到這了,更多相關源代碼分析Mybatis內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.cnblogs.com/Weilence/p/13416986.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 俄罗斯性高清完整版 | 精品成人一区二区三区免费视频 | 欧美日韩在线观看区一二 | 成人嗯啊视频在线观看 | 青青草在线播放 | 国产精品最新资源网 | 青青网| 免费在线视频成人 | ady成人映画网站官网 | 亚洲大片免费观看 | 欧美影院一区二区 | 亚洲高清国产拍精品影院 | 搓光美女衣 | 日本在线你懂的 | 99撸| 成人午夜在线视频 | 97国产自拍| 国模孕妇季玥全部人体写真 | 村妇超级乱淫伦小说全集 | 日本高清全集免费观看 | 大学生特黄特色大片免费播放 | 催眠白丝舞蹈老师小说 | 草草视频在线免费观看 | 日韩无砖专区体验区 | 成人免费网址 | 农村妇女野战bbxxx农村妇女 | 手机看片1024日韩 | 日本a在线天堂 | 亚洲2017天堂色无码 | 调教小荡娃h | 全是女性放屁角色的手游 | 男女车车好快的车车免费网站 | 亚洲精品中文字幕第一区 | 国产成人亚洲综合a∨婷婷 国产成人亚洲精品乱码在线观看 | 91在线精品视频 | 91大片淫黄大片在线天堂 | 污网站免费观看在线高清 | 草草在线影院 | 久久亚洲精选 | 大伊香蕉在线精品不卡视频 | 亚洲色图15p|