zoukankan      html  css  js  c++  java
  • Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取

    在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题:

      1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper?

      2,UserDao明明是我们定义的一个接口类,根本没有定义实现类,那这个userMapper是什么?是mybatis自动为我们生成的实现类吗?

      为了更好的解释着两个问题,我们需要重新认识Configuration这个类。

      但是在这之前,你需要了解一个概念(设计模式):JAVA设计模式-动态代理(Proxy)示例及说明。否则你可能对接下来的流程一头雾水。

    一,再次认识Configuration

    public class Configuration {
      //映射注册表
      protected MapperRegistry mapperRegistry = new MapperRegistry(this);
    
      // 获取映射注册表
      public MapperRegistry getMapperRegistry() {
        return mapperRegistry;
      }
    
      //添加到映射注册表
      public void addMappers(String packageName, Class<?> superType) {
        mapperRegistry.addMappers(packageName, superType);
      }
      //添加到映射注册表
      public void addMappers(String packageName) {
        mapperRegistry.addMappers(packageName);
      }
      //添加到映射注册表
      public <T> void addMapper(Class<T> type) {
        mapperRegistry.addMapper(type);
      }
      //从映射注册表中获取
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
      }
      //判断映射注册表中是否存在
      public boolean hasMapper(Class<?> type) {
        return mapperRegistry.hasMapper(type);
      }
    }

      MapperRegistry源码:

    public class MapperRegistry {
    
      private Configuration config;
      //映射缓存 键:类对象,值:映射代理工厂
      private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
    
      public MapperRegistry(Configuration config) {
        this.config = config;
      }
      //从映射注册表中获取  
      @SuppressWarnings("unchecked")
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null)
          throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        try {
          return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }
      //判断映射注册表中是否存在  
      public <T> boolean hasMapper(Class<T> type) {
        return knownMappers.containsKey(type);
      }
      //添加到映射注册表
      public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
          if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            // 处理接口类(例如UserDao)中的注解
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    
      /**
       * @since 3.2.2
       */
      //获取缓存中所有的Key,并且是不可修改的
      public Collection<Class<?>> getMappers() {
        return Collections.unmodifiableCollection(knownMappers.keySet());
      }
    
      /**
       * @since 3.2.2
       */
      //添加到映射注册表
      public void addMappers(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        for (Class<?> mapperClass : mapperSet) {
          addMapper(mapperClass);
        }
      }
    
      /**
       * @since 3.2.2
       */
      //添加到映射注册表
      public void addMappers(String packageName) {
        addMappers(packageName, Object.class);
      }
    }

      在方法getMappers中用到了Collections.unmodifiableCollection(knownMappers.keySet());,如果你不了解,可以查阅:Collections.unmodifiableMap,Collections.unmodifiableList,Collections.unmodifiableSet作用及源码解析

      在了解了这两个类之后,就来解决第一个问题:1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper?

    二,addMapper和getMapper

      1,关于addMapper,在文章Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析中,不知道大家有没有意识到,我少了一个部分没有解读:

      

      对了,就是第四部分的:绑定已经解析的命名空间

      代码:bindMapperForNamespace();

      是的,addMapper就是在这个方法中用到的。但是前提是,你需要了解java的动态代理。来看看源码:

    private void bindMapperForNamespace() {
        //获取当前命名空间(String:com.zcz.learnmybatis.dao.UserDao)
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
          Class<?> boundType = null;
          try {
            // 使用类加载器加载,加载类,获取类对象
            boundType = Resources.classForName(namespace);
          } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
          }
          if (boundType != null) {
            //判断映射注册表中是否存在
            if (!configuration.hasMapper(boundType)) {
              // Spring may not know the real resource name so we set a flag
              // to prevent loading again this resource from the mapper interface
              // look at MapperAnnotationBuilder#loadXmlResource
              // 添加到已经解析的缓存
              configuration.addLoadedResource("namespace:" + namespace);
              // 添加到映射这测表
              configuration.addMapper(boundType);
            }
          }
        }
      }

      看到了吧,就在最后一行代码。但是这里并不是简单的保存了一个类对象,而是在MapperRegistry中进行了进一步的处理:

     1 //添加到映射注册表
     2   public <T> void addMapper(Class<T> type) {
     3     if (type.isInterface()) {
     4       if (hasMapper(type)) {
     5         throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
     6       }
     7       boolean loadCompleted = false;
     8       try {
     9      // 在这里,保存的是new MapperProxyFactory实例对象。
    10         knownMappers.put(type, new MapperProxyFactory<T>(type));
    11         // It's important that the type is added before the parser is run
    12         // otherwise the binding may automatically be attempted by the
    13         // mapper parser. If the type is already known, it won't try.
    14         // 处理接口类(例如UserDao)中的注解
    15         MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
    16         parser.parse();
    17         loadCompleted = true;
    18       } finally {
    19         if (!loadCompleted) {
    20           knownMappers.remove(type);
    21         }
    22       }
    23     }
    24   }

      在第10行,保存的是映射代理工厂(MapperProxyFactory)的实例对象。到这里addMapper就解释清楚了。接下来看看getMapper方法。

      2,getMapper

        调用的地方:在文章Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码第三步中。

        代码:configuration.<T>getMapper(type, this);

          type:是UserDao.class

          this:SqlSession的实例化对象

        从第一部分Configuration中可以发现,Configuration又调用了MapperRegistry的getMapper方法

     1 //从映射注册表中获取  
     2   @SuppressWarnings("unchecked")
     3   public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
     4     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
     5     if (mapperProxyFactory == null)
     6       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
     7     try {
     8       return mapperProxyFactory.newInstance(sqlSession);
     9     } catch (Exception e) {
    10       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    11     }
    12   }

      从代码的第4行可以清晰的看到,或者根据类对象从缓存Map中,获取到了addMapper中保存的MapperProxyFactory对象实例。但是并没有将这个对象实例直接返回,而是通过调用的MapperProxyFactory的newInstance方法返回的一个UserDao实现类。接下来我们i就解释一下第二个问题:2,UserDao明明是我们定义的一个接口类,根本没有定义实现类,那这个userMapper是什么?是mybatis自动为我们生成的实现类吗?

    三,映射代理类的实现

      看过JAVA设计模式-动态代理(Proxy)示例及说明这篇文章的同学应该知道这个问题的答案了,userMapper是一个代理类对象实例。是通过映射代理工厂(MapperProxyFactory)的方法newInstance方法获取的。

      但是在这里mybatis有一个很巧妙的构思,使得这个的动态代理的使用方法和文章JAVA设计模式-动态代理(Proxy)示例及说明中的使用方法有些许不同。

      不妨在你的脑海中回顾一下JAVA设计模式-动态代理(Proxy)示例及说明中实现动态代理的关键因素:

        1,一个接口

        2,实现了接口的类

        3,一个调用处理类(构造方法中要传入2中的类的实例对象)

        4,调用Proxy.newProxyInstance方法获取代理类实例化对象

      带着这个印象,我们来分析一下mybatis是怎么实现动态代理的。既然userMapper是通过映射代理工厂(MapperProxyFactory)生产出来的,那么我们就看看它的源码: 

     1 //映射代理工厂  
     2 public class MapperProxyFactory<T> {
     3   // 接口类对象(UserDao.class)    
     4   private final Class<T> mapperInterface;
     5   // 对象中的方法缓存
     6   private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
     7 
     8   //构造器
     9   public MapperProxyFactory(Class<T> mapperInterface) {
    10     //为接口类对象赋值
    11     this.mapperInterface = mapperInterface;
    12   }
    13 
    14   public Class<T> getMapperInterface() {
    15     return mapperInterface;
    16   }
    17 
    18   public Map<Method, MapperMethod> getMethodCache() {
    19     return methodCache;
    20   }
    21   
    22   // 实例化映射代理类
    23   @SuppressWarnings("unchecked")
    24   protected T newInstance(MapperProxy<T> mapperProxy) {
    25     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    26   }
    27   
    28   // 实例化映射代理类
    29   public T newInstance(SqlSession sqlSession) {
    30     final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    31     return newInstance(mapperProxy);
    32   }
    33 
    34 }

      我们调用的newInstance方法就是第29行的方法。然后这个方法又调用了24行的方法。我们来看25行的代码,是不是很熟悉?Proxy.newProxyInstance(类加载器,接口类对象数组,实现了InvocationHandler的对象实例 mapperProxy),到这里映射代理类的实例化已经解释清楚了,也就是解决了第二个问题,接下来我们扩展一下:

      现在我们还没有看到MapperProxy类的源码,但是我们可以大胆的猜测,类MapperProxy一定是实现了InvocationHandler接口,并且也一定实现了Invoke方法:

     1 // 映射代理  
     2 public class MapperProxy<T> implements InvocationHandler, Serializable {
     3   private static final long serialVersionUID = -6424540398559729838L;
     4   private final SqlSession sqlSession;
     5   private final Class<T> mapperInterface;
     6   private final Map<Method, MapperMethod> methodCache;
     7   
     8   //构造方法
     9   public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    10     this.sqlSession = sqlSession;
    11     this.mapperInterface = mapperInterface;
    12     this.methodCache = methodCache;
    13   }
    14   //代理类调用的时候执行的方法
    15   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    16     // 检查Method方法所在的类是否是Object
    17     if (Object.class.equals(method.getDeclaringClass())) {
    18       try {
    19         return method.invoke(this, args);
    20       } catch (Throwable t) {
    21         throw ExceptionUtil.unwrapThrowable(t);
    22       }
    23     }
    24     // 应用缓存
    25     final MapperMethod mapperMethod = cachedMapperMethod(method);
    26     //执行查询
    27     return mapperMethod.execute(sqlSession, args);
    28   }
    29   
    30   //应用缓存
    31   private MapperMethod cachedMapperMethod(Method method) {
    32     MapperMethod mapperMethod = methodCache.get(method);
    33     if (mapperMethod == null) {
    34       mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
    35       methodCache.put(method, mapperMethod);
    36     }
    37     return mapperMethod;
    38   }
    39 }

      那么,mybatis使用动态代理的方式跟文章JAVA设计模式-动态代理(Proxy)示例及说明使用动态代理的方式,有哪些不同呢?

      1,一个接口(UserDao)

      2,实现了接口的类(没有)

      3,一个调用处理类(构造方法中要传入2中的类的实例对象)(2中没有,自然这里也不会传入对象)

      4,调用Proxy.newProxyInstance方法获取代理类实例化对象(有的)

      

      如果你实实在在的明白了JAVA设计模式-动态代理(Proxy)示例及说明这篇文中所叙述的内容,相信这篇文章不难理解。

      好了,mybatis源码解析到这里,已经基本接近尾声了,继续探索吧:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码


    原创不易,转载请声明出处:https://www.cnblogs.com/zhangchengzi/p/9706395.html 

  • 相关阅读:
    泛微云桥e-Bridge 目录遍历,任意文件读取
    (CVE-2020-8209)XenMobile-控制台存在任意文件读取漏洞
    selenium 使用初
    将HTML文件转换为MD文件
    Python对word文档进行操作
    使用java安装jar包出错,提示不是有效的JDK java主目录
    Windows server 2012安装VM tools异常解决办法
    ifconfig 命令,改变主机名,改DNS hosts、关闭selinux firewalld netfilter 、防火墙iptables规则
    iostat iotop 查看硬盘的读写、 free 查看内存的命令 、netstat 命令查看网络、tcpdump 命令
    使用w uptime vmstat top sar nload 等命令查看系统负载
  • 原文地址:https://www.cnblogs.com/zhangchengzi/p/9706395.html
Copyright © 2011-2022 走看看