上篇文章从JDBC优化开始,对Mybatis传统xml配置主线流程源码剖析,这里不多对JDBC,xml配置的框架源码做多的分析,直接看源码:
还是从框架的启动入口着手,SqlSession接口的解析,在上篇文章中有提到这里不多赘述。
通常情况,UserMapper我们并没有对其进行接口的实现,在接口的声明时通过@Mapper配置或XML配置Mapper接口路径去暴露接口位置信息。 接口中包含的一些Mybatis框架自己封装的数据库操作方法或是映射xml中配置的方法,它们是怎样被调用的?可以肯定的是通过了接口路径去做了动态的加载,而方法的调用则是通过了动态代理的方式去执行。
InputStream inputStream =Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.getUserByName("test")
getMapper解析
上面说到了UserMapper接口的加载,是怎样去加载的?直接点进getMapper方法去查看。进入到SqlSession接口中的getMapper方法,有两个实现类 DefaultSqlSession & SqlSessionManager。
/**
* Retrieves a mapper.
* @param <T> the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
<T> T getMapper(Class<T> type);
这里不用多去分析具体去查阅哪一个,他们中的实现方法都会跳转到Configuration类中getMapper方法中去
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
然后看一下MapperRegistry类,上一篇文章中有说到过Configure类,它里面存放了Mybatis读取的配置信息,XML中解析出来的MapperStatement对象,解析拼接而成的sql....,这里的源码中可以发现MapperRegistry是用来做了一个配置的传递对象,有点类似工厂类去做的事情。
MapperRegistry的对象实例化后,调用了它的getMapper方法。然后会发现它去获取了一个MapperProxyFactory对象。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获得 MapperProxyFactory 对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 不存在,则抛出 BindingException 异常
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);
}
}
不急着往下看,先看knownMappers这个属性中是如何存入值的,这里就需要一步一步的倒放去看。knownMappers是定义的一个MapperProxyFactory的存放容器。既然是容器那么肯定有一个存入容器的方法提供,在这个类中往下翻。
果然发现了一个addMapper的方法,去往容器中注值。前面的判断就是判断传过来的这类是不是一个接口然后就开始放入,然后对一些注解进行了解析,解析的无非就是Mapper接口中不想去配置XML去加了@Select等等一些sql直接暴露的注解,这里不多深挖,去看这个addMapper方法被谁调用了!
然后继续的去倒着查,发现又回到了Configure对象中去了...,还是老一套的Configure对象中调用Resiger对象方法,然后继续的倒追,然后到了Mybatis框架启动build类中的mapperElement方法,这个里的MapperClass我用红箭头标记了,是解析xml中获取的Mapper接口的路径信息,既然到这里了,回顾下Mapper接口的配置吧 ,直接贴配置,配置不做多的分析。到了这里就跟我前面说的吻合了,通过配置的类的路径去动态加载这个类的实例。
<mappers>
<mapper class="cn.yihan.mapper.UserMapper"/>
<package name="cn.yihan.mapper"/>
</mappers>
回到上面的MapperProxyFactory,这里就是重点了,拿到这个类后下面调用了它的newInstance方法,从命名上来看应该是实例化,刚刚一路跟踪过来也看了,拿到的只是一个Class对象并没有进行实例化的Mapper.class。
点进去到MapperProxyFactory的newInstance方法,先从这个类的初始化方法说起,之前knownMappers方法去实例化MapperFactory对象传入的Mapper.class通过初始化方法赋值属性mapperInterface,而methodCache则是用来存放mapper的方法的容器。参数解释完,然后去看MapperProxy方法。
public class MapperProxyFactory<T> {
/**
* Mapper 接口
*/
private final Class<T> mapperInterface;
/**
* 方法与 MapperMethod 的映射
*/
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public T newInstance(SqlSession sqlSession) {
// 创建了JDK动态代理的invocationHandler接口的实现类mapperProxy
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 调用了重载方法
return newInstance(mapperProxy);
}
}
MapperProxy对象实现了InvocationHandler接口,去做JDK动态代理,JDK动态代理这里不做多的顶层赘述,简要说一下,JDK动态代理返回了示例后,可以直接调用mapper类中的方法了,但代理对象不管调用什么方法,在调方法时,执行的是 在MapperProxy中的invoke方法中,这也是AOP面向切面编程的重要方式之一。
贴出invoke方法, Proxy是动态代理对象,method是你调用的动态代理的方法,args则是你传入的参数,等同Object ... params 的写法。点进去看execute方法,然后发现到这里之后,则开始执行Sql的处理基本操作了,然后返回Sql执行的对象。 然后就是方法执行中做了很多的反射判断,对方法是不是存在啊,参数是不是对应的上啊之类的判断然后返回异常给开发人员等等操作,不做多的赘述。
@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 if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 获得 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 重点在这:MapperMethod最终调用了执行的方法
return mapperMethod.execute(sqlSession, args);
}