前言
上一篇博客【Mybatis-Spring源码分析(一) MapperScan】主要说了Mybatis的注解MapperScan是怎么把Mapper接口转换为一个MapperFactoryBean的。本篇则会侧重讲解一个MapperFactoryBean是怎么被动态代理并执行SQL语句的。更多Spring内容进入【Spring解读系列目录】。
MapperFactoryBean生成代理对象
上一篇说过MapperFactoryBean
实现了FactoryBean
,因此当调用到相关类的时候执行返回的是getObject()
方法里面的内容,因此我们去看下这个方法:
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
方法里面有getSqlSession()
,SqlSession
是Mybatis里面一个很重要的概念,原生的Mybatis中使用的是DefaultSqlSession
,而在Mybatis-Spring
中使用的是另一个封装SqlSessionTemplate
。也正是这样一个代理导致了Mybatis一级缓存失效,当然这是后话,我们以后再说,关于Mybatis和Spring缓存的内容可以参考【Mybatis在Spring中鸡肋的一级缓存和二级缓存】。既然知道是哪个类了,就直接去SqlSessionTemplate#getMapper()
里:
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
进入后发现是个空壳方法接着进入Configuration#getMapper()
:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
继续进入MapperRegistry#getMapper()
:
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);
}
}
发现这里拿到了mapperProxyFactory
对象,然后调用newInstance(sqlSession)
产生了这个对象,继续进入MapperProxyFactory#newInstance()
:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
//进入newInstance(mapperProxy)到下方的代码块
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
mapperInterface }, mapperProxy);
}
到了这里就看的十分的清楚了,Proxy.newProxyInstance()
创建了一个代理,其代理的mapperInterface
就是传入进来的Mapper
接口,并且使用MapperProxy
类作为InvocationHandler
处理的具体的逻辑。相关Spring知识点参考【从山寨Spring中学习Spring 动态加载】。 也就是说到了这里Mybatis
给Mapper
接口产生了一个动态代理,并且用MapperProxy
处理相关逻辑。
MapperProxy
既然是作为InvocationHandler
传入的,那么其执行逻辑一定就是在MapperProxy#invoke()
方法里面:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断是不是当前对象,肯定不是,走到else
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
//记住这个.invoke(proxy, method, args, sqlSession)后面还会说到
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
走到这里会走到else
逻辑块中,所以cachedInvoker(method).invoke(proxy, method, args, sqlSession);
又是啥呢?这是一个方法,最终返回的是MapperMethodInvoker
,并且调用它的invoke()
方法去执行Sql
语句,进入里面:
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
//需要确定m是谁
return methodCache.computeIfAbsent(method, m -> {
//这里m肯定不是java或者Spring框架自带的,因为这里我们关注的是自己的UserMapper等等接口
//直接去else里面
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;
}
}
看起来很复杂,但是抓住重点methodCache
,这是类声明的一个Map <Method, MapperMethodInvoker> methodCache
。它的value
必须是一个MapperMethodInvoker
类型,问题就在于m
是谁。首先m
一定不是default
的,因为我们外部写的Mapper
接口,比如UserMapper
,CityMapper
这些接口肯定不是java自带的。因此直接跳else
里面到了new PlainMethodInvoker()
,点击进入这个类PlainMethodInvoker
:
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
//执行sql语句
return mapperMethod.execute(sqlSession, args);
}
}
果然这个类就是实现了MapperMethodInvoker
,并且还记得上面这个方法后面调用的invoke()
吗?就是在这个类里实现的,使用mapperMethod.execute(sqlSession, args);
执行的sql
方法。
MapperMethod
我们看到源码里PlainMethodInvoker
里面构建的MapperMethod
,并且使用它的对象调用了invoke()
方法执行了sql
语句,那么MapperMethod
是什么呢?其实MapperMethod
和Spring中的BeanDefinition
类似,就是在描述Mapper
接口中的方法。之所以它能够执行就是因为它能获取到一个方法的所有信息,比如返回信息,比如注解的内容等等。其实我们看下构建的时候传递的什么东西进来new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())
。第一个参数就是当前的接口mapperInterface
,第二个参数就是当前要执行的方法method
,最后的参数就是当前的SqlSessionTemplate
。程序外部调用哪个方法,这里就会invoke
哪个方法。比如外部调用CityMapper
里面的list()
,method
就是list()
;如果调用update()
,method
就是update()
。而mapperInterface
就是这个CityMapper
。
public interface CityMapper {
@Select("select * from city")
public List<Map<String,Object>> list();
@Update("update city set name='AAAA' where id=1")
public int update();
}
总结
自此Mybatis
生成代理对象,并执行Sql
的流程就走完了,至于Sql
是怎么执行的,也就是说mapperMethod.execute(sqlSession, args)
里面的内容是怎么走的,是下一篇【Mybatis-Spring源码分析(三) 执行SQL导致的血案】的内容。