通過源代碼分析Mybatis的功能流程詳解
SQL解析
Mybatis在初始化的時候,會讀取xml中的SQL,解析后會生成SqlSource對象,SqlSource對象分為兩種。
DynamicSqlSource,動態(tài)SQL,獲取SQL(getBoundSQL方法中)的時候生成參數(shù)化SQL。RawSqlSource,原始SQL,創(chuàng)建對象時直接生成參數(shù)化SQL。
因?yàn)?code>RawSqlSource不會重復(fù)去生成參數(shù)化SQL,調(diào)用的時候直接傳入?yún)?shù)并執(zhí)行,而DynamicSqlSource則是每次執(zhí)行的時候參數(shù)化SQL,所以RawSqlSource是DynamicSqlSource的性能要好的。
解析的時候會先解析include標(biāo)簽和selectkey標(biāo)簽,然后判斷是否是動態(tài)SQL,判斷取決于以下兩個條件:
- SQL中有動態(tài)拼接字符串,簡單來說就是是否使用了
${}表達(dá)式。注意這種方式存在SQL注入,謹(jǐn)慎使用。 - SQL中有
trim、where、set、foreach、if、choose、when、otherwise、bind標(biāo)簽
相關(guān)代碼如下:
protected MixedSqlNode parseDynamicTags(XNode node) {
// 創(chuàng)建 SqlNode 數(shù)組
List<SqlNode> contents = new ArrayList<>();
// 遍歷 SQL 節(jié)點(diǎn)的所有子節(jié)點(diǎn)
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
// 當(dāng)前子節(jié)點(diǎn)
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) {
// 獲得內(nèi)容
String data = child.getStringBody("");
// 創(chuàng)建 TextSqlNode 對象
TextSqlNode textSqlNode = new TextSqlNode(data);
// 如果是動態(tài)的 TextSqlNode 對象(是否使用了${}表達(dá)式)
if (textSqlNode.isDynamic()) {
// 添加到 contents 中
contents.add(textSqlNode);
// 標(biāo)記為動態(tài) SQL
isDynamic = true;
// 如果是非動態(tài)的 TextSqlNode 對象
} else {
// 創(chuàng)建 StaticTextSqlNode 添加到 contents 中
contents.add(new StaticTextSqlNode(data));
}
// 如果類型是 Node.ELEMENT_NODE,其實(shí)就是XMl中<where>等那些動態(tài)標(biāo)簽
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
// 根據(jù)子節(jié)點(diǎn)的標(biāo)簽,獲得對應(yīng)的 NodeHandler 對象
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) { // 獲得不到,說明是未知的標(biāo)簽,拋出 BuilderException 異常
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
// 執(zhí)行 NodeHandler 處理
handler.handleNode(child, contents);
// 標(biāo)記為動態(tài) SQL
isDynamic = true;
}
}
// 創(chuàng)建 MixedSqlNode 對象
return new MixedSqlNode(contents);
}
參數(shù)解析
Mybais中用于解析Mapper方法的參數(shù)的類是ParamNameResolver,它主要做了這些事情:
- 每個Mapper方法第一次運(yùn)行時會去創(chuàng)建
ParamNameResolver,之后會緩存 - 創(chuàng)建時會根據(jù)方法簽名,解析出參數(shù)名,解析的規(guī)則順序是
如果參數(shù)類型是RowBounds或者ResultHandler類型或者他們的子類,則不處理。
如果參數(shù)中有Param注解,則使用Param中的值作為參數(shù)名
如果配置項(xiàng)useActualParamName=true,argn(n>=0)標(biāo)作為參數(shù)名,如果你是Java8以上并且開啟了-parameters`,則是實(shí)際的參數(shù)名
如果配置項(xiàng)useActualParamName=false,則使用n(n>=0)作為參數(shù)名
相關(guān)源代碼:
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;
// 獲取方法中每個參數(shù)在SQL中的參數(shù)名
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 跳過RowBounds、ResultHandler類型
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
String name = null;
// 遍歷參數(shù)上面的所有注解,如果有Param注解,使用它的值作為參數(shù)名
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
// 如果沒有指定注解
if (name == null) {
// 如果開啟了useActualParamName配置,則參數(shù)名為argn(n>=0),如果是Java8以上并且開啟-parameters,則為實(shí)際的參數(shù)名
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
// 否則為下標(biāo)
if (name == null) {
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
而在使用這個names構(gòu)建xml中參數(shù)對象和值的映射時,還進(jìn)行了進(jìn)一步的處理。
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
// 無參數(shù),直接返回null
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 一個參數(shù),并且沒有注解,直接返回這個對象
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的鍵和對應(yīng)位置的參數(shù)值,其實(shí)就是構(gòu)造函數(shù)中存入的值
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// 防止覆蓋 @Param 的參數(shù)值
if (!names.containsValue(genericParamName)) {
// 然后放入GENERIC_NAME_PREFIX + index + 1,其實(shí)就是param1,params2,paramn
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
另外值得一提的是,對于集合類型,最后還有一個特殊處理
private Object wrapCollection(final Object object) {
// 如果對象是集合屬性
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
// 加入一個collection參數(shù)
map.put("collection", object);
// 如果是一個List集合
if (object instanceof List) {
// 額外加入一個list屬性使用
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
// 數(shù)組使用array
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
由此我們可以得出使用參數(shù)的結(jié)論:
- 如果參數(shù)加了
@Param注解,則使用注解的值作為參數(shù) - 如果只有一個參數(shù),并且不是集合類型和數(shù)組,且沒有加注解,則使用對象的屬性名作為參數(shù)如果只有一個參數(shù),并且是集合類型,則使用
collection參數(shù),如果是List對象,可以額外使用list參數(shù)。 - 如果只有一個參數(shù),并且是數(shù)組,則可以使用
array參數(shù)如果有多個參數(shù),沒有加@Param注解的可以使用argn或者n(n>=0,取決于useActualParamName配置項(xiàng))作為參數(shù),加了注解的使用注解的值。 - 如果有多個參數(shù),任意參數(shù)只要不是和
@Param中的值覆蓋,都可以使用paramn(n>=1)
延遲加載
Mybatis是支持延遲加載的,具體的實(shí)現(xiàn)方式根據(jù)resultMap創(chuàng)建返回對象時,發(fā)現(xiàn)fetchType=“l(fā)azy”,則使用代理對象,默認(rèn)使用Javassist(MyBatis 3.3 以上,可以修改為使用CgLib)。代碼處理邏輯在處理返回結(jié)果集時,具體代碼調(diào)用關(guān)系如下:
PreparedStatementHandler.query=> handleResultSets =>handleResultSet=>handleRowValues=>handleRowValuesForNestedResultMap=>getRowValue
在getRowValue中,有一個方法createResultObject創(chuàng)建返回對象,其中的關(guān)鍵代碼創(chuàng)建了代理對象:
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}
另一方面,getRowValue會調(diào)用applyPropertyMappings方法,其內(nèi)部會調(diào)用getPropertyMappingValue,繼續(xù)追蹤到getNestedQueryMappingValue方法,在這里,有幾行關(guān)鍵代碼:
// 如果要求延遲加載,則延遲加載
if (propertyMapping.isLazy()) {
// 如果該屬性配置了延遲加載,則將其添加到 `ResultLoader.loaderMap` 中,等待真正使用時再執(zhí)行嵌套查詢并得到結(jié)果對象。
lazyLoader.addLoader(property, metaResultObject, resultLoader);
// 返回已定義
value = DEFERED;
// 如果不要求延遲加載,則直接執(zhí)行加載對應(yīng)的值
} else {
value = resultLoader.loadResult();
}
這幾行的目的是跳過屬性值的加載,等真正需要值的時候,再獲取值。
Executor
Executor是一個接口,其直接實(shí)現(xiàn)的類是BaseExecutor和CachingExecutor,BaseExecutor又派生了BatchExecutor、ReuseExecutor、SimpleExecutor、ClosedExecutor。其繼承結(jié)構(gòu)如圖:

其中ClosedExecutor是一個私有類,用戶不直接使用它。
BaseExecutor:模板類,里面有各個Executor的公用的方法。SimpleExecutor:最常用的Executor,默認(rèn)是使用它去連接數(shù)據(jù)庫,執(zhí)行SQL語句,沒有特殊行為。ReuseExecutor:SQL語句執(zhí)行后會進(jìn)行緩存,不會關(guān)閉Statement,下次執(zhí)行時會復(fù)用,緩存的key值是BoundSql解析后SQL,清空緩存使用doFlushStatements。其他與SimpleExecutor相同。BatchExecutor:當(dāng)有連續(xù)的Insert、Update、Delete的操作語句,并且語句的BoundSql相同,則這些語句會批量執(zhí)行。使用doFlushStatements方法獲取批量操作的返回值。CachingExecutor:當(dāng)你開啟二級緩存的時候,會使用CachingExecutor裝飾SimpleExecutor、ReuseExecutor和BatchExecutor,Mybatis通過CachingExecutor來實(shí)現(xiàn)二級緩存。
緩存
一級緩存
Mybatis一級緩存的實(shí)現(xiàn)主要是在BaseExecutor中,在它的查詢方法里,會優(yōu)先查詢緩存中的值,如果不存在,再查詢數(shù)據(jù)庫,查詢部分的代碼如下,關(guān)鍵代碼在17-24行:
@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());
// 已經(jīng)關(guān)閉,則拋出 ExecutorException 異常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空本地緩存,如果 queryStack 為零,并且要求清空本地緩存。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// queryStack + 1
queryStack++;
// 從一級緩存中,獲取查詢結(jié)果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
// 獲取到,則進(jìn)行處理
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
// 獲得不到,則從數(shù)據(jù)庫中查詢
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// queryStack - 1
queryStack--;
}
if (queryStack == 0) {
// 執(zhí)行延遲加載
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
// 清空 deferredLoads
deferredLoads.clear();
// 如果緩存級別是 LocalCacheScope.STATEMENT ,則進(jìn)行清理
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
而在queryFromDatabase中,則會將查詢出來的結(jié)果放到緩存中。
// 從數(shù)據(jù)庫中讀取操作
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 在緩存中,添加占位對象。此處的占位符,和延遲加載有關(guān),可見 `DeferredLoad#canLoad()` 方法
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 執(zhí)行讀操作
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 從緩存中,移除占位對象
localCache.removeObject(key);
}
// 添加到緩存中
localCache.putObject(key, list);
// 暫時忽略,存儲過程相關(guān)
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
而一級緩存的Key,從方法的參數(shù)可以看出,與調(diào)用方法、參數(shù)、rowBounds分頁參數(shù)、最終生成的sql有關(guān)。
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 創(chuàng)建 CacheKey 對象
CacheKey cacheKey = new CacheKey();
// 設(shè)置 id、offset、limit、sql 到 CacheKey 對象中
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
// 設(shè)置 ParameterMapping 數(shù)組的元素對應(yīng)的每個 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);
}
}
// 設(shè)置 Environment.id 到 CacheKey 對象中
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
通過查看一級緩存類的實(shí)現(xiàn),可以看出一級緩存是通過HashMap結(jié)構(gòu)存儲的:
/**
* 一級緩存的實(shí)現(xiàn)類,部分源代碼
*/
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);
}
}
通過配置項(xiàng),我們可以控制一級緩存的使用范圍,默認(rèn)是Session級別的,也就是SqlSession的范圍內(nèi)有效。也可以配制成Statement級別,當(dāng)本次查詢結(jié)束后立即清除緩存。
當(dāng)進(jìn)行插入、更新、刪除操作時,也會在執(zhí)行SQL之前清空以及緩存。
二級緩存
Mybatis二級緩存的實(shí)現(xiàn)是依靠CachingExecutor裝飾其他的Executor實(shí)現(xiàn)。原理是在查詢的時候先根據(jù)CacheKey查詢緩存中是否存在值,如果存在則返回緩存的值,沒有則查詢數(shù)據(jù)庫。
在CachingExecutor中query方法中,就有緩存的使用:
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) {
// 如果需要清空緩存,則進(jìn)行清空
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
// 暫時忽略,存儲過程相關(guān)
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 從二級緩存中,獲取結(jié)果
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 如果不存在,則從數(shù)據(jù)庫中查詢
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 緩存結(jié)果到二級緩存中
tcm.putObject(cache, key, list); // issue #578 and #116
}
// 如果存在,則直接返回結(jié)果
return list;
}
}
// 不使用緩存,則從數(shù)據(jù)庫中查詢
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
那么這個Cache是在哪里創(chuàng)建的呢?通過調(diào)用的追溯,可以找到它的創(chuàng)建:
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
// 創(chuàng)建 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對象,也就是說,共享這個緩存。
另一個創(chuàng)建方法是通過CacheRef里面的:
public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true; // 標(biāo)記未解決
// 獲得 Cache 對象
Cache cache = configuration.getCache(namespace);
// 獲得不到,拋出 IncompleteElementException 異常
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
// 記錄當(dāng)前 Cache 對象
currentCache = cache;
unresolvedCacheRef = false; // 標(biāo)記已解決
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
這里的話會通過CacheRef中的參數(shù)namespace,找到那個Cache對象,且這里使用了unresolvedCacheRef,因?yàn)镸apper文件的加載是有順序的,可能當(dāng)前加載時引用的那個namespace的Mapper文件還沒有加載,所以用這個標(biāo)記一下,延后加載。
二級緩存通過TransactionalCache來管理,內(nèi)部使用的是一個HashMap。Key是Cache對象,默認(rèn)的實(shí)現(xiàn)是PerpetualCache,一個namespace下共享這個對象。Value是另一個Cache的對象,默認(rèn)實(shí)現(xiàn)是TransactionalCache,是前面那個Key值的裝飾器,擴(kuò)展了事務(wù)方面的功能。
通過查看TransactionalCache的源碼我們可以知道,默認(rèn)查詢后添加的緩存保存在待提交對象里。
public void putObject(Object key, Object object) {
// 暫存 KV 到 entriesToAddOnCommit 中
entriesToAddOnCommit.put(key, object);
}
只有等到commit的時候才會去刷入緩存。
public void commit() {
// 如果 clearOnCommit 為 true ,則清空 delegate 緩存
if (clearOnCommit) {
delegate.clear();
}
// 將 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate 中
flushPendingEntries();
// 重置
reset();
}
查看clear代碼,只是做了標(biāo)記,并沒有真正釋放對象。在查詢時根據(jù)標(biāo)記直接返回空,在commit才真正釋放對象:
public void clear() {
// 標(biāo)記 clearOnCommit 為 true
clearOnCommit = true;
// 清空 entriesToAddOnCommit
entriesToAddOnCommit.clear();
}
public Object getObject(Object key) {
// issue #116
// 從 delegate 中獲取 key 對應(yīng)的 value
Object object = delegate.getObject(key);
// 如果不存在,則添加到 entriesMissedInCache 中
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
// 如果 clearOnCommit 為 true ,表示處于持續(xù)清空狀態(tài),則返回 null
if (clearOnCommit) {
return null;
// 返回 value
} else {
return object;
}
}
rollback會清空這些臨時緩存:
public void rollback() {
// 從 delegate 移除出 entriesMissedInCache
unlockMissedEntries();
// 重置
reset();
}
private void reset() {
// 重置 clearOnCommit 為 false
clearOnCommit = false;
// 清空 entriesToAddOnCommit、entriesMissedInCache
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
根據(jù)二級緩存代碼可以看出,二級緩存是基于namespace的,可以跨SqlSession。也正是因?yàn)榛?code>namespace,如果在不同的namespace中修改了同一個表的數(shù)據(jù),會導(dǎo)致臟讀的問題。
插件
Mybatis的插件是通過代理對象實(shí)現(xiàn)的,可以代理的對象有:
Executor:執(zhí)行器,執(zhí)行器是執(zhí)行過程中第一個代理對象,它內(nèi)部調(diào)用StatementHandler返回SQL結(jié)果。StatementHandler:語句處理器,執(zhí)行SQL前調(diào)用ParameterHandler處理參數(shù),執(zhí)行SQL后調(diào)用ResultSetHandler處理返回結(jié)果ParameterHandler:參數(shù)處理器ResultSetHandler:返回對象處理器
這四個對象的接口的所有方法都可以用插件攔截。
插件的實(shí)現(xiàn)代碼如下:
// 創(chuàng)建 ParameterHandler 對象
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
// 創(chuàng)建 ParameterHandler 對象
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 應(yīng)用插件
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
// 創(chuàng)建 ResultSetHandler 對象
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
// 創(chuàng)建 DefaultResultSetHandler 對象
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 應(yīng)用插件
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
// 創(chuàng)建 StatementHandler 對象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 創(chuàng)建 RoutingStatementHandler 對象
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 應(yīng)用插件
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
/**
* 創(chuàng)建 Executor 對象
*
* @param transaction 事務(wù)對象
* @param executorType 執(zhí)行器類型
* @return Executor 對象
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 獲得執(zhí)行器類型
executorType = executorType == null ? defaultExecutorType : executorType; // 使用默認(rèn)
executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 使用 ExecutorType.SIMPLE
// 創(chuàng)建對應(yīng)實(shí)現(xiàn)的 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);
}
// 如果開啟緩存,創(chuàng)建 CachingExecutor 對象,進(jìn)行包裝
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 應(yīng)用插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
可以很明顯的看到,四個方法內(nèi)都有interceptorChain.pluginAll()方法的調(diào)用,繼續(xù)查看這個方法:
/**
* 應(yīng)用所有插件
*
* @param target 目標(biāo)對象
* @return 應(yīng)用結(jié)果
*/
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
這個方法比較簡單,就是遍歷interceptors列表,然后調(diào)用器plugin方法。interceptors是在解析XML配置文件是通過反射創(chuàng)建的,而創(chuàng)建后會立即調(diào)用setProperties方法
我們通常配置插件時,會在interceptor.plugin調(diào)用Plugin.wrap,這里面通過Java的動態(tài)代理,攔截方法的實(shí)現(xiàn):
/**
* 創(chuàng)建目標(biāo)類的代理對象
*
* @param target 目標(biāo)類
* @param interceptor 攔截器對象
* @return 代理對象
*/
public static Object wrap(Object target, Interceptor interceptor) {
// 獲得攔截的方法映射
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 獲得目標(biāo)類的類型
Class<?> type = target.getClass();
// 獲得目標(biāo)類的接口集合
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 若有接口,則創(chuàng)建目標(biāo)對象的 JDK Proxy 對象
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap)); // 因?yàn)?Plugin 實(shí)現(xiàn)了 InvocationHandler 接口,所以可以作為 JDK 動態(tài)代理的調(diào)用處理器
}
// 如果沒有,則返回原始的目標(biāo)對象
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 獲得目標(biāo)方法是否被攔截
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 如果是,則攔截處理該方法
return interceptor.intercept(new Invocation(target, method, args));
}
// 如果不是,則調(diào)用原方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
而攔截的參數(shù)傳了Plugin對象,Plugin本身是實(shí)現(xiàn)了InvocationHandler接口,其invoke方法里面調(diào)用了interceptor.intercept,這個方法就是我們實(shí)現(xiàn)攔截處理的地方。
注意到里面有個getSignatureMap方法,這個方法實(shí)現(xiàn)的是查找我們自定義攔截器的注解,通過注解確定哪些方法需要被攔截:
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;
}
通過源代碼我們可以知道,創(chuàng)建一個插件需要做以下事情:
- 創(chuàng)建一個類,實(shí)現(xiàn)
Interceptor接口。 - 這個類必須使用
@Intercepts、@Signature來表明要攔截哪個對象的哪些方法。 - 這個類的
plugin方法中調(diào)用Plugin.wrap(target, this)。 - (可選)這個類的
setProperties方法設(shè)置一些參數(shù)。 - XML中
<plugins>節(jié)點(diǎn)配置<plugin interceptor="你的自定義類的全名稱"></plugin>。
可以在第三點(diǎn)中根據(jù)具體的業(yè)務(wù)情況不進(jìn)行本次SQL操作的代理,畢竟動態(tài)代理還是有性能損耗的。
到此這篇關(guān)于通過源代碼分析Mybatis的功能的文章就介紹到這了,更多相關(guān)源代碼分析Mybatis內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring實(shí)現(xiàn)源碼下載編譯及導(dǎo)入IDEA過程圖解
這篇文章主要介紹了Spring實(shí)現(xiàn)源碼下載編譯及導(dǎo)入IDEA,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-07-07
三分鐘帶你掌握J(rèn)ava開發(fā)圖片驗(yàn)證碼功能方法
這篇文章主要來為大家詳細(xì)介紹Java實(shí)現(xiàn)開發(fā)圖片驗(yàn)證碼的具體方法,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,需要的可以參考一下2023-02-02
給JavaBean賦默認(rèn)值并且轉(zhuǎn)Json字符串的實(shí)例
這篇文章主要介紹了給JavaBean賦默認(rèn)值并且轉(zhuǎn)Json字符串的實(shí)例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Java中使用JWT生成Token進(jìn)行接口鑒權(quán)實(shí)現(xiàn)方法
這篇文章主要介紹了Java中使用JWT生成Token進(jìn)行接口鑒權(quán)實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
SpringBoot之配置logging日志及在控制臺中輸出過程
這篇文章主要介紹了SpringBoot之配置logging日志及在控制臺中輸出過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
Lombok使用@Tolerate實(shí)現(xiàn)沖突兼容問題
這篇文章主要介紹了Lombok使用@Tolerate實(shí)現(xiàn)沖突兼容問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08

