Mybatis为什么只写接口就能执行sql?
至于这个问题,也算是一个常见的面试题,基本都会答动态代理,那么动态代理到底是如何做的呢?
Configuration对象
Mybatis会将配置文件和映射文件中的所有信息保存在这个对象中。其中有一个属性为mapperRegistry。这个属性保存接口和它对应的代理工厂
// 接口和代理类的注册中心
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
MapperRegistry这个类中含有一个属性名为knowMappers,本质上是一个hashMap,其中key为Class类型,value为MapperProxyFactory类型。
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
其中这个class就是我们所写的接口的class对象,当我们执行getMapper(mapper.class)的时候,会执行如下逻辑:
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);
}
}
使用map的get方法,获取到对应的代理工厂,然后使用代理工厂创建代理对象。最终代码来到这:
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, 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 if (isDefaultMethod(method)) {
// jdk8中接口中的默认反复不需要增强
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 主要逻辑
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
其中mapperMathod相当于一个桥梁,一方面沟通接口中的方法和参数,另一方面沟通mapper.xml文件中的sql语句。最终调用execute方法。
接下来就是sql执行的逻辑了。