Mybatis源码解析之MapperProxy

前言:

    我们在前面Mybatis源码解析之一级缓存  介绍过使用SqlSession提供的方法直接操作JDBC的方式。

    在实际工作中,我们一般都比较偏向于使用以下方式来进行JDBC操作

BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog3 = mapper.queryById(17);

    以前一直比较奇怪一个问题,就是BlogMapper是一个接口,按照我们的一般思路应该是有一个实现类,才能继续操作其中的方法,但这个是接口类,所以肯定有一个默认的实现类来操作了,具体是如何的呢?

1.Mybatis的使用

    具体配置可参考Mybatis源码解析之一级缓存

    

2.测试类

public static void main(String[] args) {
    SqlSession session = sqlSessionFactory.openSession();
    SqlSession session1 = sqlSessionFactory.openSession();
    try {
        // 直接获取对应的Mapper
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        Blog blog3 = mapper.queryById(17);

    } finally {
        session.close();
    }
}

    结果:这样是可以测试成功的,使用同session.selectOne()是一致的

3.有关于SqlSession.getMapper()的源码分析

    1)DefaultSqlSession.getMapper()默认实现类为DefaultSqlSession

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

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

// MapperRegistry.getMapper()
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 1.可以看到,这里是从MapperRegistry.knownMappers获取对应的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 {
        // 2.从MapperProxyFactory中获取MapperProxy 
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

    2)MapperProxyFactory.newInstance()获取MapperProxy

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

    可以看到,这个是一个动态代理模式,我们使用MapperProxy来代理所有的类(在本例中就是BlogMapper接口)

    总结:SqlSession.getMapper()获取的就是一个代理类MapperProxy

    Q:

        这里有一个问题,MapperRegistry.getMapper()获取MapperProxy,而MapperProxy又是从MapperProxyFactory中获取的,MapperProxyFactory从knownMappers.get(type)获取,这个knownMappers什么时候把type和MapperProxyFactory的值封装进来的呢?

        这里先当做一个疑问,读者先思考一下!

4.BlogMapper.queryById()的执行分析

    由上可知:真正执行的是代理类MapperProxy,那么执行方法时,就会执行MapperProxy.invoke()方法

    1)MapperProxy.invoke()

// MapperProxy.invoke()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // Object的方法执行
    if (Object.class.equals(method.getDeclaringClass())) {
        try {
            return method.invoke(this, args);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
    // 获取MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 真正的处理在这里
    return mapperMethod.execute(sqlSession, args);
}

private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
        mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
        methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
}

    2)MapperMethod.execute()方法执行

// MapperMethod.execute()
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 具体的增删改查操作,都有具体的执行,
    if (SqlCommandType.INSERT == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
    // 在这里我们主要看本例中的查询操作
    } else if (SqlCommandType.SELECT == command.getType()) {
        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 {
            // 本例就返回一个结果值,看这里
            // 封装参数值,最后还是交给SqlSession来处理
            Object param = method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(command.getName(), param);
        }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
        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;
}

5.MapperProxyFactory的封装与获取

    MapperProxy的执行过程我们看完了,现在回过头来,看下我们刚才提的问题,MapperProxyFactory是什么时候和type对应封装起来的呢?

    仔细回忆整个过程,除了获取BlogMapper,整个测试代码就只有解析blog.xml,那么就应该是在解析blog.xml的时候把这个对应关系封装起来的。那么我们就从这里开始入手

    1)SqlSessionFactoryBuilder.parse()

    还是从这里开始追踪,按照之前的追踪过程,我们一路追到XMLMapperBuilder.parse()    

// XMLMapperBuilder.parse()   
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        configurationElement(parser.evalNode("/mapper"));// 之前分析过这个方法
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();// 我们来看下这个方法
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
}

// bindMapperForNamespace()
private void bindMapperForNamespace() {
    // 1.这里的namespace就是blog.xml中配置的mapper对应的namespace,也就是mybatis.BlogMapper
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        Class<?> boundType = null;
        try {
            // 2.反射获取对应的type
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
        }
        if (boundType != null) {
            if (!configuration.hasMapper(boundType)) {
                configuration.addLoadedResource("namespace:" + namespace);
                // 3.这里就是将BlogMapper添加到configuration,我们来具体看一下做法
                configuration.addMapper(boundType);
            }
        }
    }
}

// configuration.addMapper()
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);// 继续看
}

// mapperRegistry.addMapper()
public <T> void addMapper(Class<T> type) {
    // 1.只有接口才添加..有点意思啊
    if (type.isInterface()) {
        // 2.在这里我们可以看到,Mapper名称是不能重复的,否则会报错
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            // 3.将type和MapperProxyFactory的对应关系添加到knowMappers中
            // 就是这里喽
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

    总结:我们一次性看完解析的所有操作,最终找到,在解析mapper xml文件的时候,把具体的Mapper添加到Configuration中,

总结:

    1)解析xml文件时,将Mapper type和MapperProxyFactory的对应关系封装到Configuration中,以备后面使用

    2)在SqlSession.getMapper(type)时,就是从Configuration中获取type对应的MapperProxyFactory

    3)MapperProxyFactory用于获取MapperProxy

    4)MapperProxy是一个动态代理类,真正的实现还是交由SqlSession来处理

发布了124 篇原创文章 · 获赞 126 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_26323323/article/details/85755965