MyBatis的版本是: 3.5.4-SNAPSHOT
断点看从sqlSession中获取mapper对象。
我们知道,MyBatis使用的Java动态代理为接口(UserMapper)生成代理类。
简单说下:我们加载MaBatis的xml配置文件和Mapper文件,会生成一个 configuration 对象。这里对象里面有一个
MapperRegistry类型对象(这个对象就是存储我们的Mapper.xml中的nameSpace对应的接口所对用的MapperProxyFactory对象。比如我这个:UserMapper 接口。)
这个MapperRegistry类的 getMapper 方法:第一个参数就是我们要被代理的接口(比如我的UserMapper 接口) ,第二个参数是sqlSession。逻辑很简单,先从Map对象 knownMappers 中获取到该接口UserMapper 对应的MapperProxyFactory类型的对象mapperProxyFactory。
然后mapperProxyFactory对象是什么呢?
查看源码:MapperProxyFactory类是我们的MapperProxy类的工厂类,再查看MapperProxy的源码。
public class MapperProxy<T> implements InvocationHandler, Serializable
看到没,实现了InvocationHandler接口。学过java动态代理的都知道,实现这个接口的类,需要重写invoke方法,而这个invoke方法就是对应的代理类到时候的执行逻辑。继续看我们之前的从MapperProxyFactory工厂里获取对象mapperProxy的方法:先我们初始化一个实现了InvocationHandler接口的类 MapperProxy,然后使用JAVA动态代理为我们的接口UserMapper生成代理类
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
重点是:我们执行
userMapper.selectUser("10");
这一句时,根据动态代理我们知道,最终会执行的就是MapperProxy类里面的的invoke方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(proxy, method, args).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
第一个if条件对应的就是,代理类从Object继承的那些方法执行的时候,直接调用。 比如:
userMapper.toString();
重点就是这个方法的else中的逻辑,这个逻辑就是在做执行sql,然后经结果转换为java类型对象。
MapperMethodInvoker接口有俩个实现类,查看代码逻辑:
private MapperMethodInvoker cachedInvoker(Object proxy, Method method, Object[] args) throws Throwable {
try {
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {//Function接口中的default方法时,执行这个if,比如 compose方法
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;
}
}
这里面用的是java8的新特性:default方法,map的 computeIfAbsent方法,Function函数式接口。我们第一次调用
userMapper.selectUser("10");
这一行代码时,这时候在methodCache中,是没有对应的实现了 MapperMethodInvoker接口的类的对象的。这里就是运用了缓存的技术:第一次调用时,我们先创建实现MapperMethodInvoker接口的类的对象,然后放到map中,下次执行这个方法时,直接从map中获取,不需要再次创建对象了。比如:同一个sqlsession
如果是第一次运行时,我们的缓存methodCache
还没有这个方法method对象的调用者MapperMethodInvoker,
同时这个method对象是一个非default的方法(1.8开始,接口中允许定义default方法
,比如:在我们的UserMapper接口中定义一个default方法,那么到这里执行逻辑直接走
我们的if(m.isDefault())分支中的代码),
那么,正常我们需要被代理的方法(比如:selectUser)
直接走else里的逻辑,
生成一个PlainMethodInvoker类的对象。
如果我们第N次(N>1)调用 selectUser 方法时,
我们直接从缓存methodCache中取到
这个方法method对象的调用者MapperMethodInvoker。
另外说一下,我这个MyBatis源码版本是比较新的,它里面用到
好多java1.8的新特性。
1.什么是默认方法?
默认方法:在接口类型中,声明的具有主体的非静态方法(有具体实现的)。
举例:下面是我们的Function接口中的默认方法
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
2.map的 computeIfAbsent 方法
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
翻译一下,就是 如果不存在则执行
也就是说类似我们先从map中get(k)操作,取出来的值如果是null,然后执行创建一个value,同时这个value不为null,然后再put进去map中。如果get出来的value有值,直接返回value。
if ((v = get(key)) == null) <------取出来值为null
mappingFunction.apply(key) <------执行生成一个value值的逻辑
接下来就是这个Function接口的apply方法
3.函数式接口Function的使用,其实我们经常用的另一个函数式接口Runnale接口。先看下面的俩行代码:
map.computeIfAbsent("key2",m->{return m ;});
map.computeIfAbsent("key3",(m)->{return m;} );
效果是一样的,只有一个入参时,()是可以省去的
再看以前的版本要实现相同逻辑
在1.8之前,如果要实现相同的逻辑,需要用匿名内部类如下:
package com.br.itwzhangzx02.learn;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class MapTest {
@Test
public void test01() {
Map map = new HashMap<String,String>();
map.computeIfAbsent("key1", new Function<String,String>() {
@Override
public String apply(String t) {
return t ;
}
} );
map.computeIfAbsent("key2",m->{return m ;});
map.computeIfAbsent("key3",(m)->{return m;} );
System.out.println(map.get("key1"));
System.out.println(map.get("key2"));
System.out.println(map.get("key3"));
}
}
对比发现,函数式编程代码更加简洁,同时简单易读。
输入:->前面的部分,即被()包围的部分。此处只有一个输入参数,实际上输入是可以有多个的,如两个参数时写法:(a, b);当然也可以没有输入,此时直接就可以是()。
函数体:->后面的部分,即被{}包围的部分;可以是一段代码。
输出:函数式编程可以没有返回值,也可以有返回值。如果有返回值时,需要代码段的最后一句通过return的方式返回对应的值。
其实,mybatis源码中好多函数式编程,就比如在一些test类中:
继续回到我们之前的代码执行处:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {//重点是这个方法:执行的时候在cachedInvoker中会执行我们的return new
//PlainMethodInvoker(new
//MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
return cachedInvoker(proxy, method, args).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
/*初始化 MapperMethod对象的持有者或调用者PlainMethodInvoker,其实这里叫适配器更合适点,这就是一个典型的对象型的适配器模式*/
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
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 {
return mapperMethod.execute(sqlSession, args);
}
}
对象型适配器的类图如下:
经过适配后,我们的invoke逻辑会执行:
mapperMethod.execute(sqlSession, args);
简单理解:mapperMethod类就是管理我们的sql语句的抽象和java类中的方法签名,这样就将我们xml中的sql语句和我们Mapper接口中定义的方法method关联映射起来。
/*这是xml配置*/
<mapper namespace="learn.UserMapper">
<select id="selectUser" parameterType="String" resultType="learn.User">
select * from u_user where usercode = #{id}
</select>
</mapper>
/*sqlID:learn.UserMapper.selectUser 然后和我们的UserMapper接口中的方法selectUser关联映射起来*/
package learn;
public interface UserMapper {
public User selectUser(String id);
}