mybatis源码解析,一步一步从浅入深:映射代理类的获取

张橙子      2022-04-13     540

关键词:

在文章: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 

由浅入深理解latentdiffusion/stablediffusion:一步一步搭建自己的stablediffusionmodels

...和读者一起遨游diffusionmodels的世界!本文主要介绍带大家一步步搭建自己的stablediffusi 查看详情

ansible一步一步从入门到精通

一:安装ansiblemac:1.安装Homebrew(gettheinstallationcommandfromtheHomebrewwebsite).2.安装Python2.7.x(brewinstallpython).3.安装Ansible(sudopipinstallansible).linux:如果系统中安装了python-pip和python-devel,你可以使用pip安装ansib 查看详情

ansible一步一步从入门到精通

一:本地基础测试环境搭建使用vmware或者virtualbox创建一个linux虚拟机(我的是centos6.6),关闭iptables和selinux将上面的服务器地址加入上一篇bolg的hosts文件中exampegroup中同样配置ssh秘钥验证二:你的第一个playbook新建ntp.yml如下:---&nb... 查看详情

ansible一步一步从入门到精通上

一:一个简单的Playbookplaybook比起shell脚本的优势,是幂等性,值得是运行一次和多次的结果都是一样的,不会对系统有影响一个简单的playbook:  1 ---  2 - hosts:  all  3   tasks: ... 查看详情

java由浅入深开发企业级电商项目大牛实战开发电商后台项目实战视频教程

...还介绍下课程安排,最后会讲解一下高大上的架构是如何一步一步从一台服务器演变到高性能、高并发、高可用架构的过程并讲解在这过程中大型架构演进思想以及代码演进细节。第2章开发环境安装与配置讲解、实操(linux平台... 查看详情

从浅入深剖析angular表单验证

最近手上维护的组件剩下的BUG都是表单验证,而且公司的表单验证那块代码经历的几代人,里面的逻辑开始变得不清晰,而且代码结构不是很angular。是很有必要深入了解表单验证。<bodyng-controller="MainController"><formname="form"n... 查看详情

spark从浅入深(第一集)

1.Spark是什么ApacheSpark是用于大规模数据处理的统一分析引擎。它提供Java、Scala、Python和R中的高级API,以及支持通用执行图的优化引擎。它还支持一组丰富的高级工具,包括用于SQL和结构化数据处理的SparkSQL、用于机器学... 查看详情

从浅入深学习springaop(代码片段)

代理模式学习什么是AOP面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。SpringAop动态代理JDK动态代理:InvocationHandler、ProxyCGLIB动态代理:MethodInterceptor、EnhancerAOP重要术语-1切面aspect:... 查看详情

带你一步一步的解析arouter源码(代码片段)

...构进行开发,通过ARouter实现了页面的跳转,之前看它的源码时忘了写笔记,因此今天来重新对它的源码进行一次分析。(顺手留下GitHub链接,需要获取相关面试或者面试宝典核心笔记PDF等内容的可以自己去找)https://github.com/xian... 查看详情

从 React Native init 一步一步地 React Native Web

】从ReactNativeinit一步一步地ReactNativeWeb【英文标题】:Reactnativewebstepbystepfromreactnativeinit【发布时间】:2021-07-2304:59:04【问题描述】:谁能给我一步一步从一个新的reactnative项目安装reactnativeweb?1)初始化反应原生2)npminstallreact-domrea... 查看详情

防抖节流从简单到复杂,一步一步从入门到深入了解(代码片段)

防抖与节流防抖场景初版代码如下:进阶版代码如下箭头函数的写法:直接使用function函数的写法:终极版代码如下:节流场景:时间戳版节流定时器版节流定时器版节流与防抖方法代码逻辑详解说明:写... 查看详情

python爬虫解析利器xpath,由浅入深快速掌握(附源码例子)(代码片段)

...验四、lxml加载HTML文本五、巧用浏览器开发工具六、教程源码总结写在后面前言咱们在爬虫的时候,通常能够把目标网页给请求到。并把整个网页的内容拿到。然而并不是整个网页的数据都是我们需要的。我们的目标数据可... 查看详情

mybatis增删改查(步骤详细,由浅入深,适合初学者,只看这一篇就够了)(代码片段)

MyBatis目录java(后端框架)MyBatis的使用1.mybatis基本使用2.mybatis工具类的封装和实现增删改查3.mybatis中主要类的介绍4.nybatis实现动态代理:(使用的是反射机制,重点掌握)5.mybatis参数传递:6.$和#的区别(重点掌握)7.ResultType的使用... 查看详情

一步一步解读refresh源码(代码片段)

解读refreshrefresh()1.prepareRefresh2.obtainFreshBeanFactoryBeanDefinition测试(读取Bean方式)1.xml方式读取2.组件扫描4.编程方式(Bean构建器)3.prepareBeanFactory4.postProcessBeanFactory5.invokeBeanFa 查看详情

recyclerview源码学习:一步一步自定义layoutmanager

前言这篇文章实现了一个简单的LayoutManager,重点在于从原理方面一步一步去了解如何自定义一个LayoutManager。麻雀虽小,但五脏俱全。从自定义LayoutManager的layout布局,到scroll滑动,再到添加简单的动画效果。其实&... 查看详情

大数据spark从浅入深(第一集)(代码片段)

1.Spark是什么ApacheSpark是用于大规模数据处理的统一分析引擎。它提供Java、Scala、Python和R中的高级API,以及支持通用执行图的优化引擎。它还支持一组丰富的高级工具,包括用于SQL和结构化数据处理的SparkSQL、用于机器学... 查看详情

读懂源码:一步一步实现一个vue

源码阅读:究竟怎样才算是读懂了?市面上有很多源码分析的文章,就我看到的而言,基本的套路就是梳理流程,讲一讲每个模块的功能,整篇文章有一大半都是直接挂源码。我不禁怀疑,作者真的看懂了吗?为什么我看完后还... 查看详情

一步一步学习springaop概述

1.啥是AOP在软件业,AOP为AspectOrientedProgramming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP采取横向抽取机制,解决公共功能的重复性代码(性能监视、事务管理、安全... 查看详情