Mybatis源码(三)
三、获取Mapper对象
在老的版本中,DefaultSqlSession的selectOne()方法可以直接根据Mapper.xml中的StatementID,找到SQL执行。但是这种方法属于硬编码,不以查找和修改。
并且如果是参数传入错误,在编译阶段也是不会报错的,不利于提前发现问题。
User user = (User)session.selectOne("com.xx.mapper.UserMapper.selectUserById", 1);
在Mybatis后期的版本中提供了第二种调用方式,就是定义一个接口,然后再调用Mapper接口的方法。
我们定义的接口名称和Mapper.xml的namespace是对应的,接口的方法跟statementID也都是相对应的,所以根据方法就能找到对应的要执行的SQL。
UserMapper mapper = session.getMapper(UserMapper.class);
QA:
1.getMapper获得的是一个什么对象?为什么可以执行它的方法?
2.到底是怎么根据Mapper找到XML的SQL执行的?
1、getMapper()
DefaultSqlSession的getMapper()方法,调用了Configuration的getMapper方法。
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
Configuration的getMapper()方法,又调用了MapperRegistry的getMapper()方法。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
在解析mapper标签和Mapper.xml的时候已经把接口类型和类型对应的MapperProxyFactory放到一个Map中。获取Mapper代理对象,实际是从Mapper中获取对应的工厂类后,调用一下方法创建对象:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
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);
}
}
在newInstance()方法中,先创建MapperProxy。
MapperProxy实现了InvocationHandler接口,主要属性有三个sqlSession、mapperInterface、methodCache
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
最终通过JDK动态代理模式创建、返回代理对象:
protected T newInstance(MapperProxy<T> mapperProxy) {
// 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
也就是说,getMapper()返回的是一个JDK动态代理对象(类型是$Proxy数字)。这个代理对象会继承Proxy类,实现被代理的接口,里面持有一个MapperProxy类型的触发管理类。
QA:为什么要在MapperRegistry中保存一个工厂类?
创建返回代理类,这是一个非常经典的应用。
QA:为什么要直接代理一个接口呢?
2、MapperProxy如何实现对接口的代理
我们知道,JDK的动态代理,有三个核心角色:被代理类(实现类)、接口、实现了InvocationHandler的触发管理类,用来生成代理对象。
被代理类必须实现接口,因为要通过接口获取方法,而且代理也要实现这个接口。
但是Mybatis里面的Mapper没有实现类,怎么被代理?他忽略了实现类,直接对接口进行代理。
Mybatis的动态代理:
那么Mybatis里面,动态代理为什么不需要实现类?
我们这里的目的是根据一个可以执行的方法,直接找到Mapper.xml中的StatementID,方便调用。
根据接口类型+方法的名称找到StatementID这个逻辑在Handler类(MapperProxy)中就可以完成,所以这里也就不需要实现类了。
总结
获得Mapper对象的过程,实质上是获得了一个JDK动态代理对象(类型是$Proxy数字)。这个代理类会继承Proxy类,实现被代理的接口,里面持有了一个MapperProxy类型的触发管理类。
获取代理对象
image