简要阐述一下Mybatis执行数据库查询操作过程,使用Executor
作为执行器调用StatementHandler
对Statement
进行处理,如果是PreparedStatement
会通过PreparedStatementHandler
为条件占位符设置条件值,底层还是通过JDBC原生Statement
执行数据库操作,通过ResultSetHandler
返回处理返回的ResultSet
结果集封装成ResultMap
对象返回
简单了解了Mybatis执行的主要流程,明白StatementHandler
、ParameterHandler
、ResultSetHandler
的角色定位,铺垫了学习拦截器使用场景和原理的基础
在Mybatis的核心配置类Configuration
中,可以看到在创建ParameterHandler
、ResultSetHandler
、StatementHandler
时为它们进行了拦截操作
public class Configuration {
// 拦截器注册中心
protected final InterceptorChain interceptorChain = new InterceptorChain();
/*
* 创建ParameterHandler
*/
public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang()
.createParameterHandler(mappedStatement, parameterObject, boundSql);
// 为ParameterHandler应用拦截器
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
/*
* 创建ResultSetHandler
*/
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);
// 为ResultSetHandler应用拦截器
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
/*
* 创建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 = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
}
InterceptorChain
顾名思义为拦截器链,也可以简单地理解为拦截器的注册中心,Mybatis所有拦截器都注册到了拦截器链中
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);
}
}
Interceptor
是拦截器接口,重点提供了一个调用拦截器的plugin
方法,以及拦截器拦截逻辑处理intercept
方法
public interface Interceptor {
// 应用拦截器逻辑
Object intercept(Invocation invocation) throws Throwable;
// 执行当前拦截器
Object plugin(Object target);
void setProperties(Properties properties);
}
以最常用的分页处理插件,分析如何应用拦截器
PaginationInterceptor
拦截StatementHandler
的prepare
方法执行,该方法入参为Connection
、Integer
它的核心原理是为StatementHandler
生成代理对象,当StatementHandler
执行prepare
方法的时候,执行拦截方法添加分页逻辑
当通过Configuration
创建StatementHandler
的时候,会触发拦截器链的执行,PaginationInterceptor
的plugin
方法会被调用,此时它为StatementHandler
生成代理对象,当代理对象执行prepare
方法的时候会触发代理对象的invoke
方法,此时会校验当前执行的方法是否为拦截方法,若是则执行PaginationInterceptor
的分页逻辑interceptor
方法
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})})
public class PaginationInterceptor extends SqlParserHandler implements Interceptor {
/**
* 这个方法会被Mybatis的拦截器链调用
* 在这里为StatementHandler生成代理对象
*/
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
// 分页拦截器添加分页逻辑拦截方法
public Object intercept(Invocation invocation) throws Throwable {
// ...
return invocation.proceed(); // 处理完成调用被代理对象的目标方法,放行
}
}
Plugin
为拦截器提供了工具方法,可以为拦截目标生成代理对象,记录拦截目标被拦截的方法信息,以及作为InvocationHandler
实现类提供了代理增强方法invoke
public class Plugin implements InvocationHandler {
/**
* @param target 被拦截的对象
* @param interceptor 拦截器
*/
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) {
// 利用JDK动态代理生成代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
// 当前类实现了动态代理InvocationHandler接口,
// 封装被代理的目标,拦截器,以及拦截的目标方法
new Plugin(target, interceptor, signatureMap));
}
return target;
}
/*
* 解析拦截器类上的Intercepts注解,缓存要拦截的方法信息
*/
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
if (interceptsAnnotation == null) {
// ...省略异常输出
}
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) {
// ...
}
}
return signatureMap;
}
/**
* 这个方法是JDK动态代理对象调用目标方法之后,会被调用的方法
* 在这里首先判断当前当前代理对象执行的方法是否为拦截方法,
* 如果不是使用被代理对象调用原先方法
* 如果是则使用调用代理对象的拦截方法
*/
@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)) {
// 注意这里会通过Invocation封装被代理对象、调用方法和调用参数传递给拦截器
return interceptor.intercept(new Invocation(target, method, args));
}
// 调用的不是拦截方法,使用被代理对象调用原先方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
}
Invocation
封装了被代理的目标对象和被调用的方法,作为入参传递给拦截器的interceptor
方法,当拦截器执行完逻辑之后,可以通过proceed
方法调用被代理对象的原方法逻辑
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
// ...
// 调用被代理对象的目标方法,结束拦截器的使用
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}