MyBatis 源码分析之 Mapper 接口代理对象生成及方法执行

  • 三哥

内容来自【自学星球】

欢迎大家来了解我的星球,和星主(也就是我)一起学习 Java ,深入 Java 体系中的所有技术。我给自己定的时间是一年,无论结果如何,必定能给星球中的各位带来点东西。

想要了解更多,欢迎访问👉:自学星球

--------------SSM系列源码文章及视频导航--------------

创作不易,望三连支持!

SSM源码解析视频

👉点我

Spring

  1. Spring 中注入 Bean 的各种骚操作做
  2. Spring 中Bean的生命周期及后置处理器使用
  3. Spring 中容器启动分析之refresh方法执行之前
  4. Spring refresh 方法分析之一
  5. Spring refresh 方法之二 invokeBeanFactoryPostProcessors 方法解析
  6. Spring refresh 方法分析之三
  7. Spring refresh 方法之四 finishBeanFactoryInitialization 分析
  8. Spring AOP源码分析一
  9. Spring AOP源码分析二
  10. Spring 事务源码分析

SpringMVC

  1. SpringMVC 启动流程源码分析
  2. SpringMVC 请求流程源码分析

MyBatis

  1. MyBatis 源码分析之 SqlSessionFactory 创建
  2. MyBatis 源码分析之 SqlSession 创建
  3. MyBatis 源码分析之 Mapper 接口代理对象生成及方法执行
  4. MyBatis 源码分析之 Select 语句执行(上)
  5. MyBatis 源码分析之 Select 语句执行(下)
  6. MyBatis 源码分析一二级缓存

---------------------【End】--------------------

一、Mapper 接口代理对象生成

大家有没有这样的疑问,Mapper 为接口我们并没有对其做相关实现,我们却能调用其方法返回对应的结果?

那,下面就带大家来解答这个疑惑。

回到分析的代码

//获取对应的mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper

public <T> T getMapper(Class<T> type) {
  return configuration.<T>getMapper(type, this);
}

org.apache.ibatis.session.Configuration#getMapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

org.apache.ibatis.binding.MapperRegistry#getMapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 从 knownMappers 中获取与 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 {
        // 创建代理对象
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

getMapper 方法很简单,从 knownMappers.get(type) 中获取 Mapper 的创建工厂,然后通过工厂调用 newInstance 方法则创建出,我们所需要的 Mapper 代理对象。

那,Mapper 创建的工厂也即 MapperProxyFactory 是何时放进 knownMappers 中的呢!

还记得第三节中的解析 mapper.xml 文件的流程吗?不论是注解形式还是xml形式的 mapper 文件最终都会调用 XMLMapperBuilder#parse 方法来进行文件解析,源码如下。

org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

public void parse() {
    // 检测映射文件是否已经被解析过
    if (!configuration.isResourceLoaded(resource)) {
        // 解析 mapper 节点
        configurationElement(parser.evalNode("/mapper"));
        // 添加资源路径到“已解析资源集合”中
        configuration.addLoadedResource(resource);
        // 通过命名空间绑定 Mapper 接口
        bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

而,我们要找的答案则在 bindMapperForNamespace() 方法中,详细的源码请看 3.3 节,我这里只看最终的源码,如下。

org.apache.ibatis.binding.MapperRegistry#addMapper

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 {
            /*
             * 将 type 和 MapperProxyFactory 进行绑定,MapperProxyFactory 可为 mapper 接口生成代理类
             */
            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.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);

            // 解析注解中的信息
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

到这里,明白了创建 mapper 代理对象的工厂是如何放进去的把,而且 key 就是 mapper.xml 文件中的命名空间也即 Mapper 类文件的 class ,所以我们可以通过调用 session.getMapper(class) 方法传入要生成 mapper 对象的 class 就能返回我们所要的代理对象。

下面回到 org.apache.ibatis.binding.MapperRegistry#getMapper 方法中的如下代码:

// 创建代理对象
return mapperProxyFactory.newInstance(sqlSession);

org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)

public T newInstance(SqlSession sqlSession) {
    /*
     * 创建 MapperProxy 对象,MapperProxy 实现了 InvocationHandler 接口,代理逻辑封装在此类中
     * 将sqlSession传入MapperProxy对象中,第二个参数是Mapper的接口,并不是其实现类
     */
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy)

protected T newInstance(MapperProxy<T> mapperProxy) {
    //生成mapperInterface的代理类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

上面的代码首先创建了一个 MapperProxy 对象,该对象实现了 InvocationHandler 接口。然后将对象作为参数传给重载方法,并在重载方法中调用 JDK 动态代理接口为 Mapper接口 生成代理对象。

二、Mapper 代理对象方法执行

上面我们一句分析了代理对象的生成,那么来看看 mapper 方法的执行,也即下面代码。

System.out.println(userMapper.findAll());

由上节我们分析的 userMapper 对象为一个代理对象,所有方法执行的入口皆会来到 MapperProxy 类的 invoke 方法,下面来看看这个源码。

public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    @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);

                //如果是接口中的default方法,JDK8的新特性之一
            } else if (isDefaultMethod(method)) {
                //如果用户执行的是接口中的default方法的话,MyBatis就需要为用户提供正常的代理流程。
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        // 从缓存中获取 MapperMethod 对象,若缓存未命中,则创建 MapperMethod 对象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // 调用 execute 方法执行 SQL
        return mapperMethod.execute(sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
            //创建一个MapperMethod,参数为mapperInterface和method还有Configuration
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }
}

invoke 方法执行会先判断需要代理执行的方法是否为正常执行的方法(非 equals、hashCode、接口默认方法),如果是则直接执行不用代理,反之则代理执行。

代理执行则先从缓存中获取或者创建 MapperMethod 对象,然后通过该对象中的 execute 方法执行 SQL。

下面先来看看 MapperMethod 对象的创建,也即 new MapperMethod() 源码。

public class MapperMethod {

    //包含SQL相关信息,比喻MappedStatement的id属性,(mapper.UserMapper.getAll)
    private final SqlCommand command;

    //包含了关于执行的Mapper方法的参数类型和返回类型。
    private final MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        // 创建 SqlCommand 对象,该对象包含一些和 SQL 相关的信息
        this.command = new SqlCommand(config, mapperInterface, method);
        // 创建 MethodSignature 对象,从类名中可知,该对象包含了被拦截方法的一些信息
        this.method = new MethodSignature(config, mapperInterface, method);
    }
}

MapperMethod 包含两个对象:

  • SqlCommand
  • MethodSignature

下面分别来看看其创建过程。

1、创建 SqlCommand 对象

public static class SqlCommand {

    //name为MappedStatement的id,也就是namespace.methodName(mapper.UserMapper.getAll)
    private final String name;

    //SQL的类型,如insert,delete,update
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {

        //拼接Mapper接口名和方法名,(mapper.UserMapper.getAll)
        final String methodName = method.getName();
        //拼接Mapper接口名,(mapper.UserMapper)
        final Class<?> declaringClass = method.getDeclaringClass();
        //检测configuration是否有key为mapper.UserMapper.getAll的MappedStatement
        //获取MappedStatement
        MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
                                                    configuration);
        // 检测当前方法是否有对应的 MappedStatement
        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(接口 + 方法拼接) 和 type(SQL类型(SELECT 或 UPDATE 等)) 变量
            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) {
        String statementId = mapperInterface.getName() + "." + methodName;
        //检测configuration是否有key为statementName的MappedStatement
        if (configuration.hasStatement(statementId)) {
            return configuration.getMappedStatement(statementId);
        } else if (mapperInterface.equals(declaringClass)) {
            return null;
        }
        // 获取 mapperInterface 的所有继承接口,挨个找
        for (Class<?> superInterface : mapperInterface.getInterfaces()) {
            if (declaringClass.isAssignableFrom(superInterface)) {
                MappedStatement ms = resolveMappedStatement(superInterface, methodName,
                                                            declaringClass, configuration);
                if (ms != null) {
                    return ms;
                }
            }
        }
        return null;
    }
}

通过拼接接口名和方法名,在configuration获取对应的MappedStatement,并设置 name 和 type 变量,代码很简单。

MappedStatement 是如何放入 configuration 中的,则在 XMLMapperBuilder#parse 方法中有所体现。

2、创建 MethodSignature 对象

MethodSignature 包含了被拦截方法的一些信息,如目标方法的返回类型,目标方法的参数列表信息等。下面,我们来看一下 MethodSignature 的构造方法。

org.apache.ibatis.binding.MapperMethod.MethodSignature#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();
    }

    // 检测返回值类型是否是 void、集合或数组、Cursor、Map 等
    this.returnsVoid = void.class.equals(this.returnType);
    this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
    this.returnsCursor = Cursor.class.equals(this.returnType);

    // 解析 @MapKey 注解,获取注解内容
    this.mapKey = getMapKey(method);
    this.returnsMap = this.mapKey != null;

    /*
     * 获取 RowBounds 参数在参数列表中的位置,如果参数列表中
     * 包含多个 RowBounds 参数,此方法会抛出异常
     */ 
    this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
    // 获取 ResultHandler 参数在参数列表中的位置
    this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
    // 解析参数列表
    this.paramNameResolver = new ParamNameResolver(configuration, method);
}

2.1 执行 execute 方法

下面我们回到 org.apache.ibatis.binding.MapperProxy#invoke 方法,源码如下。

// 调用 execute 方法执行 SQL
return mapperMethod.execute(sqlSession, args);
public class MapperMethod {

    //包含SQL相关信息,比喻MappedStatement的id属性,(mapper.UserMapper.getAll)
    private final SqlCommand command;

    //包含了关于执行的Mapper方法的参数类型和返回类型。
    private final MethodSignature method;

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;

        // 根据 SQL 类型执行相应的数据库操作
        switch (command.getType()) {
            case INSERT: {
                // 对用户传入的参数进行转换,下同
                Object param = method.convertArgsToSqlCommandParam(args);
                // 执行插入操作,rowCountResult 方法用于处理返回值
                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()) {
                    // 执行查询操作,并将结果封装在 Map 中返回
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    // 执行查询操作,并返回一个 Cursor 对象
                    result = executeForCursor(sqlSession, args);
                } else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // 执行查询操作,并返回一个结果
                    result = sqlSession.selectOne(command.getName(), param);
                }
                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;
    }
}

该方法由 switch 语句组成,估计 SQL 的执行类型进行相应的数据库操作。

下面,我们来看看参数的处理方法 convertArgsToSqlCommandParam 是如何将方法参数数组转化成Map的。

org.apache.ibatis.binding.MapperMethod.MethodSignature#convertArgsToSqlCommandParam

public Object convertArgsToSqlCommandParam(Object[] args) {
    return paramNameResolver.getNamedParams(args);
}

org.apache.ibatis.reflection.ParamNameResolver#getNamedParams

public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
        return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
        return args[names.firstKey()];
    } else {
        //创建一个Map,key为method的参数名,值为method的运行时参数值
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : names.entrySet()) {
            // 添加 <参数名, 参数值> 键值对到 param 中
            param.put(entry.getValue(), args[entry.getKey()]);
            // add generic param names (param1, param2, ...)
            final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
            // ensure not to overwrite parameter named with @Param
            if (!names.containsValue(genericParamName)) {
                param.put(genericParamName, args[entry.getKey()]);
            }
            i++;
        }
        return param;
    }
}

该方法主要就是将传进来的 Object[] args 转化成一个 Map<参数名, 参数值> 。

下面我们就来分析分析查询过程,如下代码。

result = sqlSession.selectOne(command.getName(), param);

通过调用 sqlSession 来执行查询,并且传入的参数为command.getName()和param,也就是namespace.methodName(mapper.EmployeeMapper.getAll)和方法的运行参数。

下面具体来分析它。

好了,今天的内容到这里就结束了,我是 【J3】关注我,我们下期见


  • 由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。

  • 如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。

  • 感谢您的阅读,十分欢迎并感谢您的关注。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J3code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值