Mybatis(五): binding模块详解

今天这是学习Mybatis的第五篇文章,在今天的文章中我们学习下binding模块。这个模块是用来将Mapper与其映射文件进行绑定。

这个模块位于org.apache.ibatis.binding包下,核心的类有如下四个

  • MapperRegistry 映射器注册器
  • MapperProxyFactory 映射器代理类工厂
  • MapperProxy 映射器代理类
  • MapperMethod 映射器方法类

在今天的文章中,我们就针对上述的四个类进行介绍。

1 MapperRegistry

MapperRegistry是映射器注册器,这个类用来记录Mapper类与MapperProxyFactory的映射关系。该类的属性字段如下:

// Configuration对象   mybatis的配置信息都会解析后存储到该对象里
private final Configuration config;
// 记录Mapper接口与MapperProxyFactory的对应关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
复制代码

这里的Configuration对象是Mybatis中的一个核心类,Mybatis中相关的配置解析后都会记录到这个对象中,在后面的文章我们详细的介绍这个类。这里就不展开说明了。

在这个类中有addMappergetMapper两个核心的方法,分别用于注册和获取映射器,接下来我们分别看下这两个方法的逻辑。

1.1 addMapper()方法

addMapper方法用来注册映射器,将其添加到knownMappers中,如果大家对Mybatis(二): 执行流程的内容还有印象的话,我们在那篇文章的Mapper映射文件的解析时提到过这个方法,忘了也没关系,我们今天再详细的学习下,这个方法的源码如下:

public <T> void addMapper(Class<T> type) {
    // type为接口
    if (type.isInterface()) {
        // 判断是否已经加载过
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        // 成功加载标记
        boolean loadCompleted = false;
        try {
            // 添加到knowMappers中
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // 留到之后的builder模块中进行介绍
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            // 如果发生异常,需要移除该mapper
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}
复制代码

在该类中除了单个Mapper的注册方法,还提供了根据包来注册的方法,逻辑如下:

public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
}
​
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // 通过反射找到父类为superType的类
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    // 遍历进行注册
    for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}
复制代码

1.2 getMapper()方法

在我们前面的示例代码中,当进行数据操作时我们会先调用SqlSession.getMapper方法获取到Mapper对象,这个方法的逻辑就是调用到MapperRegistry.getMapper方法,这个方法的逻辑如下:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 查找指定type对应的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 {
        // 这里是用了JDK的动态代理 生成了Mapper的代理对象  MapperProxy
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}
复制代码

2 MapperProxyFactory

MapperProxyFactory是映射器代理类的对象工厂,看到这个名称应该很容易想到工厂模式,这个类的主要功能就是用来创建映射器代理对象。该类的属性如下:

// 该代理对象所属的Mapper接口Class对象
private final Class<T> mapperInterface;
// 存储了Mapper中方法和MapperMethodInvoker的关系
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
复制代码

这个类中创建映射器代理对象的方法如下:

public T newInstance(SqlSession sqlSession) {
    // 创建MapperProxy对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}
​
protected T newInstance(MapperProxy<T> mapperProxy) {
    // 创建实现了mapperInterface接口的代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
复制代码

通过上面的代码我们发现getMapper获取的是一个通过JDK动态代理生成的Mapper接口的代理对象。这也是为什么我们在使用Mybatis时只需要定义Mapper接口而不需要定义他的实现的原因。

3 MapperProxy

MapperProxy是映射器代理类,这个类实现了InvocationHandler接口,在JDK动态代理中代理类需要实现这个接口并实现其中的invoke方法。

这个类的属性字段如下:

// SqlSession对象
private final SqlSession sqlSession;
// Mapper接口的Class对象
private final Class<T> mapperInterface;
// Mapper接口中的方法和MapperMethodInvoker的对应关系
private final Map<Method, MapperMethodInvoker> methodCache;
复制代码

MapperMethodInvokerMapperProxy中的一个内部接口,其有两个实现类,源码如下:

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 {
        // 执行MapperMethod中的execute方法
        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);
    }
}
复制代码

当我们调用Mapper中的方法时,会调用到MapperProxy.invoke方法,这个方法的逻辑如下:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 如果目标方法继承自object则直接执行
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}
​
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        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 {
                // 返回PlainMethodInvoker对象  这里维护了MapperMethod的对象
                return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
        });
    } catch (RuntimeException re) {
        Throwable cause = re.getCause();
        throw cause == null ? re : cause;
    }
}
复制代码

通过这段代码分析,我们自己定义到Mapper中方法,最终是调用的PlainMethodInvoker.invoke方法,而这个方法会调用MapperMethod.excute方法。接下来我们看看MapperMethod这个类。

4 MapperMethod

MapperMethod中记录了Mapper中的方法信息,以及对应的映射文件中的SQL语句信息,这个类的属性如下:

// SqlCommand对象  记录SQL语句的名称和类型
private final SqlCommand command;
// Mapper接口中的方法信息
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);
}
复制代码

在构造方法中就是为两个属性进行赋值,接下来我们先看看SqlCommandMethodSignature两个类。

4.1 SqlCommand

SqlCommand中记录了Mapper中方法的名称和类型,其属性如下:

// 名称  组成方式    Mapper接口的Class名称.方法名
private final String name;
// SQL类型   取值有UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
private final SqlCommandType type;
复制代码

这个类的构造方法如下:

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    // 获取方法名称
    final String methodName = method.getName();
    final Class<?> declaringClass = method.getDeclaringClass();
    // 获取MappedStatement对象
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
                                                configuration);
    if (ms == null) {
        // 处理@Flush注解
        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和type
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: " + name);
        }
    }
}
​
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
                                               Class<?> declaringClass, Configuration configuration) {
    // statementId是由接口名称 + "." + 接口中的方法名称组成
    String statementId = mapperInterface.getName() + "." + methodName;
    // 判断configuration中是否记录了该statement
    if (configuration.hasStatement(statementId)) {
        return configuration.getMappedStatement(statementId);
    } else if (mapperInterface.equals(declaringClass)) {
        // 如果这个接口是在该Mapper中定义  返回null
        return null;
    }
    // 递归处理父类
    for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
            MappedStatement ms = resolveMappedStatement(superInterface, methodName,
                                                        declaringClass, configuration);
            if (ms != null) {
                return ms;
            }
        }
    }
    return null;
}
复制代码

4.2 MethodSignature

MethodSignature方法记录了映射器中方法的信息,其属性如下:

// 返回类型是否为列表
private final boolean returnsMany;
// 返回值类型是否为map
private final boolean returnsMap;
// 返回值是否为空
private final boolean returnsVoid;
// 返回值是否为cursor类型
private final boolean returnsCursor;
// 返回值类型是否为Optional
private final boolean returnsOptional;
// 返回值类型
private final Class<?> returnType;
//
private final String mapKey;
// 参数列表中ResultHandler类型参数所处位置
private final Integer resultHandlerIndex;
// 参数列表中RowBounds类型参数所处位置
private final Integer rowBoundsIndex;
// paramNameResolver对象
private final ParamNameResolver paramNameResolver;
复制代码

MethodSignature的构造方法如下:

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);
    // 获取mapKey
    this.mapKey = getMapKey(method);
    this.returnsMap = this.mapKey != null;
    this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
    this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
    this.paramNameResolver = new ParamNameResolver(configuration, method);
}
​
private String getMapKey(Method method) {
    String mapKey = null;
    // 返回值类型是否为Map
    if (Map.class.isAssignableFrom(method.getReturnType())) {
        // @MapKey注解
        final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
        if (mapKeyAnnotation != null) {
            // 使用@MapKey注解指定的value值
            mapKey = mapKeyAnnotation.value();
        }
    }
    return mapKey;
}
复制代码

我们先看看里面的paramNameResolver属性对应的类。

4.2.1 ParamNameResolver

ParamNameResolver是用来处理Mapper中方法的参数列表,这个类的属性如下:

// 记录方法的参数位置和值的关系
private final SortedMap<Integer, String> names;
// 记录是否使用了@Param注解
private boolean hasParamAnnotation;
复制代码
  • SortedMap<Integer, String> names 用来存储参数位置
  • boolean hasParamAnnotation 标记参数中是否使用了@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]) {
            // 如果注解是@Param
            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);
}
复制代码

在其构造方法中的处理逻辑如下:

  • 获取到方法的参数列表
  • 判断参数类型是否为特殊类型(RowBoundsResultHandler),特殊类型直接跳过
  • 或者@Param注解的value属性,作为参数名称,如果没有指定则使用方法中定义的参数名
  • 将参数索引和名称放入map中

这个类中比较重要的一个方法是getNamedParams,这个方法是用来将方法接收到的参数同参数名称进行关联,其源码如下:

public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    // 参数列表是否为空  为空返回null
    if (args == null || paramCount == 0) {
        return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
        // 参数长度为1
        Object value = args[names.firstKey()];
        return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
        final Map<String, Object> param = new ParamMap<>();
        int i = 0;
        // 遍历参数列表
        for (Map.Entry<Integer, String> entry : names.entrySet()) {
            // 将参数名作为key   接收的值作为value   放入map中
            param.put(entry.getValue(), args[entry.getKey()]);
            // add generic param names (param1, param2, ...)
            final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
            // ensure not to overwrite parameter named with @Param
            // 如果names不包含参数名 则使用param(i+1)作为参数名
            if (!names.containsValue(genericParamName)) {
                param.put(genericParamName, args[entry.getKey()]);
            }
            i++;
        }
        return param;
    }
}
复制代码

4.3 excute()方法

excute()方法是MapperMethod中的核心方法,这个方法中会根据SQL类型调用SqlSession中对应的方法,其逻辑如下:

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;
}
复制代码

通过上面得源码我们看到INSERTUPDATEDELETE都会调用rowCountResult方法,这个方法得逻辑如下:

// 这个方法中知识根据返回类型进行下转换  没什么逻辑
private Object rowCountResult(int rowCount) {
    final Object result;
    if (method.returnsVoid()) {
        result = null;
    } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
        result = rowCount;
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
        result = (long)rowCount;
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
        result = rowCount > 0;
    } else {
        throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
}
复制代码

查询方法也没有太多得逻辑,SqlSession得逻辑我们在后面得文章中详细得进行介绍,这里就不进行说明了。

3 总结

我们今天的文章到此就结束了,接下来我们简单的回顾下今天的内容。

  • binding模块是用来将Mapper和其映射文件进行关联

  • 这个模块有四个核心类,分别如下

    • MapperRegistry Mapper注册器,在Mybatis启动时会将MapperClass类与MapperProxyFactory进行关联
    • MapperProxyFactory MapperProxy的创建工厂类,会通过JDK动态代理创建Mapper的代理类MapperProxy对象
    • MapperProxy Mapper接口的代理类 我们执行Mapper中的方法会调用到其中的invoke()方法
    • MapperMethod 用来描述Mapper接口中的方法,在其中会负责参数的处理并真正调用到SqlSession相关的方法
  • Mybatis会通过动态代理为我们创建的Mapper接口创建一个代理类对象,我们无需定义其实现

感谢您的阅读,欢迎关注我的公众号:Bug搬运小能手

会定期的更新Java相关的知识

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享