mybatis插件机制
mybatis插件机制
mybatis的插件机制使用动态代理实现,不了解的朋友请先了解代理模式和动态代理;插件本质是功能增强,那么它如果需要对某个方法进行增强,首先要拦截这个方法,其实也就类似于拦截器,mybatis的插件在代码中定义为Interceptor,也就是拦截器;后面统一称作拦截器;
主要 类/接口 和 方法
- Interceptor
方法 | 主要功能 |
---|---|
Object intercept(Invocation invocation) | 拦截器功能具体实现 |
Object plugin(Object target) | 为被代理类生成代理对象 |
void setProperties(Properties properties) | 获取自定义配置 |
- InterceptorChain
方法 | 主要功能 |
---|---|
public Object pluginAll(Object target) | 生成代理对象,通过代理的方式注入拦截器功能 |
public void addInterceptor(Interceptor interceptor) | 注册插件 |
...getInterceptors() | 获取一个不可修改的拦截器集合 |
- Plugin
方法 | 主要功能 |
---|---|
public static Object wrap(Object target, Interceptor interceptor) | 使用动态代理生成代理对象 |
public Object invoke(Object proxy, Method method, Object[] args) | 调用拦截器的intercept方法或者直接调用被代理对象的当前方法 |
...getSignatureMap(Interceptor interceptor) | 根据拦截器上的注解反射获取目标方法 |
mybatis插件机制实现
InterceptorChain中维护了一个拦截器列表,也就说所有的拦截器都要在这里注册;
InterceptorChain中关键的一个方法是pluginAll:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
pluginAll方法中是调用了Interceptor的plugin方法,
这个plugin方法是一个覆写方法,需要插件开发者自己实现,但是Mybatis已经提供了相应的实现:
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
打开Plugin这个工具类发现它实现了InvocationHandler接口,再看wrap方法:
public static Object wrap(Object target, Interceptor interceptor) {
//获取此拦截器想要代理的目标方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//判断是否是否实现了接口,如果是则使用jdk动态代理生成代理对象,否则返回原对象
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
实际上就是使用jdk动态代理为target创建了一个代理对象,并且这个被代理的对象必须是一个接口的实现类(jdk动态代理必须有接口),否则不会生成代理对象,也就是说拦截器只能拦截接口的方法;既然是动态代理肯定要看一下invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
//如果当前执行的方法是拦截器想要拦截的方法则执行拦截器intercept方法否则,执行原方法;
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);
}
}
可以看到invoke方法中signatureMap是否有存在当前调用的Method决定了是否调用Interceptor的intercept方法,也就是说Interceptor只对signatureMap中的方法生效,那么再来看signatureMap是怎么来的:
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//获取拦截器Intercepts注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
if (interceptsAnnotation == null) { // issue #251
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
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) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
到这里可以看到,Interceptor需要拦截的方法通过@Intercepts注解申明,这里根据注解中的方法签名(类类型,方法名,方法参数)反射获取具体方法并添加到signatureMap中;Interceptor中的Intercepts注解就是定义需要拦截的方法集合;
那么再回过头看InterceptorChain的pluginAll方法,方法内遍历所有的拦截器并调用plugin方法,实际上就是每个插件都是一层代理,通过多层代理来绑定多个插件;换句话说,某个对象要想被InterceptorChain中的拦截器拦截,那么此对象必须经过InterceptorChain的pluginAll(Object target)方法的包装,当然由于jdk动态代理的关系必须是接口对象;
比如mybatis分页插件:
@SuppressWarnings({"rawtypes", "unchecked"})
@Intercepts(
{
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class PageInterceptor implements Interceptor {
//缓存count查询的ms
protected Cache<CacheKey, MappedStatement> msCountMap = null;
private Dialect dialect;
private String default_dialect_class = "com.github.pagehelper.PageHelper";
private Field additionalParametersField;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//分页逻辑
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
//获取配置
}
}
通过上文我们知道它需要拦截Executor的query方法,Executor是mybatis运行sql的核心组件,之所以能够被拦截是因为:
在Configuration类中创建Executor之后,有这样一句代码:
executor = (Executor) interceptorChain.pluginAll(executor);
完~
mybatis插件机制的更多相关文章
- mybatis插件机制原理
mybatis插件机制及分页插件原理 参考链接:mybatis插件机制及分页插件原理 如何编写一个自定义mybatis插件 参考链接:mybatis 自定义插件的使用
- mybatis插件机制及分页插件原理
MyBatis 插件原理与自定义插件: MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能.需要注意的是,如果没有完全理解MyBatis 的运行原理和插件的工作方式 ...
- MyBatis(八):MyBatis插件机制详解
MyBatis插件插件机制简介 MyBatis插件其实就是为使用者提供的自行拓展拦截器,主要是为了可以更好的满足业务需要. 在MyBatis中提供了四大核心组件对数据库进行处理,分别是Exec ...
- Mybatis插件机制以及PageHelper插件的原理
首先现在已经有很多Mybatis源码分析的文章,之所以重复造轮子,只是为了督促自己更好的理解源码. 1.先看一段PageHelper拦截器的配置,在mybatis的配置文件<configurat ...
- MyBatis 源码分析 - 插件机制
1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...
- 精尽MyBatis源码分析 - 插件机制
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- MyBatis7:MyBatis插件及示例----打印每条SQL语句及其执行时间
Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用 Executor(update.q ...
- MyBatis 插件 : 打印 SQL 及其执行时间
Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用: Executor(update. ...
- MyBatis插件及示例----打印每条SQL语句及其执行时间
Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用 Executor(update.q ...
随机推荐
- 深入理解JVM(一)——基本原理
前言 JVM一直是java知识里面进阶阶段的重要部分,如果希望在java领域研究的更深入,则JVM则是如论如何也避开不了的话题,本系列试图通过简洁易读的方式,讲解JVM必要的知识点. 运行流程 我们都 ...
- PopupWindow 以及拍照、裁剪
实现这样的效果 圆角图片的自定义控件直接拷进来,和com一个等级 想要弹出内容可以使用悬浮窗 layout_pupup <LinearLayout xmlns:android="htt ...
- 吴恩达机器学习笔记16-决策边界(decision boundary)
现在讲下决策边界(decision boundary)的概念.这个概念能更好地帮助我们理解逻辑回归的假设函数在计算什么. 在逻辑回归中,我们预测:当ℎ
- Kali学习笔记22:缓冲区溢出漏洞利用实验
实验机器: Kali虚拟机一台(192.168.163.133) Windows XP虚拟机一台(192.168.163.130) 如何用Kali虚拟机一步一步“黑掉”这个windowsXP虚拟机呢? ...
- SVM算法简单应用
第一部分:线性可分 通俗解释:可以用一条直线将两类分隔开来 一个简单的例子,直角坐标系中有三个点,A,B点为0类,C点为1类: from sklearn import svm # 三个点 x = [[ ...
- 根据sockfd获取TCP连接本地地址以及对端地址
摘抄自muduo代码实现. 获取本地地址(LocalAddr): struct sockaddr_in6 sockets::getLocalAddr(int sockfd) { struct sock ...
- 【转】CSS中的浮动和清除浮动
以下转自<CSS中的浮动和清除浮动,梳理一下!> 浮动到底是什么? 浮动核心就一句话:浮动元素会脱离文档流并向左/向右浮动,直到碰到父元素或者另一个浮动元素.请默念3次! 浮动最初设计的目 ...
- (转)p解决 java.util.prefs.BackingStoreException 报错问题
原文:https://blog.csdn.net/baidu_32739019/article/details/78405444 https://developer.ibm.com/answers/q ...
- Redis(3)---Redis事务
Redis事务 Redis 通过 MULTI .EXEC. DISCARD 和 WATCH 四个命令来实现事务功能. MULTI :标记一个事务块的开始. EXEC: 执行所有事务块内的命令. DI ...
- sql server 备份与恢复系列三 简单恢复模式下的备份与还原
一.概述 前面讲了备份的一些理论知识,这篇开始讲在简单恢复模式下的备份与还原.在简单模式下是不能做日志备份的,发生灾难后,数据库最后一次备份之后做的数据修改将是全部丢失的,所以在生产环境下,数据又很重 ...