一、責任鏈模式
Mybatis插件按照`責任鏈模式`實現。
[責任鏈,菜鳥教程](http://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html)
最核心的每個Logger的logMessage()方法都可以選擇自己執行或者傳遞給下一個執行,從而形成了一個鏈。
package cn.baopz.module;
/**
* @author baopz
*/
public abstract class AbstractLogger {
public static final String INFO = "info";
public static final String DEBUG = "debug";
public static final String ERROR = "error";
private AbstractLogger next;
public AbstractLogger getNext() {
return next;
}
protected void setNext(AbstractLogger next) {
this.next = next;
}
/**
* 實現
* @param level
*/
public abstract void logger(String level);
}
package cn.baopz.module;
public class Debug extends AbstractLogger {
@Override
public void logger(String level) {
if(level.equals(AbstractLogger.DEBUG)){
System.out.println("debug 消息處理");
new Exception("#").printStackTrace();
}else{
getNext().logger(level);
}
}
}
package cn.baopz.module;
public class Error extends AbstractLogger {
@Override
public void logger(String level) {
if (level.equals(AbstractLogger.ERROR)){
System.out.println("error 處理");
new Exception("#").printStackTrace();
}else{
getNext().logger(level);
}
}
}
package cn.baopz.module;
public class Info extends AbstractLogger {
@Override
public void logger(String level) {
if(level.equals(AbstractLogger.INFO)){
System.out.println("info 處理");
new Exception("#").printStackTrace();
}else {
getNext().logger(level);
}
}
}
package cn.baopz.module;
/**
* @author baopz
*/
public class Client {
public static void main(String[] args) {
AbstractLogger debug = new Debug();
AbstractLogger info = new Info();
AbstractLogger error = new Error();
debug.setNext(info);
info.setNext(error);
error.setNext(debug);
info.logger(AbstractLogger.ERROR);
info.logger(AbstractLogger.DEBUG);
}
}
這裏說明一下:new Exception("#").printStackTrace();這段代碼可以打印棧運行軌跡,不錯的調試手段。
結果如下:
error 處理
java.lang.Exception: #
debug 消息處理
at cn.baopz.module.Error.logger(Error.java:8)
at cn.baopz.module.Info.logger(Info.java:10)
at cn.baopz.module.Client.main(Client.java:16)
java.lang.Exception: #
at cn.baopz.module.Debug.logger(Debug.java:8)
at cn.baopz.module.Error.logger(Error.java:10)
at cn.baopz.module.Info.logger(Info.java:10)
at cn.baopz.module.Client.main(Client.java:17)
二、Mybatis插件核心類
Mybatis中提供一個通用的攔截器,底層使用java的反射機制實現。
org.apache.ibatis.plugin.Interceptor接口,統一實現攔截的接口。
/**
* Copyright 2009-2015 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.util.Properties;
/**
* 插件中需要用的攔截器接口
* @author Clinton Begin
*/
public interface Interceptor {
/**
*
*執行攔截邏輯的方法
* @param invocation
* @return
* @throws Throwable
*/
Object intercept(Invocation invocation) throws Throwable;
/**
* target被代理的對象
* @param target
* @return 返回增強的target
*/
Object plugin(Object target);
/**
* 得到property name="someProperty" 的值,得到配置文件的值
* <plugins>
* <plugin interceptor="org.mybatis.example.ExamplePlugin">
* <property name="someProperty" value="100"/>
* </plugin>
* </plugins>
* @param properties
*/
void setProperties(Properties properties);
}
在實現該接口的類中添加兩個註解org.apache.ibatis.plugin.Intercepts、org.apache.ibatis.plugin.Signature
/**
* 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.plugin;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Clinton Begin
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
Signature[] value();
}
/**
* 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.plugin;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Clinton Begin
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
/**
* 類名
* @return
*/
Class<?> type();
/**
* 方法
* @return
*/
String method();
/**
* 參數
* @return
*/
Class<?>[] args();
}
三、插件測試實現
具體實現如下,設置一個被攔截的接口。
package cn.baopz.plugin;
/**
* 需要被攔截的接口
* @author baopz
*/
public interface PluginInterface {
/**
* 測試被攔截的方法
* @param test
*/
void test(String test);
}
實現一個被攔截的實例
package cn.baopz.plugin;
/**
* @author baopz
*/
public class Target implements PluginInterface {
@Override
public void test(String test) {
System.out.println(test);
}
}
一個攔截實例
package cn.baopz.plugin;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
/**
* @author baopz
*/
@Intercepts({@Signature(type = PluginInterface.class, method = "test", args = {String.class})})
public class InterceptorInstance implements Interceptor {
private Properties properties;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] objects = invocation.getArgs();
for (Object object : objects) {
System.out.println("被攔截的參數。" + object);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
System.out.println(target.getClass().getName());
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
一個測試實例
package cn.baopz.plugin;
/**
* @author baopz
*/
public class TestPlugin {
public static void main(String[] args) {
PluginInterface pluginInterface = new Target();
pluginInterface = (PluginInterface) new InterceptorInstance().plugin(pluginInterface);
pluginInterface.test("hello world");
}
}
運行結果
cn.baopz.plugin.Target
被攔截的參數。hello world
hello world
測試實例分析:
需要被攔截的實例(PluginInterface pluginInterface = new Target();),通過實現的攔截器實例攔截,反射,實現自己的功能(pluginInterface = (PluginInterface) new InterceptorInstance().plugin(pluginInterface);),得到一個增強的目標實例,在目標實例執行的時候,執行了我們寫的業務邏輯。
注意事項:通過後文的Plugin類,可以得知,如果攔截的是類本身,而非接口,那麼攔截將會失敗,繼而直接執行的是目標類未被增強的方法。
四、目標爭強核心類
org.apache.ibatis.plugin.Plugin、org.apache.ibatis.plugin.Invocation
/**
* Copyright 2009-2017 the original author or authors.
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 org.apache.ibatis.reflection.ExceptionUtil;
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;
/**
* plugin工具類型,反射工具
* @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;
}
/**
* @param target 被代理的對象
* @param interceptor 攔截器,主要用來執行org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation)
* @return
*/
public static Object wrap(Object target, Interceptor 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;
}
/**
* 具體的包裝類執行方法
* @param proxy
* @param method
* @param args
* @return object 執行的結構
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//得到目標類所包含的所有方法,method.getDeclaringClass()得到目標類
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
//找到註解的方法
if (methods != null && methods.contains(method)) {
//攔截方法,並執行注入到interceptor中的方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
/**
* 按照類(就是接口,接口在type中也是class的一種,如Map接口是Map.class類),記錄下所有的方法
* @param interceptor
* @return
*/
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<Class<?>, Set<Method>>();
//所有的簽名
for (Signature sig : sigs) {
//sig.type(),類(接口),多個方法
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;
}
/**
* 獲取目標類型實現的接口
* @param type
* @param signatureMap
* @return
*/
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
//接口容器
Set<Class<?>> interfaces = new HashSet<Class<?>>();
//循環把目標類,及其父類的所有被註解標記的接口記錄下來
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()]);
}
}
/**
* Copyright 2009-2017 the original author or authors.
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 調用
* @author Clinton Begin
*/
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
核心代碼分析:
核心代碼
/**
* @param target 被代理的對象
* @param interceptor 攔截器,主要用來執行org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation)
* @return
*/
public static Object wrap(Object target, Interceptor 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;
}
查找目標類及其父類的所有接口,通過反射方法(java.lang.reflect.Proxy#newProxyInstance),new出增強類。
五、實現在目標方法上鍊式添加所有攔截器
/**
* Copyright 2009-2015 the original author or authors.
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 攔截器鏈
* @author Clinton Begin
*/
public class InterceptorChain {
/**
* 存放插件的容器
*/
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
/**
* 添加攔截器
* @param target
* @return
*/
public Object pluginAll(Object target) {
/**
* 被所有攔截器增強的target
*/
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
/**
* 添加一個攔截器
* @param interceptor
*/
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
/**
* 得到所有的攔截器
* @return
*/
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}