一、MapperProxyFactory
在使用mybatis的时候,配置好对应文件,只需要调用接口方法就可以实现对数据库操作。接口本身是不可以直接使用的,需要有具体的实现类,在mybatis中我们不需要去编写具体的实现类,只需要编写对应的接口就行。到这里,估计大家都已猜出,mybatis用代理的方式为我们做了实现,所以本文会重点分析mybatis中代理的实现。
在SqlSession中有个getMapper(Class< T > type),我们只需要传递一个具体的接口,就可以得到其代理对象。其方法的具体调用链在:
configuration.getMapper(type, this);
//configuration对象中又调用了
mapperRegistry.getMapper(type, sqlSession)
所以getMapper方法的具体实现在MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//先从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 {
//调用其实例化方法
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
在分析Configuration解析的时候,就已经分析了knownMappers,集合中保存的是接口和MapperProxyFactory对应关系。从类名称就可知这是实例化MapperProxy的工厂对象,查看其newInstance(sqlSession)方法:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
这里会把当前的SqlSession对象和mapper接口封装成MapperProxy对象,然后在调用动态代理的Proxy.newProxyInstance方法,返回mapper接口的代理对象。
二、MapperProxy
既然是动态代理,MapperProxy对象必然实现了InvocationHandler接口,具体的代理逻辑也会在这个对象中,所以具体的逻辑我们只需要查看其invoke方法就行:
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 {
//这里会把当前调用的方法封装成一个MapperMethodInvoker然后再调用其invoke方法
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
cachedInvoker方法:
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (m.isDefault()) {//判断是否是一个默认方法(接口中定义的)
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
//正常走这里
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
所以会调用到PlainMethodInvoker的invoke方法
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
到这里,我们发现最后执行的就是MapperMethod的execute方法,这个方法比较简单根据当前执行的是增删改查哪一个,然后再具体调用SqlSession对象中对应的方法即可,所以最终的调用都在SqlSession中。
三、总结
Mybatis采用动态代理模式,实现了只需要编写接口,然后调用接口方法就可以实现对数据库的操作,底层的实现是通过MapperProxy对象,通过层层封装调用,最后都会映射到SqlSession的调用上来。
以上,有任何不对的地方,请指正,敬请谅解。