前言
上一篇博客【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导致的血案】的内容。

本文深入探讨了Mybatis-Spring中MapperFactoryBean如何通过动态代理来执行SQL语句的过程。从MapperFactoryBean的getObject()方法开始,逐步分析了SqlSessionTemplate、MapperProxyFactory和MapperProxy的角色,揭示了Mapper接口如何被转换为动态代理对象,并通过MapperProxy的invoke()方法执行MapperMethod来完成SQL操作。MapperMethod作为方法描述符,持有执行方法所需的所有信息。整个流程为理解Mybatis-Spring的内部工作原理提供了清晰的脉络。
1763

被折叠的 条评论
为什么被折叠?



