Mybatis在初始化的時候,會讀取xml中的SQL,解析後會生成SqlSource對象,SqlSource對象分爲兩種。java
DynamicSqlSource
,動態SQL,獲取SQL(getBoundSQL
方法中)的時候生成參數化SQL。node
RawSqlSource
,原始SQL,建立對象時直接生成參數化SQL。sql
由於RawSqlSource
不會重複去生成參數化SQL,調用的時候直接傳入參數並執行,而DynamicSqlSource
則是每次執行的時候參數化SQL,因此RawSqlSource
是DynamicSqlSource
的性能要好的。數據庫
解析的時候會先解析include
標籤和selectkey
標籤,而後判斷是不是動態SQL,判斷取決於如下兩個條件:數組
${}
表達式。注意這種方式存在SQL注入,謹慎使用。trim
、where
、set
、foreach
、if
、choose
、when
、otherwise
、bind
標籤相關代碼以下:緩存
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
,它主要作了這些事情:app
每一個Mapper方法第一次運行時會去建立ParamNameResolver
,以後會緩存ide
建立時會根據方法簽名,解析出參數名,解析的規則順序是函數
若是參數類型是RowBounds
或者ResultHandler
類型或者他們的子類,則不處理。性能
若是參數中有Param
註解,則使用Param
中的值做爲參數名
若是配置項useActualParamName
=true,argn
(n>=0)標做爲參數名,若是你是Java8以上而且開啓了
-parameters`,則是實際的參數名
若是配置項useActualParamName
=false,則使用n
(n>=0)做爲參數名
相關源代碼:
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中參數對象和值的映射時,還進行了進一步的處理。
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; } }
另外值得一提的是,對於集合類型,最後還有一個特殊處理
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
建立返回對象,其中的關鍵代碼建立了代理對象:
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); }
另外一方面,getRowValue
會調用applyPropertyMappings
方法,其內部會調用getPropertyMappingValue
,繼續追蹤到getNestedQueryMappingValue
方法,在這裏,有幾行關鍵代碼:
// 若是要求延遲加載,則延遲加載 if (propertyMapping.isLazy()) { // 若是該屬性配置了延遲加載,則將其添加到 `ResultLoader.loaderMap` 中,等待真正使用時再執行嵌套查詢並獲得結果對象。 lazyLoader.addLoader(property, metaResultObject, resultLoader); // 返回已定義 value = DEFERED; // 若是不要求延遲加載,則直接執行加載對應的值 } else { value = resultLoader.loadResult(); }
這幾行的目的是跳過屬性值的加載,等真正須要值的時候,再獲取值。
Executor是一個接口,其直接實現的類是BaseExecutor
和CachingExecutor
,BaseExecutor
又派生了BatchExecutor
、ReuseExecutor
、SimpleExecutor
、ClosedExecutor
。其繼承結構如圖:
其中ClosedExecutor
是一個私有類,用戶不直接使用它。
BaseExecutor
:模板類,裏面有各個Executor的公用的方法。SimpleExecutor
:最經常使用的Executor
,默認是使用它去鏈接數據庫,執行SQL語句,沒有特殊行爲。ReuseExecutor
:SQL語句執行後會進行緩存,不會關閉Statement
,下次執行時會複用,緩存的key
值是BoundSql
解析後SQL,清空緩存使用doFlushStatements
。其餘與SimpleExecutor
相同。BatchExecutor
:當有連續的Insert
、Update
、Delete
的操做語句,而且語句的BoundSql
相同,則這些語句會批量執行。使用doFlushStatements
方法獲取批量操做的返回值。CachingExecutor
:當你開啓二級緩存的時候,會使用CachingExecutor
裝飾SimpleExecutor
、ReuseExecutor
和BatchExecutor
,Mybatis經過CachingExecutor
來實現二級緩存。Mybatis一級緩存的實現主要是在BaseExecutor
中,在它的查詢方法裏,會優先查詢緩存中的值,若是不存在,再查詢數據庫,查詢部分的代碼以下,關鍵代碼在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()); // 已經關閉,則拋出 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
中,則會將查詢出來的結果放到緩存中。
// 從數據庫中讀取操做 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有關。
@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結構存儲的:
/** * 一級緩存的實現類,部分源代碼 */ 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查詢緩存中是否存在值,若是存在則返回緩存的值,沒有則查詢數據庫。
在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) { // 若是須要清空緩存,則進行清空 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
是在哪裏建立的呢?經過調用的追溯,能夠找到它的建立:
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裏面的:
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
的源碼咱們能夠知道,默認查詢後添加的緩存保存在待提交對象裏。
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
代碼,只是作了標記,並無真正釋放對象。在查詢時根據標記直接返回空,在commit
才真正釋放對象:
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
會清空這些臨時緩存:
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
:返回對象處理器這四個對象的接口的全部方法均可以用插件攔截。
插件的實現代碼以下:
// 建立 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()
方法的調用,繼續查看這個方法:
/** * 應用全部插件 * * @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的動態代理,攔截方法的實現:
/** * 建立目標類的代理對象 * * @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
方法,這個方法實現的是查找咱們自定義攔截器的註解,經過註解肯定哪些方法須要被攔截:
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
方法設置一些參數。<plugins>
節點配置<plugin interceptor="你的自定義類的全名稱"></plugin>
。能夠在第三點中根據具體的業務狀況不進行本次SQL操做的代理,畢竟動態代理仍是有性能損耗的。