• MyBatis是如何为Dao接口创建实现类的


    本文是我的MyBatis源码分析专栏中第三节的一小部分,作为试读部分,详细讲述了MyBatis是如何通过动态代理创建Dao接口的实现类的。
    专栏地址:MyBatis源码分析

    专栏字数:14w+
    专栏目录:在这里插入图片描述

    SqlSession.getMapper如何设计的?

     UserDAO userDAO =  SqlSession.getMapper(UserDAO.class);
        //UserDAO接口的实现类的对象 
        //疑问? UserDAO接口实现类 在哪里? 我们写的是xml文件呀? --> 这是因为动态字节码技术(Spring 的 AOP 也是用了动态字节码技术)
        //动态字节码技术 ---> 类 在JVM 运行时创建 ,JVM运行结束后,消失了 
    
    • 1
    • 2
    • 3
    • 4

    image-20221119115140118

    动态自己码技术如何创建的实现类呢?
    1. 如何 创建 UserDAO XXXDAO接口的实现类 
                 代理 (动态代理)应用场景:
                 a. 为原始对象(目标)增加【额外功能】 
                 b. 远程代理 1.网络通信 2.输出传输 (RPCDubbo 
                 c. 接口实现类,我们看不见实实在在的类文件,但是运行时却能体现出来。无中生有
                
    Proxy.newProxyIntance(ClassLoader,Interface,InvocationHandler)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    SqlSession.getMapper应用了代理模式:

    debugSqlSession.getMappe方法可以发现雀氏是个代理对象:

    image-20221119113858499

    实现类的逻辑
    interface UserDAO{
            List<User> queryAllUsers();         
            save(User user);
     }
                 
    
    UserDAOImpl implements UserDAO{
     queryAllUsers(){
          sqlSession.select("namespace.id",参数)
                            |-Excutor
                                |-StatementHandler
                                     |- ParameterHandler , ResultSetHandler
                                                  TypeHandler 
     }
     save(){
        sqlSession.insert("namespace.id",参数)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    手写Mapper接口的实现类代理

    来回顾一下动态代理设计模式:

    image-20221119114310496

    核心逻辑需要SqlSession来实现,SqlSession操作JDBC又需要statement,namespace.id: 可以通过接口class和方法名拿到。(接口的方法名不就是mapper标签中的id吗?)

    所以代理类的构造方法如下设计:

    public class MyMapperProxy implements InvocationHandler {
    
        private SqlSession sqlSession;
    
        private Class daoClass;
    
        public MyMapperProxy(SqlSession sqlSession,Class daoClass){
            this.sqlSession = sqlSession;
            this.daoClass = daoClass;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("namespace:"+daoClass.getName()+"."+method.getName());
            return sqlSession.selectList(daoClass.getName()+"."+method.getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    测试方法如下:

    @Test
        public void testPorxy() throws IOException {
            InputStream inputStream = Resources.getResourceAsStream("MyBatis/MyBatis-config.xml");
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(inputStream);
    
            SqlSession sqlSession = factory.openSession();
            Class[] interfaces = new Class[]{UserDao.class};
            //创建代理
            UserDao userDao = (UserDao) Proxy.newProxyInstance(
                    MybatisTest.class.getClassLoader(),
                    interfaces,
                    new MyMapperProxy(sqlSession,UserDao.class));
    
            List<User> users = userDao.queryAll();
            users.forEach(System.out::println);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20221119141053617

    那如果我们userDao中接口如果有参数呢?Dao中还会有很多方法,上面的例子只有一个方法。

    源码探究
      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);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • mapperProxyFactory.newInstance(sqlSession); 就相当于上面例子中 Proxy.newProxyInstance
    MapperProxyFactory核心代码分析:
    public class MapperProxyFactory<T> {
    
      //各种xxxDao的class文件
      private final Class<T> mapperInterface;
      //方法缓存
      private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
    
      public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      public Class<T> getMapperInterface() {
        return mapperInterface;
      }
    
      public Map<Method, MapperMethodInvoker> getMethodCache() {
        return methodCache;
      }
    
      @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy<T> mapperProxy) {
         //核心代码,创建代理,mapperProxy就是具体的代理实现
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
    
      public T newInstance(SqlSession sqlSession) {
        //相当于上面简单例子中的MyMapperProxy中的构造方法
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    MapperProxy

    看完了创建代理的工厂源码,下面该去看看添加的额外方法了:这里是关键代码,我们结合debug来看。

    MapperProxy的构造方法如下:

      public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        //方法缓存
        this.methodCache = methodCache;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最开始的方法缓存是空的:

    image-20221119151501346

    MapperProxy实现了InvocationHandler,所以我们核心看invoke方法的实现:

    public class MapperProxy<T> implements InvocationHandler, Serializable {
        
      // ....
      
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
           //如果是Object中的方法直接执行,例如toString、equals、wait...
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else {
             //核心代码,对dao接口中的方法做了封装,然后再invoke
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }
        // ....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    这里通过debug来看一下cachedInvoker(method)的作用:

      private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            //computeIfAbsent() 方法对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hashMap 中。
          return MapUtil.computeIfAbsent(methodCache, method, m -> {
            //判断是不是默认方法
            if (m.isDefault()) {
              try {
                if (privateLookupInMethod == null) {
                  return new DefaultMethodInvoker(getMethodHandleJava8(method));
                } else {
                  return new DefaultMethodInvoker(getMethodHandleJava9(method));
                }
              } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                  | NoSuchMethodException e) {
                throw new RuntimeException(e);
              }
            } else {
              //不是默认方法
              return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
          });
        } catch (RuntimeException re) {
          Throwable cause = re.getCause();
          throw cause == null ? re : cause;
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    debug发现执行完这个方法,methodCache中有了我们正在调用的userDao中的方法:

    image-20221119152920893

    那么这个mapperMethod又是什么呢?

    MapperMethod
    public class MapperMethod {
    
      private final SqlCommand command;
      private final MethodSignature method;
    
      public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, mapperInterface, method);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    image-20221119153853184

    实际上MapperMethod就是封装了两个非常核心的成员变量:

    • SqlCommand :sql命令

          private final String name; //标签id(namespace.id)
          private final SqlCommandType type;   //Sql命令类型:这条sql是查询还是插入?
      
      public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
            final String methodName = method.getName();
            final Class<?> declaringClass = method.getDeclaringClass();
            MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
                configuration);
            if (ms == null) {
              if (method.getAnnotation(Flush.class) != null) {
                name = null;
                type = SqlCommandType.FLUSH;
              } else {
                throw new BindingException("Invalid bound statement (not found): "
                    + mapperInterface.getName() + "." + methodName);
              }
            } else {
              //核心代码
              name = ms.getId();
              type = ms.getSqlCommandType();
              if (type == SqlCommandType.UNKNOWN) {
                throw new BindingException("Unknown execution method for: " + name);
              }
            }
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25

      image-20221119154607485

    • MethodSignature 方法签名(返回值、分页、参数…)

      public static class MethodSignature {
      
          private final boolean returnsMany;
          private final boolean returnsMap;
          private final boolean returnsVoid;
          private final boolean returnsCursor;
          private final boolean returnsOptional;
          private final Class<?> returnType;
          private final String mapKey;
          private final Integer resultHandlerIndex;
          private final Integer rowBoundsIndex;
          private final ParamNameResolver paramNameResolver;
      
          public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
            Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
            if (resolvedReturnType instanceof Class<?>) {
              this.returnType = (Class<?>) resolvedReturnType;
            } else if (resolvedReturnType instanceof ParameterizedType) {
              this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
            } else {
              this.returnType = method.getReturnType();
            }
            this.returnsVoid = void.class.equals(this.returnType);
            this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
            this.returnsCursor = Cursor.class.equals(this.returnType);
            this.returnsOptional = Optional.class.equals(this.returnType);
            this.mapKey = getMapKey(method);
            this.returnsMap = this.mapKey != null;
            this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
            this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
            //@param 中的参数名是如何解析的,在这一步
            this.paramNameResolver = new ParamNameResolver(configuration, method);
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33

      image-20221119155325082

    ParamNameResolver方法:处理@Param

    public ParamNameResolver(Configuration config, Method method) {
        this.useActualParamName = config.isUseActualParamName();
        final Class<?>[] paramTypes = method.getParameterTypes();
        final Annotation[][] paramAnnotations = method.getParameterAnnotations();
        final SortedMap<Integer, String> map = new TreeMap<>();
        int paramCount = paramAnnotations.length;
        // get names from @Param annotations
        for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
          if (isSpecialParameter(paramTypes[paramIndex])) {
            // skip special parameters
            continue;
          }
          String name = null;
          for (Annotation annotation : paramAnnotations[paramIndex]) {
            if (annotation instanceof Param) {
              hasParamAnnotation = true;
              name = ((Param) annotation).value();
              break;
            }
          }
          if (name == null) {
            // @Param was not specified.
            if (useActualParamName) {
              name = getActualParamName(method, paramIndex);
            }
            if (name == null) {
              // use the parameter index as the name ("0", "1", ...)
              // gcode issue #71
              name = String.valueOf(map.size());
            }
          }
          map.put(paramIndex, name);
        }
        names = Collections.unmodifiableSortedMap(map);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    分析到这里,我们已经将mapper.xml中的各种参数返回值都封装到了MapperMethod中,下面也就该找真正执行sqlSession的地方:

    invoke

    在MapperProxy类中,真正执行SqlSession相关操作的代码在mapperMethod.execute

        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          return mapperMethod.execute(sqlSession, args);
        }
    
    • 1
    • 2
    • 3
    • 4

    image-20221119160631450

    进入到execute方法中:这里就是我们熟悉的sqlSession方法:

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
          case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
          case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
          }
          case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
          }
          case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
              executeWithResultHandler(sqlSession, args);
              result = null;
            } else if (method.returnsMany()) {
              result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
              result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
              result = executeForCursor(sqlSession, args);
            } else {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = sqlSession.selectOne(command.getName(), param);
              if (method.returnsOptional()
                  && (result == null || !method.getReturnType().equals(result.getClass()))) {
                result = Optional.ofNullable(result);
              }
            }
            break;
          case FLUSH:
            result = sqlSession.flushStatements();
            break;
          default:
            throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
          throw new BindingException("Mapper method '" + command.getName()
              + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
  • 相关阅读:
    mysql主从复制搭建
    linux 下代码检查工具部署使用
    学不动系列-git-hooks和husky+lintstage
    四、cadence ic 617 ——添加工艺库文件
    第十九章、【Linux】开机流程、模块管理与Loader
    【洛谷题解】P2066 机器分配
    12-资源注解annotations和安全行下文securityContext(了解即可)
    SwiftUI 4.0 如何轻松在 iOS 16 中设置 TextEditor 背景色
    【C++】
    员工脉搏/脉动调查的5个好处
  • 原文地址:https://blog.csdn.net/Ares___/article/details/128087886