插件允許對Mybatis的四大物件(Executor、ParameterHandler、ResultSetHandler、StatementHandler)進行攔截
問題
Mybatis插件的注冊順序與呼叫順序的關系?
使用
在講原始碼之前,先看看如何自定義插件,
mybatis-demo,官方檔案
-
創建插件類
自定義插件類需要實作Interceptor
// 注解配置需要攔截的類以及方法 @Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}) }) // 實作Interceptor介面 public class SqlLogPlugin implements Interceptor { /** * 具體的攔截邏輯 */ @Override public Object intercept(Invocation invocation) throws Throwable { long begin = System.currentTimeMillis(); try { return invocation.proceed(); } finally { long time = System.currentTimeMillis() - begin; System.out.println("sql 運行了 :" + time + " ms"); } } /** * 判斷是否需要進行代理 * 此方法有默認實作,一般無需重寫 */ /*@Override public Object plugin(Object target) { return Plugin.wrap(target, this); }*/ /** * 自定義引數 */ @Override public void setProperties(Properties properties) { // 這是xml中配置的引數 properties.forEach((k, v) -> { System.out.printf("SqlLogPlugin---key:%s, value:%s%n", k, v); }); } } -
注冊
在組態檔注冊插件
<plugins> <plugin interceptor="com.wjw.project.intercaptor.SqlLogPlugin"> <property name="key1" value="https://www.cnblogs.com/konghuanxi/p/root"/> <property name="key2" value="https://www.cnblogs.com/konghuanxi/p/123456"/> </plugin> </plugins> -
效果
控制輸出
SqlLogPlugin---key:key1, value:root SqlLogPlugin---key:key2, value:123456 sql 運行了 :17 ms
原始碼
原理:Mybatis四大物件創建時,都回去判斷是否滿足插件的攔截條件,滿足,則四大物件就會被Plugin類代理
原始碼分3部分講,注冊、包裝、呼叫
-
注冊
xml方式的注冊,是在XMLConfigBuilder#pluginElement完成的,
不明覺厲的同學,請參考上一篇文章:Mybatis原始碼解讀-配置加載和Mapper的生成
// XMLConfigBuilder#pluginElement(XNode parent) private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // 讀取插件的類路徑 String interceptor = child.getStringAttribute("interceptor"); // 讀取自定義引數 Properties properties = child.getChildrenAsProperties(); // 反射實體化插件 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance(); interceptorInstance.setProperties(properties); // 將插件添加到配置的插件鏈中,等待后續使用 configuration.addInterceptor(interceptorInstance); } } }configuration.addInterceptor做得操作很簡單
-
包裝
上面講了插件的注冊,最后呼叫的是configuration.addInterceptor,最終呼叫的是InterceptorChain#addInterceptor
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<>(); /* * 每當四大物件創建時,都會執行此方法 * 滿足攔截條件,則回傳Plugin代理,否則回傳原物件 * @param target Mybatis四大物件之一 */ public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { // 呼叫每個插件的plugin方法,判斷是否需要代理 target = interceptor.plugin(target); } return target; } // 將攔截器添加interceptors集合中存起來 public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } public List<Interceptor> getInterceptors() { return Collections.unmodifiableList(interceptors); } }我們案例是攔截StatementHandler,所以也以此為例
/* * 這是創建StatementHandler的方法 * Configuration#newStatementHandler */ public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 可以看到創建完StatementHandler之后,會呼叫InterceptorChain的pluginAll方法 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }那么我們再仔細分析下
pluginAll方法,pluginAll呼叫的是每個插件的plugin方法default Object plugin(Object target) { return Plugin.wrap(target, this); }可以看到,最終呼叫的是
Plugin.*wrap*/* * Plugin#wrap * 判斷是否滿足插件的攔截條件,是則回傳代理類,否則回傳原物件 */ public static Object wrap(Object target, Interceptor interceptor) { // 獲取插件的攔截資訊(就是獲取@Intercepts注解的內容) Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); // 判斷是否滿足攔截條件 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { // 滿足攔截條件則回傳Plugin代理物件 return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } // 不滿足則回傳原物件 return target; } -
呼叫
在上一個
包裝步驟提到,滿足條件會回傳代理物件,即呼叫StatementHandler的所有方法,都會經過Plugin的invoke方法,去看看// Plugin#invoke 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)) { // 滿足攔截條件,則呼叫插件的intercept方法 return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/474811.html
標籤:Java
上一篇:OAuth2密碼模式已死,最先進的Spring Cloud認證授權方案在這里
下一篇:基于DEM的坡度坡向分析
