MyBatis 外掛
MyBatis 的外掛實際上就是 Interceptor,攔截器,攔截並重寫 SQL
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);
}