mybatis官方定义:MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

  mybatis的拦截器,一般用于针对数据库的一些通用操作处理,比如慢查耗时打印,压测场景影子表写入。用户需要使用拦截器的时候,通过实现Interceptor接口即可。拦截器的功能,不仅带来了切面编程的优势,还使用起来也很方便。那么mybatis具体是如何实现拦截器的呢?下面我们来一探究竟。以下所有分析均基于3.4.5版本。

1.拦截器初始化

  通过查看源码,我们可以发现,关于拦截器的代码,都放在了plugin包目录下,该目录下包含七个类:

  1. Intercepts:注解类,其value为Signature类数值,注解在Interceptor实现类上,表示实现类对哪些sql执行类(实现Executor)的哪些方法切入
  2. Signature:注解类,表示一个唯一的Interceptor实现类的一个方法,以及入参
  3. InterceptorChain:拦截器链表,用于初始化一个拦截器链
  4. Interceptor:拦截器接口
  5. Invocation:拦截衔接类,用于指向下一个拦截器或者sql执行类
  6. Plugin:拦截器实现辅助类
  7. PluginException:异常

  Intercepts和Signature,对于熟悉mybatis切面编程的同学都知道,是用户的Interceptor实现类注解。

  Intercepts的内部结构很简单就是Signature数组:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Intercepts {
    Signature[] value();
}

  Signature注解也比较简单,包含目标类,方法,入参类型数组,标识唯一一个方法

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {  // 目标类
    Class<?> type();
  // 方法
    String method();
  // 入参类型数组
    Class<?>[] args();
}

  InterceptorChain类的pluginAll方法是mybatis初始化的时候,初始化拦截器功能的入口方法

private final List<Interceptor> interceptors = new ArrayList();
// target是Executor实现类之一,所有sql语句执行都需要通过这些实现类生效
public Object pluginAll(Object target) {
        Interceptor interceptor;    // 遍历数组,调用每一个interceptor的plugin方法
        for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
            interceptor = (Interceptor)var2.next();
        }

        return target;
    }

  interceptor实现类(需要使用者编写)的plugin方法一个标准的实现如下:

@Override
    public Object plugin(Object target) {     // 直接调用
        return Plugin.wrap(target, this);
    }

  Plugin类wrap方法,Plugin实现InvocationHandler,用于JDK动态代理

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) {     // 根据Intercepor实现类的注解,获取Executor实现类各个需要拦截的方法,Map中的key是Executor实现类,value是类中需要拦截的方法集合
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();     // 遍历target类型的接口数值,因为target同一实现Executor接口,所以该数组长度为1,值类型为Executor.class
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);     // 根据是否需要代理,返回target代理类或者target
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        Intercepts interceptsAnnotation = (Intercepts)interceptor.getClass().getAnnotation(Intercepts.class);
        if (interceptsAnnotation == null) {
            throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        } else {
            Signature[] sigs = interceptsAnnotation.value();
            Map<Class<?>, Set<Method>> signatureMap = new HashMap();
            Signature[] var4 = sigs;
            int var5 = sigs.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                Signature sig = var4[var6];
                Set<Method> methods = (Set)signatureMap.get(sig.type());
                if (methods == null) {
                    methods = new HashSet();
                    signatureMap.put(sig.type(), methods);
                }

                try {
                    Method method = sig.type().getMethod(sig.method(), sig.args());
                    ((Set)methods).add(method);
                } catch (NoSuchMethodException var10) {
                    throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + var10, var10);
                }
            }

            return signatureMap;
        }
    }

    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        HashSet interfaces;
        for(interfaces = new HashSet(); type != null; type = type.getSuperclass()) {
            Class[] var3 = type.getInterfaces();
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Class<?> c = var3[var5];
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
        }

        return (Class[])interfaces.toArray(new Class[interfaces.size()]);
    }

  因此,我们可以看到,调用路径为:InterceptorChain.pulginAll --> Interceptor.plugin --> Pulgin.wrap,InterceptorChain.pulginAll的入参target和返回值经历了这样的一个过程:target --> 根据Intercepor实现类的注解是否包含本target,通过JDK动态代理返回Proxy或者target --> target --> 下一个Intercepor,这样一直遍历InterceptorChain,不断返回当前target的代理类或者直接返回target,在target包了一层又一层:

  

  最后返回的target就是就是不断代理的结果,而相邻代理之间通过Pulgin.wrap方法实现,wrap方法实现上调用了Proxy,也就是通过JDK的动态代理实现

2.sql执行

  以上是从初始化时,已pulginAll方法为切入点,看拦截器各个模块间的关系以及实现方式,下面从sql执行的角度看看。

  通过调试发现,执行的入口方法的Pulgin.invoke方法,当代理对象执行方法调用的时候,就会进入

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 Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {       // 获取所有需要拦截的方法,这里method.getDeclaringClass()的值为Executor.class
            Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());       // 判断当前方法是否需要拦截,需要拦截则调用interceptor实现类的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);
        }
    }

  ...
}

  Interceptor实现类一般会处理一下业务上需求,最后调用被代理类

@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
        @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 }) })
@Component
public class SqlMonitorManager implements Interceptor {
  private boolean showSql = true;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 这里是业务处理
        /****/
        // 调用proceed方法
        try {
            return invocation.proceed();
        } catch (Throwable e) {
            throw e;
        }
    }
  // 初始化时,可以指定属性值,这里配置了showSql
    @Override
    public void setProperties(Properties properties) {
        if (properties == null) {
            return;
        }
        if (properties.containsKey("show_sql")) {
            String value = properties.getProperty("show_sql");
            if (Boolean.TRUE.toString().equals(value)) {
                this.showSql = true;
            }
        }
    }

}

  intercept方法最后调用了invocation的proceed方法:

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 this.method.invoke(this.target, this.args);
    }
}

  其实从执行的调度就是从最外层的proxy,层层进入,最后调用target的方法执行sql,这与动态代理的使用也是类似的,存在调用路径为:

Proxy2.method --> Pulgin.invoke --> 是否方法拦截,如果是,掉用interceptor.intercept方法,最后调用被代理类方法,如果否,调用直接调用代理类方法啊 -->Proxy1.method,这样一直调用下去。调用流程图如下:

  

3.总结

  总的来说,mybatis拦截器提供了相对方便并且可控的切面编程支持,也是一种动态代理的一种最佳实践。通过嵌套代理,实现多个拦截器,通过传递被代理类方法以及入参,推迟并由用户决定被代理类的调用,从而实现拦截。

mybatis Interceptor拦截器代码详解的更多相关文章

  1. Mybatis Interceptor 拦截器原理 源码分析

    Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最 ...

  2. 拦截器 应用详解--SpringMVC

    在实际项目中,拦截器的使用是非常普遍的,例如在购物网站中通过拦截器可以拦截未登录的用户,禁止其购买商品,或者使用它来验证已登录用户是否有相应的操作权限等,Spring MVC提供了拦截器功能,通过配置 ...

  3. springboot拦截器HandlerInterceptor详解

    Web开发中,我们除了使用 Filter 来过滤请web求外,还可以使用Spring提供的HandlerInterceptor(拦截器). HandlerInterceptor 的功能跟过滤器类似,但 ...

  4. Spring 注解拦截器使用详解

    Spring mvc拦截器 平时用到的拦截器通常都是xml的配置方式.今天就特地研究了一下注解方式的拦截器. 配置Spring环境这里就不做详细介绍.本文主要介绍在Spring下,基于注解方式的拦截器 ...

  5. Quartz学习——SSMM(Spring+SpringMVC+Mybatis+Mysql)和Quartz集成详解(转)

    通过前面的学习,你可能大致了解了Quartz,本篇博文为你打开学习SSMM+Quartz的旅程!欢迎上车,开始美好的旅程! 本篇是在SSM框架基础上进行的. 参考文章: 1.Quartz学习——Qua ...

  6. Spring中的Interceptor 拦截器 专题

    spring-webmvc-4.3.14.RELEASE.jar org.springframework.web.servlet.DispatcherServlet#doDispatch /** * ...

  7. Mybatis之拦截器原理(jdk动态代理优化版本)

    在介绍Mybatis拦截器代码之前,我们先研究下jdk自带的动态代理及优化 其实动态代理也是一种设计模式...优于静态代理,同时动态代理我知道的有两种,一种是面向接口的jdk的代理,第二种是基于第三方 ...

  8. MyBatis实现拦截器分页功能

    1.原理 在mybatis使用拦截器(interceptor),截获所执行方法的sql语句与参数. (1)修改sql的查询结果:将原sql改为查询count(*) 也就是条数 (2)将语句sql进行拼 ...

  9. SpringMVC中使用Interceptor拦截器

    SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那 ...

随机推荐

  1. 9. JEB 1.5插件编写一

    2.0之后好像新增加了很多API,有所区别 本文采用Java+Eclipse作为开发环境 插件支持的语言  文档: jeb-1.5/doc/index.html 一.Hello World工程 1. ...

  2. java多线程详解(6)-线程间的通信wait及notify方法

    Java多线程间的通信 本文提纲 一. 线程的几种状态 二. 线程间的相互作用 三.实例代码分析 一. 线程的几种状态 线程有四种状态,任何一个线程肯定处于这四种状态中的一种:(1). 产生(New) ...

  3. Android图片处理-相机、相处简单调用

    安卓开发中,常常需要使用到手机相机拍照.或者相册上传头像等等.通过使用Intent,我们很方便地获得相机.相册里面的图片: 1.相机调用,通过设置File文件路径和文件名,可以将拍照得到的图片保存下来 ...

  4. TCP/IP详解 笔记八

    UDP协议 UDP是传输层协议,提供无连接不可靠的数据传输,其优点失效率高,确定确定是无序不可靠. 报文格式 UDP头部 TCP和UDP的端口号是独立的 UDP长度是指UDP数据报的总长度 UDP的校 ...

  5. FTP协议详解(转)

    FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”.用于Internet上的控制文件的双向传输.同时,它也是一个应用程序(Application ...

  6. scala学习笔记(8): 列表的map,flatMap,zip和reduce

    map,flatMap,zip和reduce函数可以让我们更容易处理列表函数. 1 map函数map将一个函数应用于列表的每一个元素并且将其作为一个新的列表返回.我们可以这样对列表的元素进行平方: s ...

  7. Nginx简单性能调优

    Nginx默认没有开启利用多核CPU (忍不住吐槽,然怪总感觉服务器性能没充分发挥), 我们可以通过增加worker_cpu_affinity配置参数来充分利用多核CPU.CPU是任务处理,计算最关键 ...

  8. linux管理员切换与管理员密码第一次设置

    在终端输入su - root回车来切回到超级管理员,Ubuntu的默认超级管理员root密码是随机的,即每次开机都有一个新的root密码.我们可以在终端输入命令 sudo passwd,然后输入当前用 ...

  9. Eclipse远程debug服务器

    一,找端口号 二,Eclipse配置 三,测试是否成功 四,结束远程debug

  10. 爬虫案例之Pubmed数据库下载

    代码 # encoding=utf-8 import os, time, re import urllib.request import urllib.parse import ssl ssl._cr ...