一、责任链模式
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);
}
}
六、MyBatis 允许使用插件来拦截的类型,方法
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
org.apache.ibatis.session.Configuration#interceptorChain
在mybatis统一配置类中(org.apache.ibatis.session.Configuration)提供了一个拦截器链(protected final InterceptorChain interceptorChain = new InterceptorChain()),拦截器链在四个方法中
org.apache.ibatis.session.Configuration#newParameterHandler
org.apache.ibatis.session.Configuration#newResultSetHandler
org.apache.ibatis.session.Configuration#newStatementHandler
org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
均调用pluginAll方法添加到插件到链中。