前文传送门:

mybatis源码学习:从SqlSessionFactory到代理对象的生成

mybatis源码学习:一级缓存和二级缓存分析

mybatis源码学习:基于动态代理实现查询全过程

一、自定义插件流程

  • 自定义插件,实现Interceptor接口。

  • 实现intercept、plugin和setProperties方法。

  • 使用@Intercepts注解完成插件签名。

  • 在主配置文件注册插件。

/**
* 自定义插件
* Intercepts:完成插件签名,告诉mybatis当前插件拦截哪个对象的哪个方法
*
* @author Summerday
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "parameterize", args = Statement.class)
})
public class MyPlugin implements Interceptor {
/**
* 拦截目标方法执行
*
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyPlugin.intercept getMethod: "+invocation.getMethod());
System.out.println("MyPlugin.intercept getTarget:"+invocation.getTarget());
System.out.println("MyPlugin.intercept getArgs:"+ Arrays.toString(invocation.getArgs()));
System.out.println("MyPlugin.intercept getClass:"+invocation.getClass());
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
} /**
* 包装目标对象,为目标对象创建一个代理对象
*
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
System.out.println("MyPlugin.plugin :mybatis将要包装的对象:"+target);
//借助Plugin类的wrap方法使用当前拦截器包装目标对象
Object wrap = Plugin.wrap(target, this);
//返回为当前target创建的动态代理
return wrap;
} /**
* 将插件注册时的properties属性设置进来
*
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的信息:" + properties);
}
}

xml配置注册插件

    <!--注册插件-->
<plugins>
<plugin interceptor="com.smday.interceptor.MyPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</plugin>
</plugins>

二、测试插件

三、源码分析

1、inteceptor在Configuration中的注册

关于xml文件的解析,当然还是需要从XMLConfigBuilder中查找,我们很容易就可以发现关于插件的解析:

  private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//获取到全类名
String interceptor = child.getStringAttribute("interceptor");
//获取properties属性
Properties properties = child.getChildrenAsProperties();
//通过反射创建实例
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
//设置属性
interceptorInstance.setProperties(properties);
//在Configuration中添加插件
configuration.addInterceptor(interceptorInstance);
}
}
} public void addInterceptor(Interceptor interceptor) {
//interceptorChain是一个存储interceptor的Arraylist
interceptorChain.addInterceptor(interceptor);
}

此时初始化成功,我们在配置文件中定义的插件,已经成功加入interceptorChain。

2、基于责任链的设计模式

我们看到chain这个词应该并不会陌生,我们之前学习过的过滤器也存在类似的玩意,什么意思呢?我们以Executor为例,当创建Executor对象的时候,并不是直接new Executor然后返回:

在返回之前,他进行了下面的操作:

executor = (Executor) interceptorChain.pluginAll(executor);

我们来看看这个方法具体干了什么:

  public Object pluginAll(Object target) {
//遍历所有的拦截器
for (Interceptor interceptor : interceptors) {
//调用plugin,返回target包装后的对象
target = interceptor.plugin(target);
}
return target;
}

很明显,现在它要从chain中一一取出interceptor,并依次调用各自的plugin方法,暂且不谈plugin的方法,我们就能感受到责任链的功能:让一个对象能够被链上的任何一个角色宠幸,真好。

3、基于动态代理的plugin

那接下来,我们就成功进入我们自定义plugin的plugin方法:

  //看看wrap方法干了点啥
public static Object wrap(Object target, Interceptor interceptor) {
//获取获取注解的信息,拦截的对象,拦截的方法,拦截方法的参数。
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
//获取当前对象的Class
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;
}

getSignatureMap(interceptor)方法:其实就是获取注解的信息,拦截的对象,拦截的方法,拦截方法的参数。

  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//定位到interceptor上的@Intercepts注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
//如果注解不存在,则报错
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
//获取@Signature组成的数组
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); for (Signature sig : sigs) {
//先看map里有没有methods set
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
//没有再创建一个
methods = new HashSet<Method>();
//class:methods设置进去
signatureMap.put(sig.type(), methods);
}
try {
//获取拦截的方法
Method method = sig.type().getMethod(sig.method(), sig.args());
//加入到set中
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}

getAllInterfaces(type, signatureMap)方法:确定是否为拦截对象

  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<Class<?>>();
while (type != null) {
//接口类型
for (Class<?> c : type.getInterfaces()) {
//如果确实是拦截的对象,则加入interfaces set
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
//从父接口中查看
type = type.getSuperclass();
}
//最后set里面存在的元素就是要拦截的对象
return interfaces.toArray(new Class<?>[interfaces.size()]);
}

我们就可以猜测,插件只会对我们要求的对象和方法进行拦截。

4、拦截方法的intercept(invocation)

确实,我们一路debug,遇到了Executor、ParameterHandler、ResultHandler都没有进行拦截,然而,当StatementHandler对象出现的时候,就出现了微妙的变化,当我们调用代理的方法必然会执行其invoke方法,不妨来看看:

ok,此时进入了我们定义的intercept方法,感觉无比亲切。

  //调度被代理对象的真实方法
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}

如果有多个插件,每经过一次wrap都会产生上衣个对象的代理对象,此处反射调用的方法也是上衣个代理对象的方法。接着,就还是执行目标的parameterize方法,但是当我们明白这些执行流程的时候,我们就可以知道如何进行一些小操作,来自定义方法的实现了。

四、插件开发插件pagehelper

插件文档地址:https://github.com/pagehelper/Mybatis-PageHelper

这款插件使分页操作变得更加简便,来一个简单的测试如下:

1、引入相关依赖

        <dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>

2、全局配置

    <!--注册插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

3、测试分页

    @Test
public void testPlugin(){
//查询第一页,每页3条记录
PageHelper.startPage(1,3);
List<User> all = userDao.findAll();
for (User user : all) {
System.out.println(user);
}
}

五、插件总结

参考:《深入浅出MyBatis技术原理与实战》

  • 插件生成地是层层代理对象的责任链模式,其中设计反射技术实现动态代理,难免会对性能产生一些影响。
  • 插件的定义需要明确需要拦截的对象、拦截的方法、拦截的方法参数。
  • 插件将会改变MyBatis的底层设计,使用时务必谨慎。

mybatis源码学习:插件定义+执行流程责任链的更多相关文章

  1. 浩哥解析MyBatis源码(一)——执行流程

    原创作品,可以转载,但是请标注出处地址: 一.MyBatis简介 MyBatis框架是一种轻量级的ORM框架,当下十分流行,配合Spring+Spring MVC组成SSM框架,能够胜任几乎所有的项目 ...

  2. Mybatis源码手记-从缓存体系看责任链派发模式与循环依赖企业级实践

    一.缓存总览 Mybatis在设计上处处都有用到的缓存,而且Mybatis的缓存体系设计上遵循单一职责.开闭原则.高度解耦.及其精巧,充分的将缓存分层,其独到之处可以套用到很多类似的业务上.这里将主要 ...

  3. Mybatis源码学习第六天(核心流程分析)之Executor分析

    今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...

  4. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  5. MyBatis 源码篇-插件模块

    本章主要描述 MyBatis 插件模块的原理,从以下两点出发: MyBatis 是如何加载插件配置的? MyBatis 是如何实现用户使用自定义拦截器对 SQL 语句执行过程中的某一点进行拦截的? 示 ...

  6. mybatis源码学习:一级缓存和二级缓存分析

    目录 零.一级缓存和二级缓存的流程 一级缓存总结 二级缓存总结 一.缓存接口Cache及其实现类 二.cache标签解析源码 三.CacheKey缓存项的key 四.二级缓存TransactionCa ...

  7. mybatis源码学习(一) 原生mybatis源码学习

    最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...

  8. mybatis源码学习:基于动态代理实现查询全过程

    前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...

  9. Yii2 源码分析 入口文件执行流程

    Yii2 源码分析  入口文件执行流程 1. 入口文件:web/index.php,第12行.(new yii\web\Application($config)->run()) 入口文件主要做4 ...

随机推荐

  1. DOM 操作成本到底高在哪儿?

    从我接触前端到现在,一直听到的一句话:操作DOM的成本很高,不要轻易去操作DOM.尤其是React.vue等MV*框架的出现,数据驱动视图的模式越发深入人心,jQuery时代提供的强大便利地操作DOM ...

  2. flex布局你真的搞懂了吗?通俗简洁,小白勿入~

    flex布局 用以代替浮动的布局手段: 必须先把一个元素设置为弹性容器://display:flex: 一个元素可以同时是弹性容器和弹性元素; 设为flex布局以后,子元素的float.clear和v ...

  3. CVE-2020-2551复现过程

    项目地址 cnsimo/CVE-2020-2551 CVE-2020-2551 weblogic iiop 反序列化漏洞 该项目的搭建脚本在10.3.6版本经过测试,12版本未测试. 环境 kali+ ...

  4. linux中的bash

    一.bash的简介 操作系统都是需要通过shell跟内核来交互的,常见的shell有GUI.KDE.sh.csh.bash.tsh.zsh等. 而linux中最常用的shell就是bash. 二.ba ...

  5. 原生js焦点轮播图的实现

    继续学习打卡,武汉加油,逆战必胜!今日咱们主要探讨一下原生js写轮播图的问题, 简单解析一下思路: 1,首先写好css样式问题 2,考虑全局变量:自动播放的定时器,以及记录图片位置的角标Index 2 ...

  6. 【Jenkins】使用 Jenkins REST API 配合清华大学镜像站更新 Jenkins 插件

    自从去年用上了 Jenkins 进行 CI/CD 之后,工作效率高了不少,摸鱼的时间更多了.不过 Jenkins 好是好,但在功夫网的影响下,插件就是经常更新不成功的,就像下面这样子: 查了不少资料, ...

  7. XXE白盒审计 PHP

    XXE与XML注入的区别 https://www.cnblogs.com/websecurity-study/p/11348913.html XXE又分为内部实体和外部实体.我简单区分为内部实体就是自 ...

  8. Zookeeper是如何实现分布式锁的

    [toc] Zookeeper是如何实现分布式锁的 标签 : Zookeeper 分布式 实现分布式锁要考虑的重要问题 1. 三个核心要素 加锁, 解锁, 锁超时 2. 三个问题 要保证原子性操作, ...

  9. webpack-bundle-analyzer打包文件分析工具

    一.安装 npm intall webpack-bundle-analyzer –save-dev 二.配置 在build/webpack.prod.config.js中的module.exports ...

  10. jmeter执行多条sql语句

    1.JDBC Connection Configuration 在配置DataBase URL的时候,加上allowMultiQueries=true 2.在JDBC Request中设置Quer T ...