mybatis专栏 https://blog.csdn.net/worn_xiao/category_6530299.html?spm=1001.2014.3001.5482
实现一个sql时间统计的插件,在看插件原理之前建议先看我写的mybaties执行流程的文章,首先我们来看一下mybatis的架构
如上所示是mybatis的基本执行流程架构图。然而我们的插件采用的也是面向切面编程的思想。分别对四个handler的操作做了拦截。这里我们以 StatementHandler的拦截为例子来说一下,首先看一下插件组装的代码。
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 = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
如上图所示就是插件介入的地方。那么我们来看看interceptorChain是什么。
protected final InterceptorChain interceptorChain = new InterceptorChain();
如上所示interceptorChain是一个拦截器链表。
/**
* Copyright 2009-2018 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.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Clinton Begin
*/
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
如图我们看一下这个pluginAll的方法。传入了目标对象,也就是我们的StatementHandler。
它实际上市调用了interceptor的plugin方法。这里实际上是采用了责任链的设计模式,通过为Interceptor 接口包装一个默认的代理类。把责任链依次经过代理。具体我们来看一下Inteceptor接口
public interface Interceptor {
Object intercept(Invocation var1) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
}
}
实际上这就是我们的插件。所以Mybaties的插件都会通过Plugin.wrap(target,this)来进行包装。那么这里面到底是什么呢,让我们来看一下
/**
* @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<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
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<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()]);
}
}
如上代码所示,可以看到实际上是创建了对应类的return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));这个代理类是一个插件的代理,那么我们看看这个插件的代理做了什么。
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);
}
}
实际上就是代理了StatementHandler这个目标队形执行之前执行signatureMap的方法。那么问题来了signatuireMap这个方法里面装的是什么东西呢。
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;
}
先找到获取这个map方法的地方。
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;
}
这个回我们可以看到了,实际就是获取当前类的intercepts.class的注解,然后获取注解里面的Signature注解数组,然后吧签名的类和方法映射以后放到signatureMap这个映射集合中。那么我们接下来看一下这个到底是什么
@Intercepts({
@Signature(type = Map.class, method = "get", args = {Object.class})})
就是这样一个注解。获取到Map这个类,映射map的get方法,后面标识参数类型。好的我们知道了signaturemap以后,我们来看看插件代理做了什么。
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);
}
}
实际上它是拿当前执行方法对应的类去获取方法的集合,然后执行实现了对应插件的interceptor方法,传递这个方法和目标代理对象,和执行的参数。也就是说如果代理的是Statemenhandler我们可以在它执行query,prepare等方法的时候进行拦截,从而获取到对应的变量值进行改写,比如sql,改写了sql再组装回到target对象。然后执行对应prepare的方法。
这样我们可以利用这个插件来实现我们的分表操作。甚至说我们可以拦截list方法,通过参数来构造分页查询。也是可以的。这个就是Mybaties插件的原理。讲完了插件原理以后我们来编写一个插件。
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyPlugin implements Interceptor {
private long time;
//方法拦截
@Override
public Object intercept(Invocation invocation) throws Throwable {
//通过StatementHandler获取执行的sql
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
long start = System.currentTimeMillis();
Object proceed = invocation.proceed();
long end = System.currentTimeMillis();
if ((end - start) > time) {
System.out.println("本次数据库操作是慢查询,sql是:" + sql);
}
return proceed;
}
//获取到拦截的对象,底层也是通过代理实现的,实际上是拿到一个目标代理对象
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
//获取设置的阈值等参数
@Override
public void setProperties(Properties properties) {
this.time = Long.parseLong(properties.getProperty("time"));
}
}
@org.springframework.context.annotation.Configuration
@MapperScan({"com.springboot.demo.mapper"})
public class MapperConfig {
//将插件加入到mybatis插件拦截链中
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(Configuration configuration) {
//插件拦截链采用了责任链模式,执行顺序和加入连接链的顺序有关
MyPlugin myPlugin = new MyPlugin();
//设置参数,比如阈值等,可以在配置文件中配置,这里直接写死便于测试
Properties properties = new Properties();
//这里设置慢查询阈值为1毫秒,便于测试
properties.setProperty("time", "1");
myPlugin.setProperties(properties);
configuration.addInterceptor(myPlugin);
}
};
}
}