Mybatis插件机制
MyBatis 插件可以用来实现拦截器接口 Interceptor ,在实现类中对拦截对象和方法进行处理。
拦截器接口Interceptor
// Intercepts注解类的@Signature签名注解用于描述Invocation参数的信息
@Intercepts({
@Signature(type = Executor.class, method = "xxx", args = {xxx.class, xxx.class})
})
public interface Interceptor {
// 执行拦截逻辑的方法,插件的核心方法
Object intercept(Invocation invocation) throws Throwable;
//生成target的代理对象
//将当前的拦截器生成代理对象存到拦截器链中
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
//传递插件所需参数
default void setProperties(Properties properties) {
...
}
}
复制
Invocation
Invocation是拦截逻辑方法的参数。
public class Invocation {
private final Object target; // 拦截的对象信息,@Signature注解的type属性
private final Method method; // 拦截的方法信息,@Signature注解的method属性
private final Object[] args; // 拦截的对象方法中的参数,@Signature的args属性
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
// get...
// 利用反射来执行拦截对象的方法
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
复制
plugin方法
Mybatis在创建拦截器代理时候会判断一次,当前这个类 Interceptor 到底需不需要生成一个代理进行拦截,如果需要拦截,就生成一个代理对象,这个代理就是一个 {@link Plugin},它实现了jdk的动态代理接口 {@link InvocationHandler},如果不需要代理,则直接返回目标对象本身。
加载时机:该方法在 mybatis 加载核心配置文件时被调用。
生成代理对象的方法
public class Plugin implements InvocationHandler {
// 利用反射,获取这个拦截器 MyInterceptor 的注解 Intercepts和Signature,然后解析里面的值,
// 1 先是判断要拦截的对象是哪一个
// 2 然后根据方法名称和参数判断要对哪一个方法进行拦截
// 3 根据结果做出决定,是返回一个对象呢还是代理对象
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;
}
}
复制
执行模式
当配置多个拦截器时, MyBatis 会遍历所有拦截器,按顺序执行拦截器的 plugin 口方法, 被拦截的对象就会被层层代理。
在执行拦截对象的方法时,会一层层地调用拦截器,拦截器通 invocation proceed()调用下层的方法,直到真正的方法被执行。
方法执行的结果 从最里面开始向外 层层返回,所以如果存在按顺序配置的三个签名相同的拦截器, MyBaits 会按照 C>B>A>target.proceed()>A>B>C 的顺序执行。如果签名不同, 就会按照 MyBatis 拦截对象的逻辑执行.
拦截器注解@Intercepts
拦截器接口上的注解@Intercepts决定了插件拦截的对象是什么,什么时候进行拦截。
@Intercepts({@Signature(
type = ParameterHandler.class,
method = "setParameters",
args = {PreparedStatement.class})
})
@AllArgsConstructor
public class MybatisEncryptInterceptor implements Interceptor {
...
}
复制
@Intercepts({})
注解中是一个@Signature()
数组,可以在一个拦截器中同时拦截不同的接口和方法。
@Signature注解
包含以下三个属性:
- type 设置拦截接口,可选值是前面提到的4个接口
- method 设置拦截接口中的方法名 可选值是前面4个接口中所对应的方法,需要和接口匹配
- args 设置拦截方法的参数类型数组 通过方法名和参数类型可以确定唯一一个方法
@Signature.type
指定拦截什么接口类,允许使用插件来拦截的接口包括以下几个:
- Executor类:执行器Executor (update、query、commit、rollback等方法);
- ParameterHandler:参数处理器ParameterHandler (getParameterObject、setParameters方法);
- ResultSetHandler:结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法);
- StatementHandler:SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);
@Signature.method
@Signature.method代表在@Signature.type配置的类基础上指定需要拦截的类中的方法。
@Signature.args
@Signature.args是在@Signature.method的基础上指定方法的入参类型,按方法定义的顺序写。最终,通过类+方法+入参类型
定位拦截位置。
配置方式
如下面示例代码配置了 type = ParameterHandler.class 、 method = “setParameters” 、args = {PreparedStatement.class}
代表拦截ParameterHandler类的setParameters方法且参数是PreparedStatement类型。
//示例注解配置
@Intercepts({@Signature(
type = ParameterHandler.class,
method = "setParameters",
args = {PreparedStatement.class})
})
复制
ParameterHandler类
public interface ParameterHandler {
Object getParameterObject();
// 注解配置命中拦截的方法
void setParameters(PreparedStatement ps)
throws SQLException;
}
复制