MyBatis-07-插件原理
MyBatis 插件
MyBatis 的插件实际上就是 Interceptor,拦截器,拦截并重写 SQL
为什么叫插件而不叫拦截器,因为在包 plugin 里,( ﹁ ﹁ ) ~→
Interceptor: 拦截器
InterceptorChain: 拦截器链
Invocation: 封装 Object.method(args),反射调用
Plugin: JDK 代理处理器 InvocationHandler,封装一个 Interceptor 及其拦截的方法,及 拦截对象 + 拦截方法 + Interceptor,就可以 invoke 前先进行拦截处理了
Interceptor
拦截器,只能拦截 MyBatis 中的四个对象
- ParameterHandler
- ResultSetHandler
- StatementHandler
- Executor
于是当这几个对象被 new 时,会代理他们,在代理逻辑中对他进行拦截
public interface Interceptor {
// 拦截器逻辑
Object intercept(Invocation invocation) throws Throwable;
// 封装为插件, 当 target 要被拦截时, 取出所有拦截器, 均调用他们的 plugin, 最终得到一个封装了要拦截的对象和所有拦截器实例的对象
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
InterceptorChain
实际就是多个 Interceptor 的更进一步封装,拦截器链
查看 pluginAll 的引用,可以看见就是 MyBatis 的四大对象的 new 的地方使用到了
public class InterceptorChain {
// 持有所有拦截器
private final List<Interceptor> interceptors = new ArrayList<>();
// 调用所有拦截器的 plugin 方法, 每调用一次生成一个代理对象, 后面的拦截器在前一个代理对象的基础上继续生成代理对象
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);
}
}
Intercepts、Signature
声明拦截器及拦截的方法
如下案例
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }))
public static class SwitchCatalogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
Connection con = (Connection) args[0];
con.setSchema(SchemaHolder.get());
return invocation.proceed();
}
}
Invocation
配合封装拦截器的,从构造函数可以看出仅支持 Executor、ParameterHandler、ResultSetHandler、StatementHandler 的封装
public class Invocation {
private static final List<Class<?>> targetClasses = Arrays.asList(Executor.class, ParameterHandler.class,
ResultSetHandler.class, StatementHandler.class);
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
if (!targetClasses.contains(method.getDeclaringClass())) {
throw new IllegalArgumentException("Method '" + method + "' is not supported as a plugin target.");
}
this.target = target;
this.method = method;
this.args = args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
Plugin
实现插件/拦截的逻辑,也是为什么叫做插件的原因
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();
// 要被代理的实例的所有接口, 用于创建 JDK 代理
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 有接口才会代理, 本身 MyBatis 的四大对象都是接口
if (interfaces.length > 0) {
// Plugin 是 InvocationHandler, 实现了代理逻辑
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 注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
// 为空, 这种应该是注入了 Interceptor 类型的但是没有使用注解
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 = MapUtil.computeIfAbsent(signatureMap, 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;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
// 这里只获取了要拦截的接口列表
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[0]);
}
}
总结
XMLConfigBuilder#pluginsElement: 解析 Interceptor
- Configuration#addInterceptor: 添加到 Configuration 中
- InterceptorChain#addInterceptor: Configuration 持有 InterceptorChain 实例, 添加到 InterceptorChain 中
Configuration new 四个对象时都调用了拦截器链的代理方法
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,
parameterObject, boundSql);
// 调用
return (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds,
ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler,
resultHandler, boundSql, rowBounds);
// 调用
return (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
}
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);
// 调用
return (StatementHandler) interceptorChain.pluginAll(statementHandler);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 调用
return (Executor) interceptorChain.pluginAll(executor);
}
分页的拦截
PageHelper 和 MP 的分页逻辑都是拦截 Executor.query 方法,根据原 SQL 生成 count SQL,然后根据diaect 生成 分页 SQL 方言,最终执行分页查询逻辑
MP 在 MyBatis 的 Interceptor 基础上增加了自己的 InnerInterceptor,提供了更多的类似 before 这种切入点。
当然 MP 的分页属于 MP 的扩展。