Mybatis—学习过程—Mybatis缓存之插件机制
1.什么是插件:可以理解为其他形式的拓展点
2.Mybatis插件介绍:
也就是说使用插件完成组件中的动态增强,底层执行的就是动态代理,也就是说这四大组件(四大对象)
返回的并不是对应的原生对象,而是代理对象
四大核心对象的关系图
- Executor:就是执行器,主要负责增删改查的行为
- StatementHandler:sql语法构建器,主要是完成sql语法的预编译
- ParamterHandler:参数处理器,用来设置参数
- ResultSetHandler:结果集处理器,主要用来处理返回结果集的
- Mybatis
- Mybatis允许使用插件对这四大核心对象进行拦截,允许拦截的方法如下
Mybatis被允许拦截的方法如下(也就是四大核心对象里面的方法)
:
3.Mybatis插件原理
在四大对象创建的时候
拦截:插件如何拦截并附加额外功能,以ParameterHandler来说
public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
Object object, BoundSql sql, InterceptorChain interceptorChain) {
//创建了一个parameterHandler对象
ParameterHandler parameterHandler =
mappedStatement.getLang().createParameterHandler(mappedStatement, object, sql);
//每个创建出来的parameterHandler对象不是直接返回的,
//而是使用interceptorChain.plugAll(paramterHandler)进行了处理
//这个pluginAll是怎么进行处理的?请看下面这部分代码
parameterHandler = (ParameterHandler)interceptorChain.pluginAll(parameterHandler);
//通过pluginAll方法返回的代理对象,最中还是要赋值给parameterHandler
//最中我们要返回的就是parameterHandler这个代理对象(重重代理对象)
return parameterHandler;
}
pluginAll方法如何处理的
//获取所有的Interceptor(拦截器)插件需要实现接口,调用interceptor.plugin(target);返回target包装后的对象
public Object pluginAll(Object target) {
//首先在这个方法中遍历了所有的拦截器,另外target是上面传过来的原生对象
for (Interceptor interceptor : interceptors) {
//将原生对象进行了处理,怎么处理的就是使用了动态代理,产生了一个代理对象target
target = interceptor.plugin(target);
}
//最终返回的也是代理对象
return target;
}
4.Mybatis自定义插件
如果想要拦截Excutor中的query方法,可以这样定义插件
- 参数
- type:表示要拦截的接口类型
- method:表示要拦截接口中的哪个方法
- args:表示该方法的入参
- 定义完插件之后,还需要将插件配置到Mybatis全局配置文件(
sqlMapConfig.xml
)中去<plugins> <plugin interceptor="com.lagou.plugin.ExamplePlugin"> </plugin> </plugins>
5.Mybatis插件总结
1.因为返回的Excutor
是代理对象,代理对象执行方法其实是调用底层的invoke
方法
2.在invoke方法中,我们首先可以完成一个原方法的调用,但是在原方法调用的前和后可以进行对相关逻辑的增强
3.Mybatis允许拦截的核心对象一共就4个,主要是通过拦截四个核心组件当中的方法,来完成方法的动态增强。
6.Mybatis自定义插件
Mybatis的插件接口——Interceptor
(拦截器)
7.自定义插件实例
@Intercepts({
//@Signature:这个注解可以配置很多个,可以拦截多个核心对象里面的方法
@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})
})
public class MyPlugin implements Interceptor {
/**
* 拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行这个Intercept方法,
* 增强的逻辑写在intercept中即可
*
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("对方法进行了增强");
//proceed让原方法执行
return invocation.proceed();
}
/**
* 主要是把当前的拦截器生成的代理对象存到拦截器链中
* target是被拦截的目标对象
* 调用Plugin类中的wrap(target,interceptor)方法
*
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
Object wrap = Plugin.wrap(target, this);
return wrap;
}
/**
* 获取配置文件的参数
*
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("获取到的配置参数是"+properties);
}
}
配置在sqlMapConfig.xml
全局配置文件
<plugins>
<plugin interceptor="com.lagou.plugin.MyPlugin">
//property属性是设置参数的,最终执行打印出来的结果是----获取到的配置参数是{name=tom}
<property name="name" value="tom"/>
</plugin>
</plugins>
8.Mybatis插件机制—源码分析
- 先找入口,认为
Plugin
是入口,因为它实现了InvocationHandler
接口,编写JDK动态代理时,第三个参数就是InvocationHandler
,代码如下。并且发现,每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,而且
编写JDK动态代理代码实例
Object Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){ /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); final Constructor<?> cons = cl.getConstructor(constructorParams); return cons.newInstance(new Object[]{h}); }
- 由于Mybatis中的四大核心组件返回的都是代理对象,当代理对象调用方法的时候,底层要执行invoke方法,那么invoke方法怎么知道你当前调用的方法是否需要增强呢?
invoke方法执行逻辑
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //method.getDeclaringClass():表示根据当前调用的方法获取所在的类的class对象,获取到被拦截列表 //所以说当前这个Set集合存储的都是被拦截方法列表 Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass()); //判断存储方法的Set集合中是否包含被拦截的方法,如果有就调用intercept方法来进行方法的增强 //之后才进行原方法的执行,如果没有就执行这个代理类的目标代理方法,匹配参数 return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args); } catch (Exception var5) { throw ExceptionUtil.unwrapThrowable(var5); } }