zoukankan      html  css  js  c++  java
  • mybatis Interceptor拦截器代码详解

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

  • 相关阅读:
    sql中not exists的用法
    jsp中target="_blank"的用法
    jsp 运用 session 登录输出
    jsp留言板
    编写JSP 实现用户登录并判断用户或密码
    jsp打印九九乘法表
    jsp输出当前时间
    jsp输出金字塔
    jsp输出5的阶乘
    jdbc练习3
  • 原文地址:https://www.cnblogs.com/blueSkyline/p/10178992.html
Copyright © 2011-2022 走看看