1.Mybatis3的插件其实主要是用到了责任链和动态代理两种模式相结合而生成的。下面我们看一个例子,在执行所有update操作时,执行一个小小的测试输出。

@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties; @Override
public Object intercept(Invocation invocation) throws Throwable {
//do something
System.out.println(properties.getProperty("pluginProperty"));
return invocation.proceed();
} @Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} @Override
public void setProperties(Properties properties) {
this.properties = properties;
} public Properties getProperties() {
return properties;
} }
  <plugins>
<plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
<property name="pluginProperty" value="hello plugin"/>
</plugin>
</plugins>

输出如上图,这么看上去插件开发其实蛮简单的,我们继续往下看。

2.Interceptor拦截器

Interceptor接口,我们看名字,都能知道他是个拦截器。拦截器,其实我们先不看代码,拦截器的存在无外呼这两点:他要拦截什么?拦截下来要做什么?,这么一看,我们是不是敏感的察觉到动态代理的味道?

下面看下他的具体方法。

public interface Interceptor {

  //具体的拦截器业务逻辑
Object intercept(Invocation invocation) throws Throwable; //生成代理对象
default Object plugin(Object target) {
return Plugin.wrap(target, this);
} default void setProperties(Properties properties) {
// NOP
} }

Interceptor的注册我们其实在之前的Configuration解析中已经提过了,这边再重温下。

private void parseConfiguration(XNode root) {
try {
...
//解析plugins节点(注册interceptorChain里记录对应的拦截器)
pluginElement(root.evalNode("plugins"));
...
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
  private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}

代码很简单,就不解释了,主要就是解析xml中Interceptor,反射实例化生成放入configuration中属性interceptorChain中。

下面我们看下这个interceptorChain,从字面上我们就知道他是个拦截链,也就是个拦截器的集合。

public class InterceptorChain {

  private final List<Interceptor> 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<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
} }

这里面最重要的就是pluginAll方法,他主要是调用plugin生成代理对象。我们看下pluginAll方法到底有哪些地方用到了?

也就是对应的我们到底要代理哪些对象,也就是回答之前的问题,我们到底要拦截哪些方法?

  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
//又见到老朋友插件的过滤器链
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
} public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
//又见到老朋友插件的过滤器链
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
} public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//根据不同的type生成不同的StatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//又见到老朋友插件的过滤器链
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
} public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
} public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//典型的工厂模式,三种类型的executor
if (ExecutorType.BATCH == executorType) {
//批量执行器
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
//可重用执行器(用到了享元模式,用到statementMap来缓存同一生命周期内的statement。)
executor = new ReuseExecutor(this, transaction);
} else {
//默认的简单执行器
executor = new SimpleExecutor(this, transaction);
}
//开启二级缓存的话,再外面再包一层CachingExecutor,装饰者模式
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//插件机制,其实就是一个动态代理的拦截器链,用到了一个责任链模式
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

总结一波,一共代理了四种对象:

  • ParameterHandler(拦截参数)
  • ResultSetHandler(拦截结果集)
  • StatementHandler(拦截Statement)
  • Executor(拦截Executor执行器)

也就对应的我们之前的拦截器头上的注解type:

3.Plugin

下面我们看下是如何具体生成代理对象的。

public interface Interceptor {
...
//生成代理对象
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
...
}
public class Plugin implements InvocationHandler {
//被代理的对象
private final Object target;
//拦截器
private final Interceptor interceptor;
//拦截器注解上被拦截的类和方法签名
private final Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
} //对目标对象生成代理对象
public static Object wrap(Object target, Interceptor interceptor) {
//根据interceptor上面定义的注解,获取需要拦截的信息
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;
}
//代理逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//拿出signatureMap中对应的methods集合
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
//当前执行的方法是否在拦截器中
if (methods != null && methods.contains(method)) {
//执行具体的拦截器业务逻辑(譬如之前的"hello plugin")
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
//根据interceptor上面定义的注解,获取需要拦截的信息
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//取得注解信息
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251(取不到对应注解报错)
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<>();
for (Signature sig : sigs) {
//生成signatureMap,key=sig.type(),value=Methods集合
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
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;
} //返回需要拦截的接口信息,一直往上查
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
//拦截器中支持的接口
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[0]);
} }

注释写的很清楚,基本上这一切都串上来了。

4.Invocation

最后就生下一个问题,拦截器链的具体实现。譬如:

  <plugins>
<plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
<property name="pluginProperty" value="hello plugin"/>
</plugin>
<plugin interceptor="org.apache.ibatis.builder.Example2Plugin">
<property name="pluginProperty" value="hello plugin2"/>
</plugin>
</plugins>

首先所以我们发现对代理的对象,我们是有一个多次代理的操作。

public class InterceptorChain {
... public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
...
}
  //代理逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//拿出signatureMap中对应的methods集合
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
//当前执行的方法是否在拦截器中
if (methods != null && methods.contains(method)) {
//执行具体的拦截器业务逻辑(譬如之前的"hello plugin")
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
public class Invocation {
//被代理对象
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 proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
} }

上面的Invocation对应的proceed就是形成链的关键方法,他是返回上次代理的处理方法。所以在所有自定义的Interceptor的处理逻辑的最后一句都会返回这个方法,以此来调用上层的拦截逻辑。

此时的target就是上一层的Plugin对象。

所以综上所述,如果有多个拦截器,他是一个嵌套的过程,被代理的对象是上次生成的代理对象,以此类推,形成一个链条的形式。

Mybatis3源码笔记(七)Plugin的更多相关文章

  1. Tomcat8源码笔记(七)组件启动Server Service Engine Host启动

    一.Tomcat启动的入口 Tomcat初始化简单流程前面博客介绍了一遍,组件除了StandardHost都有博客,欢迎大家指文中错误.Tomcat启动类是Bootstrap,而启动容器启动入口位于 ...

  2. Mybatis3源码笔记(一)环境搭建

    1. 源码下载 地址:https://github.com/mybatis/mybatis-3.git. 国内访问有时确实有点慢,像我就直接先fork.然后从git上同步到国内的gitte上,然后在i ...

  3. Mybatis3源码笔记(八)小窥MyBatis-plus

    前言 Mybatis-Plus是一个 MyBatis增强工具包,简化 CRUD 操作,在 MyBatis 的基础上只做增强不做改变,为简化开发.提高效率而生,号称无侵入,现在开发中比较常用,包括我自己 ...

  4. Mybatis3源码笔记(六)SqlSession执行过程

    前几篇大致分析了初始化的过程,今天打算走一个SqlSession具体执行过程. @Test void shouldSelectAllAuthors() { try (SqlSession sessio ...

  5. Mybatis3源码笔记(四)Configuration(续)

    1.pluginElement(root.evalNode("plugins")) 解析plugins节点(注册interceptorChain里记录对应的拦截器) private ...

  6. Mybatis3源码笔记(五)mapperElement

    1.四种解析mapper方式 : package,resource,url,class. <mappers> <mapper resource="org/apache/ib ...

  7. Mybatis3源码笔记(三)Configuration

    1. XMLConfigBuilder 上一篇大致介绍了SqlSession的生成.在DefaultSqlSessionFactory的构造函数中就提到了Configuration这个对象.现在我们来 ...

  8. Mybatis3源码笔记(二)SqlSession

    1. 核心层次 2. SqlSession 先从顶层的SqlSession接口开始说起.SqlSession是MyBatis提供的面向用户的API,表示和数据库的会话对象,用于完成对数据库的一系列CR ...

  9. Tomcat8源码笔记(三)Catalina加载过程

    之前介绍过 Catalina加载过程是Bootstrap的load调用的  Tomcat8源码笔记(二)Bootstrap启动 按照Catalina的load过程,大致如下: 接下来一步步分析加载过程 ...

随机推荐

  1. MySQL5.7.29 和 Navicat ===> windows窗口式按装和使用

    MySQL windows窗口式按装下载方法:官网: https://www.mysql.com/ ==> DOWNLOADS ==> MySQL Community (GPL) Down ...

  2. oracle startup startup nomount startup mount 的区别

    startup nomount选项启动实例,但不安装 数据库.当数据库以这个模式启动时,参数文件被读取:后台进程和内存结构被启动:但它们不被附加或与数据库的磁盘结构进行通信.当实例处于这个状态时sta ...

  3. WPF -- 自定义按钮

    本文介绍WPF一种自定义按钮的方法. 实现效果 使用图片做按钮背景: 自定义鼠标进入时效果: 自定义按压效果: 自定义禁用效果 实现效果如下图所示: 实现步骤 创建CustomButton.cs,继承 ...

  4. window下象MAC一样工作的工具

    前面是MAC 后面是windows对应工具,只是做一个列表说明,具体使用自行百度 1.item2 vs Cmder 命令行 2.Homebrew vs Chocolatey 包管理器 3.Spotli ...

  5. DCL之单例模式

    所谓的DCL 就是 Double Check Lock,即双重锁定检查,在了解DCL在单例模式中如何应用之前,我们先了解一下单例模式.单例模式通常分为"饿汉"和"懒汉&q ...

  6. 一个页面中多个window.onload = function(){}冲突问题解决思路

    转: 一个页面中多个window.onload = function(){}冲突问题解决思路 一个页面中多个window.onload = function(){}冲突问题解决思路 参考文章: (1) ...

  7. vue打开新窗口并且实现传参,有图有真相

    我要实现的功能是打开一个新窗口用来展示新页面,而且需要传参数,并且参数不能显示在地址栏里面,而且当我刷新页面的时候,传过来的参数不能丢失,要一直存在,除非我手动关闭这个新窗口,即浏览器的标签页. 通过 ...

  8. web服务器-并发服务器2

    阅读目录 1.Web静态服务器-5-非堵塞模式 2.Web静态服务器-6-epoll 3.Web静态服务器-7-gevent版 4.知识扩展-C10K问题 一.Web静态服务器-5-非堵塞模式 单进程 ...

  9. EurekaServer源码分析

    Eureka Server功能 接受服务注册 接受服务心跳 服务剔除 服务下线 集群同步 获取注册表中服务实例信息 需要注意的是,Eureka Server同时也是一个Eureka Client,在不 ...

  10. 『力荐汇总』这些 VS Code 快捷键太好用,忍不住录了这34张gif动图

    之前写过三篇文章,收获了极其不错的阅读量与转发量: 你真的会用 VS Code 的 Ctrl.Shift和Alt吗?高效易用的快捷键:多光标.跳转引用等轻松搞定 VS Code 中的 Vim 操作 | ...