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

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

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

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

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

一,再次认识Configuration

  1. public class Configuration {
  2. //映射注册表
  3. protected MapperRegistry mapperRegistry = new MapperRegistry(this);
  4.  
  5. // 获取映射注册表
  6. public MapperRegistry getMapperRegistry() {
  7. return mapperRegistry;
  8. }
  9.  
  10. //添加到映射注册表
  11. public void addMappers(String packageName, Class<?> superType) {
  12. mapperRegistry.addMappers(packageName, superType);
  13. }
  14. //添加到映射注册表
  15. public void addMappers(String packageName) {
  16. mapperRegistry.addMappers(packageName);
  17. }
  18. //添加到映射注册表
  19. public <T> void addMapper(Class<T> type) {
  20. mapperRegistry.addMapper(type);
  21. }
  22. //从映射注册表中获取
  23. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  24. return mapperRegistry.getMapper(type, sqlSession);
  25. }
  26. //判断映射注册表中是否存在
  27. public boolean hasMapper(Class<?> type) {
  28. return mapperRegistry.hasMapper(type);
  29. }
  30. }

  MapperRegistry源码:

  1. public class MapperRegistry {
  2.  
  3. private Configuration config;
  4. //映射缓存 键:类对象,值:映射代理工厂
  5. private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
  6.  
  7. public MapperRegistry(Configuration config) {
  8. this.config = config;
  9. }
  10. //从映射注册表中获取
  11. @SuppressWarnings("unchecked")
  12. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  13. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  14. if (mapperProxyFactory == null)
  15. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  16. try {
  17. return mapperProxyFactory.newInstance(sqlSession);
  18. } catch (Exception e) {
  19. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  20. }
  21. }
  22. //判断映射注册表中是否存在
  23. public <T> boolean hasMapper(Class<T> type) {
  24. return knownMappers.containsKey(type);
  25. }
  26. //添加到映射注册表
  27. public <T> void addMapper(Class<T> type) {
  28. if (type.isInterface()) {
  29. if (hasMapper(type)) {
  30. throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  31. }
  32. boolean loadCompleted = false;
  33. try {
  34. knownMappers.put(type, new MapperProxyFactory<T>(type));
  35. // It's important that the type is added before the parser is run
  36. // otherwise the binding may automatically be attempted by the
  37. // mapper parser. If the type is already known, it won't try.
  38. // 处理接口类(例如UserDao)中的注解
  39. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  40. parser.parse();
  41. loadCompleted = true;
  42. } finally {
  43. if (!loadCompleted) {
  44. knownMappers.remove(type);
  45. }
  46. }
  47. }
  48. }
  49.  
  50. /**
  51. * @since 3.2.2
  52. */
  53. //获取缓存中所有的Key,并且是不可修改的
  54. public Collection<Class<?>> getMappers() {
  55. return Collections.unmodifiableCollection(knownMappers.keySet());
  56. }
  57.  
  58. /**
  59. * @since 3.2.2
  60. */
  61. //添加到映射注册表
  62. public void addMappers(String packageName, Class<?> superType) {
  63. ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
  64. resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  65. Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  66. for (Class<?> mapperClass : mapperSet) {
  67. addMapper(mapperClass);
  68. }
  69. }
  70.  
  71. /**
  72. * @since 3.2.2
  73. */
  74. //添加到映射注册表
  75. public void addMappers(String packageName) {
  76. addMappers(packageName, Object.class);
  77. }
  78. }

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

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

二,addMapper和getMapper

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

  

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

  代码:bindMapperForNamespace();

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

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

  看到了吧,就在最后一行代码。但是这里并不是简单的保存了一个类对象,而是在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

Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取的更多相关文章

  1. Mybatis源码解析,一步一步从浅入深(一):创建准备工程

    Spring SpringMVC Mybatis(简称ssm)是一个很流行的java web框架,而Mybatis作为ORM 持久层框架,因其灵活简单,深受青睐.而且现在的招聘职位中都要求应试者熟悉M ...

  2. Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码

    在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能 ...

  3. Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)

    在上一篇文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码 ,中我们看到 代码:XMLConfigBuilder parser = new XMLConfigBuilder(read ...

  4. Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例

    在Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化.而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅 ...

  5. Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析

    在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...

  6. Mybatis源码解析,一步一步从浅入深(七):执行查询

    一,前言 我们在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码的最后一步说到执行查询的关键代码: result = sqlSession.selectOne(command.ge ...

  7. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  8. Mybatis源码解析-DynamicSqlSource和RawSqlSource的区别

    XMLLanguageDriver是ibatis的默认解析sql节点帮助类,其中的方法其会调用生成DynamicSqlSource和RawSqlSource这两个帮助类,本文将对此作下简单的简析 应用 ...

  9. Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

    Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的?   如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...

随机推荐

  1. idea实现第一个springboot程序

    1.环境准备 JDK:1.8 Apache Maven: 3.6.1 IntelliJ IDEA 2019.1.3 x64 SpringBoot 1.5.9.RELEASE:1.5.9: 1.1.MA ...

  2. Leetcode solution 124: Binary Tree Maximum Path Sum

    Problem Statement Given a non-empty binary tree, find the maximum path sum. For this problem, a path ...

  3. java 中 size() 和 length()

    偶然发现自己不清楚 java size() 和length()是干嘛用的,总结一下: 1.java中的length()方法是针对字符串String说的,如果想看这个字符串的长度则用到length()这 ...

  4. RabbitMQ的各个参数

    简介 原文:https://blog.csdn.net/vbirdbest/article/details/78670550 本节主要讨论队列声明的各个参数 queueDeclare(String q ...

  5. Windows内核原理-同步IO与异步IO

    目录 Windows内核原理-同步IO与异步IO 背景 目的 I/O 同步I/O 异步I/O I/O完成通知 总结 参考文档 Windows内核原理-同步IO与异步IO 背景 在前段时间检查异常连接导 ...

  6. UVA 10395 素数筛

    Twin Primes Twin primes are pairs of primes of the form (p; p + 2). The term \twin prime" was c ...

  7. Azure DevOps vsts-agent-linux 安装出错, Must not run with sudo

    在linux安装 vsts-agent-linux 在vsts-agent-linux的解压目录运行./config.sh, 提示"Must not run with sudo", ...

  8. requests.get爬虫模块参数

    地址和请求头参数--url和header res = requests.get(url,headers=headers) 向网站发起请求,并获取响应对象 参数 url :需要抓取的URL地址 head ...

  9. HTML连载34-背景关联和缩写以及插图图片和背景图片的区别

    一.背景属性缩写的格式 1.backgound:背景颜色  背景图片  平铺方式  关联方式  定位方式 2.注意点: 这里的所有值都可以省略,但是至少需要一个 3.什么是背景关联方式 默认情况下,背 ...

  10. volatile、Synchronized实现变量可见性的原理,volatile使用注意事项

    变量不可见的两个原因 Java每个线程工作都有一个工作空间,需要的变量都是从主存中加载进来的.Java内存模型如下(JMM): 线程访问一个共享的变量时,都需要先从主存中加载一个副本到自己的工作内存中 ...