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 

  • 相关阅读:
    sessionStorage用于分页,瀑布流和存储用户数据等
    js瀑布流
    sql 日结
    js 去除html标签
    c# 去除文本的html标签
    jQuery 数据滚动(上下)
    jQuery 图片随滚动条滚动加载
    sql 指定范围 获取随机数
    js 时间格式化
    js自写字符串 append 方法
  • 原文地址:https://www.cnblogs.com/zhangchengzi/p/9706395.html
Copyright © 2011-2022 走看看