20.MyBatis入门系列(20) -MyBatis之插件机制源码及流程分析
20.MyBatis入门系列(20) -MyBatis之插件机制源码及流程分析
官网案例
话不多说,先来个spring boot使用mybatis插件案例。
- 创建插件类,并注入到IOC中
@Component
@Intercepts({@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
@Override
public Object intercept(Invocation invocation) throws Throwable {
// do something ...... 方法拦截前执行代码块
System.out.println("do something ...... 方法拦截前执行代码块");
Object result = invocation.proceed();
// do something .......方法拦截后执行代码块
System.out.println(" do something .......方法拦截后执行代码块");
return result;
}
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}
- 执行查询,在SQL执行前后,都输出了插件中的代码。
可以看出,和AOP机制差不多,可以对执行的方法进行拦截处理,插件实际就是拦截器。
插件简介
官网说明
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
实际就是插件可以对四大组件进项拦截,在这些对象进行操作的前后植入自己的代码,类似于Spring中的AOP机制。 在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
使用场景
MyBatis的使用场景十分多,比如mybatis plus就扩展了很多插件,分页、数据权限、租户等等,MyBatis 的插件可以在不修改原来的代码的情况下,通过拦截的方式,改变四大核心对象的行为,比如处理参数,处理 SQL,处理结果。
一般用来:
- 分页
- 数据权限
- SQL日志
- 性能分析
插件原理
代理模式
mybatis中大量使用了设计模式和反射机制,其中插件机制基于代理模式实现,所以先复习下代理模式(Proxy)。
代理模式简介
代理模式: 为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上.增强额外的功能操作,即扩展目标对象的功能。
代理模式有不同的形式,主要有三种静态代理、动态代理(JDK 代理、接口代理)和Cglib 代理(可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴)。
简单示意图: 通过访问代理对象,最终访问到目标对象,代理对象可以对目标对象进行增强扩展。
动态代理
代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象,动态代理也叫做:JDK代理、接口代理。
所以动态是因为在程序运行时,通过反射机制动态创建而成。
在java的动态代理机制中,有两个重要的接口和类。一个是接口InvoactionHandler,一个是类Proxy,这一个类和一个接口是实现动态代理所必须用到的。
案例:
- 创建目标对象接口,并实现接口
public interface Target {
void test();
}
/**
* Created by TD on 2021/6/28
* 被代理对象
*/
public class TargetObject implements Target {
@Override
public void test() {
System.out.println("TargetObject test");
}
}
- 创建代理对象类实现InvoactionHandler接口
public class ProxyObject implements InvocationHandler {
// 维护一个目标对象
private Object target;
// 构造器 对target进行初始化
public ProxyObject(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理 before");
// 反射机制调用目标对象的方法
Object invoke = method.invoke(target, args);
System.out.println("JDK代理 after");
return invoke;
}
// 使用JDK Proxy API 给目标对象生成一个代理对象
public Object getJdkProxy() {
// newProxyInstance
// 参数1:类加载器
// 参数2:目标对象实现的接口
// 参数3:h:动态代理方法在执行时,会调用h里面的invoke方法去执行
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
- 测试
public class Test {
public static void main(String[] args) {
// 创建目标对象
Target target = new TargetObject();
// 使用目标对象创建代理对象
ProxyObject proxyObject = new ProxyObject(target);
// 获取代理对象并强转
Target jdkProxy = (Target) proxyObject.getJdkProxy();
// 调用方法
jdkProxy.test();
}
}
插件源码分析
拦截器链
之前我们介绍在加载Configuration对象时,会将所有拦截器加载到拦截器链中。
InterceptorChain 类提供了一个List集合存放所有拦截器,及其get/set 方法。
并提供了pluginAll方法,循环所有插件,对目标对象调用插件的plugin方法进行代理。
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
/**
* 包装执行器
*
* @param target
* @return
*/
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);
}
}
代理执行
之前也介绍过在执行查询时,Executor创建执行器的时候,都会调用pluginAll方法对执行器进行包装(四大对象创建的时候都会进行此步骤)。
插件类会对这些拦截对象进行代理,进入的是插件的plugin方法。
Plugin就是我们的插件代理对象,维护了一个目标对象,当目标对象执行时,会实际执行代理对象,那么一旦四大组件对象被代理后,我们就可以使用代理模式对他们进行增强扩展处理了。
plugin方法会获取你插件配置需要拦截四大对象的哪些方法,如果执行的这个方法需要拦截,就会使用JDK中的 Proxy.newProxyInstance方法进行动态代理。
/**
* 包装代理
*
* @param target 目标对象
* @param interceptor 拦截器
*/
public static Object wrap(Object target, Interceptor interceptor) {
// 获取需要拦截的类及方法Map
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 获取目标对象的classorg.apache.ibatis.executor.SimpleExecutor
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;
}
经过对目标对象的层层代理,就实现了mybatis的插件机制。
来源:https://yunyanchengyu.blog.csdn.net/article/details/118192008