zoukankan      html  css  js  c++  java
  • mybaits源码分析--binding模块(五)

    一、binding模块

    接下来我们看看在org.apache.ibatis.binding包下提供的Binding模块 ,binding其实在执行sqlSession.getMapper(UserMapper.class);获取接口代理的对象时有用到;


    发现这个包里面提供的工具比较少,就几个,先来分别了解下他们的作用,然后在串联起来。

    1.1 MapperRegistry

    这显然是一个注册中心,这个注册中是用来保存MapperProxyFactory对象的,所以这个注册器中提供的功能肯定是围绕MapperProxyFactory的添加和获取操作,来看看具体的代码逻辑
    成员变量:

     private final Configuration config;
      // 记录 Mapper 接口和 MapperProxyFactory 之间的关系
      private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    addMapper方法

      public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) { // 检测 type 是否为接口
          if (hasMapper(type)) { // 检测是否已经加装过该接口
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            // !Map<Class<?>, MapperProxyFactory<?>> 存放的是接口类型,和对应的工厂类的关系
            knownMappers.put(type, new MapperProxyFactory<>(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.
    
            // 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }

    getMapper方法

      /**
       * 获取Mapper接口对应的代理对象
       */
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // 获取Mapper接口对应的 MapperProxyFactory 对象
        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);
        }
      }

    通过这个方法本质上获取的就是Mapper接口的代理对象。

    1.2 MapperProxyFactory

    MapperProxyFactory是一个工厂对象,专门负责创建MapperProxy对象。其中核心字段的含义和功能如下:

    /**
     * 负责创建 MapperProxy 对象
     * @author Lasse Voss
     */
    public class MapperProxyFactory<T> {
    
      /**
       * MapperProxyFactory 可以创建 mapperInterface 接口的代理对象
       *     创建的代理对象要实现的接口
       */
      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")
      /**
       * 创建实现了 mapperInterface 接口的代理对象
       */
      protected T newInstance(MapperProxy<T> mapperProxy) {
        // 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
    }

    1.3 MapperProxy

    通过MapperProxyFactory创建的MapperProxy是Mapper接口的代理对象,实现了InvocationHandler接口,通过前面讲解的动态代理模式,那么这部分的内容就很简单了。

    /**
     * Mapper 代理对象
     * @author  
     * @author  
     */
    public class MapperProxy<T> implements InvocationHandler, Serializable {
    
      private static final long serialVersionUID = -4724728412955527868L;
      private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
          | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
      private static final Constructor<Lookup> lookupConstructor;
      private static final Method privateLookupInMethod;
      private final SqlSession sqlSession; // 记录关联的 SqlSession对象
      private final Class<T> mapperInterface; // Mapper接口对应的Class对象
      // 用于缓存MapperMethod对象,key是Mapper接口方法对应的Method对象,value是对应的MapperMethod对象。‘
      // MapperMethod对象会完成参数转换以及SQL语句的执行
      // 注意:MapperMethod中并不会记录任何状态信息,可以在多线程间共享
      private final Map<Method, MapperMethodInvoker> methodCache;
    
      public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
      }
    
      static {
        Method privateLookupIn;
        try {
          privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
        } catch (NoSuchMethodException e) {
          privateLookupIn = null;
        }
        privateLookupInMethod = privateLookupIn;
    
        Constructor<Lookup> lookup = null;
        if (privateLookupInMethod == null) {
          // JDK 1.8
          try {
            lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
            lookup.setAccessible(true);
          } catch (NoSuchMethodException e) {
            throw new IllegalStateException(
                "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
                e);
          } catch (Exception e) {
            lookup = null;
          }
        }
        lookupConstructor = lookup;
      }
    
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          // toString hashCode equals getClass等方法,无需走到执行SQL的流程
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else {
            // 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
            // 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }
    
      private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
          // Java8 中 Map 的方法,根据 key 获取值,如果值是 null,则把后面Object 的值赋给 key
          // 如果获取不到,就创建
          // 获取的是 MapperMethodInvoker(接口) 对象,只有一个invoke方法
          // 根据method 去methodCache中获取 如果返回空 则用第二个参数填充
          return methodCache.computeIfAbsent(method, m -> {
            if (m.isDefault()) {
              // 接口的默认方法(Java8),只要实现接口都会继承接口的默认方法,例如 List.sort()
              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 {
              // 创建了一个 MapperMethod
              return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
          });
        } catch (RuntimeException re) {
          Throwable cause = re.getCause();
          throw cause == null ? re : cause;
        }
      }
    
      private MethodHandle getMethodHandleJava9(Method method)
          throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        final Class<?> declaringClass = method.getDeclaringClass();
        return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
            declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
            declaringClass);
      }
    
      private MethodHandle getMethodHandleJava8(Method method)
          throws IllegalAccessException, InstantiationException, InvocationTargetException {
        final Class<?> declaringClass = method.getDeclaringClass();
        return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
      }
    
      interface MapperMethodInvoker {
        Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
      }
    
      private static class PlainMethodInvoker implements MapperMethodInvoker {
        private final MapperMethod mapperMethod;
    
        public PlainMethodInvoker(MapperMethod mapperMethod) {
          super();
          this.mapperMethod = mapperMethod;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          // SQL执行的真正起点
          return mapperMethod.execute(sqlSession, args);
        }
      }
    
      private static class DefaultMethodInvoker implements MapperMethodInvoker {
        private final MethodHandle methodHandle;
    
        public DefaultMethodInvoker(MethodHandle methodHandle) {
          super();
          this.methodHandle = methodHandle;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          return methodHandle.bindTo(proxy).invokeWithArguments(args);
        }
      }
    }

    1.4 MapperMethod

    MapperMethod中封装了Mapper接口中对应方法的信息,以及SQL语句的信息,可以把MapperMethod看成是配置文件中定义的SQL语句和Mapper接口的桥梁。

     属性和构造方法

      // statement id (例如:com.gupaoedu.mapper.BlogMapper.selectBlogById) 和 SQL 类型
      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.4.1 SqlCommand

    SqlCommand是MapperMethod中定义的内部类,记录了SQL语句名称以及对应的类型(UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH)

    1.4.2 MethodSignature

    MethodSignature也是MapperMethod的内部类,在其中封装了Mapper接口中定义的方法相关信息。

      private final boolean returnsMany; // 判断返回是否为 Collection类型或者数组类型
        private final boolean returnsMap; // 返回值是否为 Map类型
        private final boolean returnsVoid; // 返回值类型是否为 void
        private final boolean returnsCursor; // 返回值类型是否为 Cursor 类型
        private final boolean returnsOptional; // 返回值类型是否为 Optional 类型
        private final Class<?> returnType; // 返回值类型
        private final String mapKey; // 如果返回值类型为 Map  则 mapKey 记录了作为 key的 列名
        private final Integer resultHandlerIndex; // 用来标记该方法参数列表中 ResultHandler 类型参数的位置
        private final Integer rowBoundsIndex; // 用来标记该方法参数列表中 rowBounds 类型参数的位置
        private final ParamNameResolver paramNameResolver; // 该方法对应的 ParamNameResolver 对象

    构造方法中完成了相关信息分初始化操作

      /**
         * 方法签名
         * @param configuration
         * @param mapperInterface
         * @param method
         */
        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;
          // getUniqueParamIndex 查找指定类型的参数在 参数列表中的位置
          this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
          this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
          this.paramNameResolver = new ParamNameResolver(configuration, method);
        }

    getUniqueParamIndex的主要作用是 查找指定类型的参数在参数列表中的位置

        /**
         * 查找指定类型的参数在参数列表中的位置
         * @param method
         * @param paramType
         * @return
         */
        private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
          Integer index = null;
          // 获取对应方法的参数列表
          final Class<?>[] argTypes = method.getParameterTypes();
          // 遍历
          for (int i = 0; i < argTypes.length; i++) {
            // 判断是否是需要查找的类型
            if (paramType.isAssignableFrom(argTypes[i])) {
              // 记录对应类型在参数列表中的位置
              if (index == null) {
                index = i;
              } else {
                // RowBounds 和 ResultHandler 类型的参数只能有一个,不能重复出现
                throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
              }
            }
          }
          return index;
        }

    1.4.3 execute方法

    最后来看下再MapperMethod中最核心的方法execute方法,这个方法完成了数据库操作

      public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) { // 根据SQL语句的类型调用SqlSession对应的方法
          case INSERT: {
            // 通过 ParamNameResolver 处理args[] 数组 将用户传入的实参和指定参数名称关联起来
            Object param = method.convertArgsToSqlCommandParam(args);
            // sqlSession.insert(command.getName(), param) 调用SqlSession的insert方法
            // rowCountResult 方法会根据 method 字段中记录的方法的返回值类型对结果进行转换
            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()) {
              // 返回值为空 且 ResultSet通过 ResultHandler处理的方法
              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);
              // 普通 select 语句的执行入口 >>
              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.4.4 核心流程串联

    首先在映射文件加载解析的位置,XMLMapperBuilder.parse位置

      public void parse() {
        // 总体上做了两件事情,对于语句的注册和接口的注册
        if (!configuration.isResourceLoaded(resource)) {
          // 1、具体增删改查标签的解析。
          // 一个标签一个MappedStatement。 >>
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          // 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。
          // 一个namespace 一个 MapperProxyFactory >>
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
      }

    在bindMapperForNamespace中会完成Mapper接口的注册并调用前面介绍过的addMapper方法然后就是在我们执行

    // 4.通过SqlSession中提供的 API方法来操作数据库
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> list = mapper.selectUserList();

    这两行代码的内部逻辑,首先看下getMapper方法

     public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type, this);
      }
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // mapperRegistry中注册的有Mapper的相关信息 在解析映射文件时 调用过addMapper方法
        return mapperRegistry.getMapper(type, sqlSession);
      }

    然后就是从MapperRegistry中获取对应的MapperProxyFactory对象。

      /**
       * 获取Mapper接口对应的代理对象
       */
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // 获取Mapper接口对应的 MapperProxyFactory 对象
        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);
        }
      }

    然后根据MapperProxyFactory对象获取Mapper接口对应的代理对象。

      /**
       * 创建实现了 mapperInterface 接口的代理对象
       */
      protected T newInstance(MapperProxy<T> mapperProxy) {
        // 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }

    不清楚可以看前面画的时序图

     然后我们再来看下调用代理对象中的方法执行的顺序

    List<User> list = mapper.selectUserList();

    会进入MapperProxy的Invoker方法中

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          // toString hashCode equals getClass等方法,无需走到执行SQL的流程
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else {
            // 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
            // 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }

    然后进入PlainMethodInvoker中的invoke方法

        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          // SQL执行的真正起点
          return mapperMethod.execute(sqlSession, args);
        }
      }

    然后会进入到 MapperMethod的execute方法中

     public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) { // 根据SQL语句的类型调用SqlSession对应的方法
          case INSERT: {
            // 通过 ParamNameResolver 处理args[] 数组 将用户传入的实参和指定参数名称关联起来
            Object param = method.convertArgsToSqlCommandParam(args);
            // sqlSession.insert(command.getName(), param) 调用SqlSession的insert方法
            // rowCountResult 方法会根据 method 字段中记录的方法的返回值类型对结果进行转换
            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()) {
              // 返回值为空 且 ResultSet通过 ResultHandler处理的方法
              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);
              // 普通 select 语句的执行入口 >>
              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;
      }

    之后就会根据对应的SQL类型而调用SqlSession中对应的方法来执行操作

    这短短的一生我们最终都会失去,不妨大胆一点,爱一个人,攀一座山,追一个梦
  • 相关阅读:
    【html+table】基于jQuery,利用TableFreeze.js实现html的table冰冻效果,非常强大
    vue中使用element-ui实现excel表格导入
    正则第二次配结果与第一次不一致;正则匹配,第一次匹配结果正常,第二次匹配结果就错误,第三次又正常,第四次又错误,以此类推
    关于用vsCode格式化代码时,代码自动换行问题
    百度地图实现测量面积和测量距离功能
    sql 通过group by、sum聚合查询结果的排序
    C#小数计算的坑
    C# 反射的例子
    IE8环境下的上传图片预览
    做移动端电子签名发现canvas的 一些坑
  • 原文地址:https://www.cnblogs.com/xing1/p/15207676.html
Copyright © 2011-2022 走看看