外掛
原理
使用的是責任鏈模式,在四大物件排程的時候,使用外掛執行程式碼,去執行操作
即初始化的時候在Config物件中,由於是單例,全域性唯一,生成的外掛物件儲存進入陣列中,這個外掛需要實現一個介面,然後在使用外掛的時候,通過責任鏈的方式,迴圈呼叫,進行處理.
核心在於下面這一段
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
使用外掛
使用外掛需要實現介面Interceptor
package com.ming.MyBatis; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Invocation; import java.util.Properties; public class PluginElement implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { return null; } @Override public Object plugin(Object target) { return null; } @Override public void setProperties(Properties properties) { } }
外掛代理和反射設計
首先進入執行器
executor = (Executor) interceptorChain.pluginAll(executor);
然後執行器執行在這
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
plugin為生成代理物件的方法,逐步傳遞.
一層一層攔截處理
MyBatis提供了外掛類,用來生成代理物件,用來上方迴圈
/** *Copyright 2009-2019 the original author or authors. * *Licensed under the Apache License, Version 2.0 (the "License"); *you may not use this file except in compliance with the License. *You may obtain a copy of the License at * *http://www.apache.org/licenses/LICENSE-2.0 * *Unless required by applicable law or agreed to in writing, software *distributed under the License is distributed on an "AS IS" BASIS, *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *See the License for the specific language governing permissions and *limitations under the License. */ package org.apache.ibatis.plugin; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.ibatis.reflection.ExceptionUtil; /** * @author Clinton Begin */ public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } 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); } } 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; } private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { Set<Class<?>> interfaces = new HashSet<>(); while (type != null) { for (Class<?> c : type.getInterfaces()) { if (signatureMap.containsKey(c)) { interfaces.add(c); } } type = type.getSuperclass(); } return interfaces.toArray(new Class<?>[interfaces.size()]); } }
這是一個動態代理物件.
wrap是生成的動態代理物件.
invoke是排程的方法.
如果存在攔截簽名,將會在這裡呼叫
若不存在攔截簽名,將會直接呼叫原生的方法
外掛開發
看上方的程式碼,可以知道需要確定攔截簽名用來呼叫開發好的外掛
如果需要攔截StatementHandler 執行SQL的過程
需要先檢視StatementHandler介面
/** *Copyright 2009-2016 the original author or authors. * *Licensed under the Apache License, Version 2.0 (the "License"); *you may not use this file except in compliance with the License. *You may obtain a copy of the License at * *http://www.apache.org/licenses/LICENSE-2.0 * *Unless required by applicable law or agreed to in writing, software *distributed under the License is distributed on an "AS IS" BASIS, *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *See the License for the specific language governing permissions and *limitations under the License. */ package org.apache.ibatis.executor.statement; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.List; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.session.ResultHandler; /** * @author Clinton Begin */ public interface StatementHandler { Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; void parameterize(Statement statement) throws SQLException; void batch(Statement statement) throws SQLException; int update(Statement statement) throws SQLException; <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException; <E> Cursor<E> queryCursor(Statement statement) throws SQLException; BoundSql getBoundSql(); ParameterHandler getParameterHandler(); }
開發外掛需要實現Interceptor介面
通過看介面定義可以知道需要攔截
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
方法
所以編寫註解,提供簽名如下
// 說明這是攔截器 @Intercepts({ // 需要攔截的方法 @Signature( type = StatementHandler.class, method = "prepare", args = { // 提供引數 Connection.class } ) })
下面需要實現該介面
package com.ming.MyBatis.plugin; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import java.util.Properties; /** * 對資料庫返回的資料進行限制外掛 * @author ming */ // 說明這是攔截器 @Intercepts({ // 需要攔截的方法 @Signature( type = Executor.class, method = "update", args = { // 提供引數 MappedStatement.class, Object.class } ) }) public class MyPlugin implements Interceptor { Properties properties = null; /** * 用於替換攔截方法的內容,在這裡,原始方法被攔截 * @param invocation此為處理該內容的責任鏈 * @return * @throws Throwable */ @Override public Object intercept(Invocation invocation) throws Throwable { // 處理之前 System.out.println("before....."); // 進行呼叫真實的物件,否則排程到下一個代理物件invoke方法 Object object = invocation.proceed(); // 處理之後 System.out.println("after...."); return object; } /** * 生成物件代理,用於原始碼中的迴圈 即呼叫的是這一句 target = interceptor.plugin(target); * 在這裡,targer為外掛物件,然後在這裡進行迴圈處理,使用預設的wrap進行呼叫攔截的方法 * @param target * @return */ @Override public Object plugin(Object target) { System.out.println("呼叫生成代理物件"); // 使用的是wrap方法,用於生成代理物件 return Plugin.wrap(target, this); } /** * 從配置檔案中,獲取配置屬性 初始化的時候會呼叫此方法 * @param properties */ @Override public void setProperties(Properties properties) { System.out.println(properties.get("dbType")); this.properties = properties; } }
一個是用來呼叫真實方法的.一個是用來生成代理物件的時候迴圈生成.最後是讀取內容
書寫完成以後更改配置檔案
<plugins> <plugin interceptor="com.ming.MyBatis.plugin.MyPlugin"> <property name="dbType" value="mysql"/> </plugin> </plugins>
然後執行.
檢視日誌資訊,可以看到
[2019-04-21 01:16:41,506] Artifact mingmingxiao:war exploded: Artifact is deployed successfully [2019-04-21 01:16:41,506] Artifact mingmingxiao:war exploded: Deploy took 2,203 milliseconds mysql 呼叫生成代理物件 呼叫生成代理物件 呼叫生成代理物件 呼叫生成代理物件 呼叫生成代理物件 21-Apr-2019 01:16:49.028 資訊 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployDirectory 把web 應用程式部署到目錄 [/home/ming/apache-tomcat-9.0.17/webapps/manager] 21-Apr-2019 01:16:49.107 資訊 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/home/ming/apache-tomcat-9.0.17/webapps/manager] has finished in [79] ms 呼叫生成代理物件
在這裡,完成一個外掛的開發