浅析Mybatis mapper动态代理

大学后再也没用过mybatis,最近看到动态代理了,顺带了解一下mapper的原理。
不清楚代理模式的,可以先看下这篇:
代理模式

对配置熟悉的可直接跳到第二步

1.测试代码

项目结构.PNG
(1)mybatis-config.xml



    
    
        
            
            
                
                
                
                
            
        
    
    
    
        
    

(2)TMapper.xml




    

(3)T.java
package framework.mybatis1.domain;

public class T {

    private int id;

    private int a;

    private int b;


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    public int getB() {
        return b;
    }

    public void setB(int b) {
        this.b = b;
    }

    @Override
    public String toString() {
        return "T{" +
                "id=" + id +
                ", a=" + a +
                ", b=" + b +
                '}';
    }
}
(4) TMapper.java
package framework.mybatis1.mapper;

public interface TMapper {

    T selectT(int id);
}
(5)测试
package framework.mybatis1;

public class MyBatisTest {

    public static void main(String[] args) throws IOException {
        //mybatis的配置文件
        String resource = "config/mybatis-config";
        //使用MyBatis提供的Resources类加载mybatis的配置文件
        Reader reader = Resources.getResourceAsReader(resource);
        //构建sqlSession的工厂
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);

        SqlSession session = sessionFactory.openSession();
        TMapper mapper = session.getMapper(TMapper.class);//获取自定义的 Mapper 接口的代理对象
        T user = mapper.selectT(1);
        System.out.println(user);
    }
}

2.原理分析

主要是下面几步:

(1)构建 SqlSessionFactory
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);

它做了什么呢?

我们知道它传入的参数reader就是mybatis-config.xml配置文件的字符流,
mybatis-config.xml最终会被解析放进Configuration实例(也会解析TMapper.xml):

public class Configuration {
    Environment environment;//数据库连接配置
    MapperRegistry mapperRegistry;//mapperRegistry.knownMappers
    Map mappedStatements;//
    ...//为了好理解,省去其他代码
}
Configuration.png
(2)从 SqlSessionFactory 中获取 SqlSession
SqlSession session = sessionFactory.openSession();

从Configuration 类中拿到Environment封装的数据源连接数据库

(3)获取mapper接口的代理对象
TMapper mapper = session.getMapper(TMapper.class);

里面调用的其实是Configuration的getMapper(..)方法:

    public  T getMapper(Class type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
    public  T getMapper(Class type, SqlSession sqlSession) {
        MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                //调用MapperProxyFactory创建代理
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

MapperProxyFactory在第一步已经放进配置中了,它是生成代理类的工厂,来看下具体做了什么:

public class MapperProxyFactory {
    private final Class mapperInterface;
    private final Map methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    protected T newInstance(MapperProxy mapperProxy) {//生成代理对象
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

重点在:Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);

可以看到这个生成的Proxy实现了mapper接口(对应上面例子就是实现了TMapper.java接口)

来看下MapperProxy:

public class MapperProxy implements InvocationHandler, Serializable {
    private final SqlSession sqlSession;
    private final Class mapperInterface;
    private final Map methodCache;

    public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //执行mapper.java中的方法继承自Object的方法,比如toString(),equals()
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        //最终调用了这里,MapperMethod 会交给sqlSession去执行并返回结果
        return mapperMethod.execute(this.sqlSession, args);
    }

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

        return mapperMethod;
    }
}

MapperProxy实现了InvocationHandler接口,我们知道,java动态代理调用方法后实际调用的是InvocationHandler的invoke方法,MapperProxy.invoke()中最终起作用的是mapperMethod.execute(this.sqlSession, args)

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

可以看到实际是调用sqlSession来执行具体的sql命令。command的类型前面解析TMapper.xml时,通过元素