目录
0.前述
1.拦截器实现
1.1 Interceptor接口
1.2 Invocation类
1.3 Intercepts&Signature注解
2.mybatis拦截器配置处理
2.1 拦截器解析
2.2 拦截器代理
2.2.1 Executor
3.拦截器执行逻辑
Mybatis拦截器为编程人员提供了操作数据库sql的编程接口,可以定制感兴趣的拦截方法,并在其上施加额外的处理逻辑,大大方便了编程人员实现定制化操作的需求;
本篇主要介绍一下几点:
下面介绍一个简单的加密拦截插件SecurityExecutorInterceptor,其基本代码逻辑如下:
@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }) })
public class SecurityExecutorInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//执行前对需要加密的字段进行加密
Object result = invocation.proceed();
//恢复加密字段,起到恢复现场的作用
return result;
}
@Override
public Object plugin(Object target) {
return target instanceof Executor ? Plugin.wrap(target, this) : target;
}
@Override
public void setProperties(Properties properties) {
// do nothing
}
}
Interceptor是拦截器的顶层接口,主要定义了三个接口方法:
intercept: 用于对目标方法调用的封装类Invocation进行拦截逻辑的处理
plugin:用于对target对象实现jdk动态代理
setProperties: 提供设置拦截器配置参数的接口,方便实现拦截器的定制化参数配置,并将该配置用于具体的拦截逻辑
Invocation类封装了被代理类target,目标方法method和方法实参,并提供了proceed方法,该方法通过反射调用的方式,调用被代理类的目标方法:
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
Intercepts用于标注拦截器类具体拦截的是哪个接口的哪个方法以及方法的形参列表,这三个信息都封装到了Signature注解中,并且Intercepts可以支持对多个Signature进行拦截;
mybatis中SqlSessionFactoryBean提供了两种设置拦截器的方法:1)通过调用setPlugins(Interceptor[] plugins)方法注册拦截器 2)通过在mybatis配置文件中利用
注册的拦截器都统一放到了Configuration类中的InterceptorChain interceptorChain = new InterceptorChain()成员变量中:
public class InterceptorChain {
private final List 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 getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
目前mybatis拦截器支持对四种接口类型进行拦截:
Executor
StatementHandler
ParameterHandler
ResultSetHandler
下面主要以对Executor的拦截逻辑进行说明,其它类似;
Executor表示执行器接口,封装到了DefaultSqlSession对象中,用于具体sql语句的执行;
Executor接口类如下,其中的接口方法可以成为拦截器的目标拦截方法;
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
void clearLocalCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
下面是它的几个子类继承关系,具体介绍不做展开
在Configuration配置类中,提供了一个工厂方法newExecutor用来创建Executor,其中第15行,通过Jdk动态代理的方式使拦截器生效,具体源码如下:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : 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);
}
executor = (Executor) interceptorChain.pluginAll(executor);//该方法在上面有具体实现,逐个调用了拦截器的plugin方法,生成拦截器的jdk动态代理实现;
return executor;
}
下面具体分析下plugin(Object target)方法的内部执行逻辑,这里关键逻辑都在Plugin.wrap(target, this)中,该方法的源码如下:
public static Object wrap(Object target, Interceptor interceptor) {
Map, Set> 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;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set 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);
}
}
wrap方法中:
1)通过Jdk动态代理的方式生成了Executor的Jdk动态代理对象
2)将Intercepts和Signature注解标注的拦截方法解析后放入signatureMap成员变量中。
同时Plugin类实现了InvoicationHandler接口,覆盖了invoke方法,执行拦截逻辑的嵌入,并且把接口intercept(Invocation invoication)开放给开发者,用来实现拦截器的具体处理逻辑;
总结而言,executor = (Executor) interceptorChain.pluginAll(executor)就实现了逐级的动态代理构建,然后在具体执行sql语句的时候,调用代理类的invoke方法,实现拦截器逻辑的处理;
下面对拦截器的具体执行进一步分析;
Jdk动态代理的继承结构如下:
实际在执行Dao类的接口方法时,会首先创建一个SqlSession对象,该对象封装了经过逐级代理的Executor代理类,sqlSession具体在执行sql命令(如query)方法的时候,由于有动态代理类的存在,目标方法的调用会委托给InvocationHandler的invoke方法,这里即是Plugin的invoke方法(具体源码见上图),然后在invoke方法中利用解析好的signatureMap首先判断该目标方法是不是被拦截的方法,如果是被拦截的方法,则进入拦截器intercept(invocation) 方法中执行拦截逻辑,并且反射调用Invocation的proceed()方法逐级向下执行被代理类的目标方法,因此,所有的拦截器都会依次执行。