mybatis拦截器执行原理
mybatis的拦截器本人平时也很少用到,没了解之前,也只是知道运用到了动态代理用来增强方法的功能,但是不了解其中的原理。为了更好的使用mybatis,这次,我记录下我所了解的mybatis的原理,本文不一定完全正确,可能有理解不到位的地方。
# 1、使用mybatis的拦截器
像平常使用mybatis框架时,如果哪句sql报错了,我们可以通过控制台或日志打印的sql去查看sql的问题,但是如果sql有太多的参数,其实是很不方便的,自己还得手动去把一个一个参数给设置上,有些浪费时间,这时候就可以利用mybatis的拦截器去帮我们把参数给设置上。
配置步骤
# 1.创建拦截器
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
public class SlowSqlInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
// 主要逻辑 拼接参数到sql,并打印
}
@Override
public Object plugin(Object target) {
// 创建代理对象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 设置属性
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @author Clinton Begin
*/
public class Invocation {
// 目标对象,即ParameterHandler、ResultSetHandler、StatementHandler或者Executor实例
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 getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
/**
* 执行目标方法
* @return 目标方法执行结果
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
拦截器类实现intercepter接口
intercepter方法主要写拦截方法的逻辑,Invocation对象主要有三个内置对象和proceed方法,proceed方法的作用就是用来执行代理对象的方法,对象target是被代理的对象实例,对象method是拦截方法,对象args是被调用方法传入的参数,很符合调用代理对象invoke方法的条件。
plugin方法接受实际对象,作用返回一个代理对象,这里是调用了Plugin提供的warp方法,方便创建代理对象,==我们也可以自己写创建代理对象的代码==。
setProperties方法设置属性,当拦截器被扫描到时,会调用此方法。
类上还需要打上@Intercepts注解,value值为Signature对象,作用是拦截方法作用到哪个mybatis对象的哪个方法上,像图上是当调用statementHandler的query,update,batch方法时,mybatis才会进行拦截
# 2.mybatis配置文件配置插件
# 2、mybatis的拦截器如何创建的
- configuration创建时,会去扫描配置文件的
<plugin>
标签 - 获取
<plugin>
标签的interceptor属性 - 获取拦截器属性,转换为Properties对象
- 创建拦截器实例 利用TypeAliasRegistry的resolveAlias方法,将传进来的别名,判断如果别名在TYPE_ALIASES里面,则直接获取类对象,如果不是则反射获取类对象
- 设置拦截器实例属性信息 将第三步的properties属性添加到拦截器实例里面
- 將拦截器实例添加到拦截器链中 (拦截器在configuration里面)
# 3、mybatis的拦截器在哪些时机会被使用到
在Configuration类的
newParameterHandler()、newResultSetHandler()、newStatementHandler()、newExecutor()
这些工厂方法中,都调用了InterceptorChain对象的pluginAll()方法。-
/**
* 该方法用于创建Executor、ParameterHandler、ResultSetHandler、StatementHandler的代理对象
Plugin.warp()方法首先获取自定义的拦截类上的@Signature注解上的信息并存入map,那就知道了要拦截哪些对象地哪些方法,然后判断传入的target对象是否满足拦截对象的类型,满足则创建代理对象,不满足则直接返回原对象。
* @param target
* @param interceptor
* @return
*/
public static Object wrap(Object target, Interceptor interceptor) {
// 调用getSignatureMap()方法获取自定义插件中,通过Intercepts注解指定的方法
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 添加拦截方法
这里拿在执行器上添加拦截方法举例:
1. mybatis在创建sqlSession时,会创建执行器Executor,同时调用拦截器的pluginAll方法,调用每个拦截器的plugin方法,这个方法主要是创建代理对象,将代理功能增强到被拦截的方法上。
2. Plugin.warp()方法首先获取自定义的拦截类上的@Signature注解上的信息并存入map,那就知道了要拦截哪些对象地哪些方法,然后判断传入的target对象是否满足拦截对象的类型,满足则创建代理对象,不满足则直接返回原对象。
# 拦截器被使用到的过程
1. mapper执行方法时,相当于调用代理对象的invoke方法,mapperMethod会调用sqlSession的的select或者update方法
2. sqlSession实际调用的是执行器的query或者update方法,如果不走缓存的话,最后会走到SimpleExecutor的doQuery方法
3. configuration创建statementHandler代理对象,同样有关于statementHandler的拦截器,也会创建代理类
4. statementHandler执行query方法,如果statementHandler对象是代理对象,则进入Plugin的invoke方法,如果当前执行的方法符合被拦截的方法的要求,那么就会执行拦截方法,否则不执行拦截方法,如果statementHandler对象不是代理对象,直接执行原方法。
# 4、总结
原来以为拦截器只是简单的使用下动态代理,看了mybatis的拦截器发现,一个经得起捶打的功能是不可能那么简单的,里面用到了动态代理解决mapper的实现问题,适配器模式用来解决结果对象,参数对象的映射,而且在我看来configuration类做了太多的工作,很多初始化的数据都能在里面找到,只要持有configuration对象,很多数据都可以直接拿到,避免现拿现查的麻烦。