MyBatis 参数处理 - 接口代理机制源码分析

本章最后一节 @Param 源码分析 中,老杜简单提了下 JDK 的动态代理,以及代理模式中的一些概念,如代理对象,代理方法,目标对象,目标方法,还提到了,代理方法可以在目标方法的基础上添加一些代码,代理方法最终还是要调用目标对象的目标方法。

在学习 Java 基础时,并没有学习到 JDK 动态代理相关的内容,这里就把课给补上。

另外,老杜在追 @Param 的源码时,也涉及到了 MyBatis 接口代理机制一些相关源码的细节,因此在研究完 JDK 动态代理之后,再回过头来看看 MyBatis 的接口动态代理的实现细节,追一遍 MyBatis 接口代理的源码。

静态代理和动态代理

MyBatis 接口代理机制底层是使用 Java 动态代理技术实现的,建议先了解 Java 动态代理相关知识,可以阅读:

代理机制/代理模式下,不论是静态代理还是动态代理,都存在这些概念:代理对象,代理方法,目标对象,目标方法。下面先以静态代理为例,说明这些概念的含义。

JDK 动态代理只能基于接口,静态代理也大都是基于接口的,比如多线程中的 Runnable 接口,实现 Runnable 接口的实现类,还是需要传递给 Thread 类代理执行。

假设存在如下接口 Calculator

1
2
3
4
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}

该接口存在实现类 CalculatorImpl

1
2
3
4
5
6
7
8
9
10
11
12
public class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}

@Override
public int subtract(int a, int b) {
return a - b;
}
}

如果是静态代理的话,需要静态代理类,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class StaticCalculatorProxy implements Calculator {
private Calculator target;
public StaticCalculatorProxy(Calculator target) {
this.target = target;
}

@Override
public int add(int a, int b) {
System.out.println("目标方法的前置增强代码");
int result = target.add(a, b);
System.out.println("目标方法的后置增强代码");
return result;
}

@Override
public int subtract(int a, int b) {
System.out.println("目标方法的前置增强代码");
int result = target.subtract(a, b);
System.out.println("目标方法的后置增强代码");
return result;
}
}

静态代理的使用如下:

1
2
3
4
5
6
7
public class StaticProxyTest {
public static void main(String[] args) {
Calculator cImpl = new CalculatorImpl();
Calculator cProxy = new StaticCalculatorProxy(cImpl);
int result = cProxy.add(1, 2);
}
}

CalculatorImpl 就是目标类/实现类,其对象实例 cImpl 就是目标对象。
StaticCalculatorProxy 就是(静态)代理类,其对象实例 cProxy 就是代理对象,构建代理对象实例 cProxy 时,需要传入相应的目标对象 cImpl
cProxy.add(1, 2) 就是代理对象 cProxy 调用代理方法 add,代理方法中除了代理增强的部分,还会调用目标对象(cProxy 中持有 cImpl)的目标方法 add,来完成目标实现的部分。

  • 代理类和目标类/实现类,都需要实现同一接口,确保方法定义统一,便于代理实现。
  • 接口中的方法,在代理类中的实现称为代理方法,在目标类/实现类中的实现称为目标方法。
  • 代理方法是对目标方法的增强,因此代理方法中一般包含两部分:代理增强的部分 + 目标实现的部分。
  • 为了完成代理方法中目标实现的部分,代理对象中,需要持有相应的目标对象。

静态代理需要为每个目标类/实现类,都创建相应的一个静态代理类。

如果需要对大量的目标类/实现类作代理增强,静态代理下就需要创建一大批静态代理类,而代理增强的代码又往往是较为通用的,此时使用静态代理就显得比较繁琐重复了。

可以考虑使用动态代理,我们无需声明式的创建代理类,而是在运行时在内存中直接生成代理。

如果使用动态代理的话,其核心是定义好代理方法的调用处理,即定义好一个调用处理器。一般来说,不论调用的是哪个代理对象的哪个代理方法,代理方法无非都是:执行一些通用的代理增强,完成目标方法的调用,这套固定的调用处理流程我们定义一次就好。通过实现 InvocationHandler 完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class GenericProxy implements InvocationHandler {
// 目标对象:代理方法调用时底层仍然需要调用目标方法,因此调用处理器需要持有目标对象
// 代理方法的调用处理一般是通用的,支持对多种目标类的目标对象进行代理,因此类型定义为 Object
private Object target;

public GenericProxy(Object target) {
this.target = target;
}

// 调用处理器核心:每当代理方法被调用,就触发下面的函数,该函数定义了代理方法的调用处理流程
// @param proxy 指向生成的代理对象。区分:target 属性指向目标对象
// @param method 被调用的接口方法是什么
// @param args 调用方法时传递的参数数组
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置的增强代码
System.out.println(method.getName() + "方法开始执行...");
// 目标调用
Object result = method.invoke(target, args);
System.out.println(result);
// 后置的代码增强
System.out.println(method.getName() + "方法执行结束...");
return result;

}
}

动态代理的使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DynamicProxyTest {
public static void main(String[] args) {
Calculator cImpl = new CalculatorImpl();
Calculator cProxy = (Calculator) Proxy.newProxyInstance(
cImpl.getClass().getClassLoader(),
cImpl.getClass().getInterfaces(),
new GenericProxy(cImpl)
);
int result = cProxy.add(1, 2);

// 假设还存在接口 A,和实现类 AImpl,需要进行代理
A aImpl = new AImpl();
A aProxy = (A) Proxy.newProxyInstance(
aImpl.getClass().getClassLoader(),
aImpl.getClass().getInterfaces(),
new GenericProxy(aImpl)
);
aProxy.doSomething();
}
}

可以看到,CalculatorImplAImpl 两个实现类/目标类,在为其生成代理类时,它们都共用一个调用处理器 GenericProxy

补充说明,以下是调用处理器以匿名内部类的方式实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DynamicProxyTest {
public static void main(String[] args) {
Calculator cImpl = new CalculatorImpl();
Calculator cProxy = (Calculator) Proxy.newProxyInstance(
cImpl.getClass().getClassLoader(),
cImpl.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "方法开始执行...");
Object result = method.invoke(cImpl, args);
System.out.println(result);
System.out.println(method.getName() + "方法执行结束...");
return result;
}
}
);
int result = cProxy.add(1, 2);
}
}

在匿名内部类的 invoke 方法中直接使用局部变量 cImpl,这样构建出的匿名内部类中将包含一个 cImpl 属性,也即 cProxy 中持有匿名内部类方式实现的调用处理器 InvocationHandler,调用处理器中又持有一个目标对象 cImpl

MyBatis 接口代理机制的目标类/实现类

回到 MyBatis 的接口代理机制,我们之前说了,它底层也是使用 JDK 动态代理实现的。

上面的示例中无论是静态代理还是动态代理,每个代理对象中都持有一个目标对象,也即每个代理类都有对应的目标类/实现类。

而在 MyBatis 中,我们只需要定义接口如 AccountMapper,却无需定义目标类/实现类,这是为什么呢?

其实可以认为,MyBatis 中的 DefaultSqlSession 类,就是所谓的目标类/实现类。因为 MyBatis 已经帮我们定义好了,所以无需我们额外定义。看下面的两行代码:

1
2
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Account fromAct = mapper.selectByActno(fromActno);

mapper 指向动态生成的代理对象,selectByActno() 则是代理方法,该代理方法要完成的工作是:

  • 根据 AccountMapper 接口的全类名和代理方法名 selectByActno,确定要执行的 SQL 语句以及该 SQL 语句的类型。

  • 根据 AccountMapper 接口的类字节码信息,获取 selectByActno 方法的方法签名,尤其是其中的返回值类型。

  • 根据代理方法 selectByActno 所关联的 SQL 语句的类型(SELECT)和方法签名(返回值是 List<Account>),选择执行 sqlSession.selectList() 然后将结果返回。

显然,对于其他代理方法来说,其要完成的工作也基本固定如上,其实这就可以抽象出调用处理器 InvocationHandler 的实现逻辑:

  • 代理方法 selectByActno() 其实主要目的是完成查询操作,因此可以认为 sqlSession.selectList() 就是代理方法中的目标实现调用部分,即通过目标对象 sqlSession 调用目标方法 selectList()

  • 其他工作,比如确定要执行的 SQL 语句和 SQL 语句类型,确定方法签名,根据 SQL 语句类型和方法签名动态选择执行 sqlSession 的不同方法,都可以认为是代理方法中的代理增强部分。

另外,使用 JDK 动态代理生成代理对象时,除了调用处理器以外,需要提供目标类/实现类的接口信息和目标对象,而 sqlSession.getMapper(AccountMapper.class) 中的 sqlSession 正好能对应目标对象,AccountMapper.class 正好是接口信息。

一切都说得通,唯一有些对不上的地方是,如果把 sqlSession 视为目标对象的话,其相应的 DefaultSqlSession 类并没有实现 AccountMapper 接口。

我们一定要让代理类和目标类/实现类,实现同一套接口吗?我认为是不一定的。

代理类和目标类/实现类实现同一套接口,其最终目的不还是为了便于代理实现吗?接口统一了代理类和目标类的方法定义,因此在调用代理对象的代理方法时,根据接口中的方法信息,能够直接确定到对应的目标对象的目标方法。

回到 MyBatis,虽然代理类和 DefaultSqlSession 没有实现同一个接口 AccountMapper,但是已经存在一套完善的机制,可以建立其代理方法和目标方法的映射关系。

因此,可以说,MyBatis 的接口代理机制,是对 JDK 动态代理的更灵活的运用。

MyBatis 接口代理机制源码分析

准备工作:从配置文件解析开始

SqlSessionFactoryBuilderbuild 方法的主要工作是:解析核心配置文件,产生包含完整配置信息的 Configuration 对象,然后根据该对象构造 SqlSessionFactory 工厂对象。

1
2
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsReader("mybatis-config.xml"));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SqlSessionFactoryBuilder.java
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}

// SqlSessionFactoryBuilder.java
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
// parser 是与 mybatis-config.xml 绑定的 XML 解析器
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// parser.parse() 完成解析后,即可产生 Configuration 对象
return build(parser.parse());
}

// SqlSessionFactoryBuilder.java
public SqlSessionFactory build(Configuration config) {
// 产生 Configuration 对象紧接着被用作 SqlSessionFactory 的构造器实参
return new DefaultSqlSessionFactory(config);
}

Configuration 包含了很多和 SQL Mapper 相关的属性,这里看其中比较重要的两个:

  • Map<String, MappedStatement> mappedStatements:解析所有 XxxMapper.xml 文件的主要产出,keysqlIdvaluesqlId 对应标签中的所有信息的封装。
  • MapperRegistry mapperRegistry:对所有的 XxxMapper 接口进行注册。该对象中包含一个 Map 集合属性 Map<Class<?>, MapperProxyFactory<?>> knownMappers,其中 keyXxxMapper 接口的 Class 对象,value 为接口对应的 Mapper 代理工厂,用于生成相应接口的动态代理。

mapperRegistry 的完成注册的细节如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// XMLConfigBuilder.java
public Configuration parse() {
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

// XMLConfigBuilder.java
private void parseConfiguration(XNode root) {
// ...
mapperElement(root.evalNode("mappers"));
}

// XMLConfigBuilder.java
private void mapperElement(XNode parent) throws Exception {
for (XNode child : parent.getChildren()) {
// mappers 使用 package 标签进行配置时,进入该 if 语句
if ("package".equals(child.getName())) {
// 获取 package 标签指定的包名
String mapperPackage = child.getStringAttribute("name");
// 指定包名路径,完成包下所有 Mapper 接口的注册
configuration.addMappers(mapperPackage);
} else {
// ...
}
}
}

// Configuration.java
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}

// MapperRegistry.java
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}

// MapperRegistry.java
public void addMappers(String packageName, Class<?> superType) {
// 查找包名路径下,查找所有的 XxxMapper 接口,并获取每个接口对应的 Class 对象
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// 根据每个接口的 Class 对象,完成接口注册
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}

// MapperRegistry.java
public <T> void addMapper(Class<T> type) {
// 接口注册:将接口和接口对应的 Mapper 代理工厂,加入到 Map 集合 knownMappers 中
knownMappers.put(type, new MapperProxyFactory<>(type));
}

准备工作的最后一步,研究一下 knownMappers 的值部分,MapperProxyFactory 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MapperProxyFactory<T> {
// XxxMapper 接口的 Class 对象
private final Class<T> mapperInterface;
// 方法缓存 Map 集合,具体作用需要结合 MapperProxy 来说明
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

public Class<T> getMapperInterface() {
return mapperInterface;
}

public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}

// 生成接口代理的内部方法:MyBatis 生成 XxxMapper 接口的动态代理,底层使用的是 JDK 动态代理
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 传入 XxxMapper 接口信息,以及该接口对应的调用处理器 mapperProxy
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

//生成接口代理的外部方法:主要是完成调用处理器 MapperProxy 的创建,然后传递给内部方法
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}

MapperProxyFactory 类的 Map<Method, MapperMethodInvoker> methodCache 属性值得重点关注,methodCache 属性主要在创建 MapperProxy 调用处理器时用到,其具体作用需要结合 MapperProxy 类的源码来理解。

目前准备工作已经做好了,总结如下:

MyBatis 进行核心配置文件解析后,会产生一个 Configuration 对象保存所有配置信息,并放在 SqlSessionFactory 对象中,该 Configuration 对象中与 Mapper 相关的有两个属性:mappedStatementsmapperRegistry,我们深入研究了一下后者。

mapperRegistry 属性底层维护了一个 Map 集合,对于配置文件中配置的每个 XxxMapper 接口,使用 XxxMapper 接口的 Class 对象作为 key,使用基于 XxxMapper 接口构建的 MapperProxyFactory 工厂对象作为 value,键值对 put 到该 Map 集合中完成接口信息的注册操作。后续需要生成接口代理时,将从该 Map 集合中取出对应的 MapperProxyFactory 来进行生产。

一个 XxxMapper 对应一个 MapperProxyFactory 对象,工厂对象中持有对应的 XxxMapper 接口信息,对外提供 newInstance() 方法生成接口的动态代理对象,但需要传入目标对象参数 sqlSession,底层使用 JDK 动态代理技术实现。

sqlSession.getMapper(XxxMapper.class) 的源码分析

1
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// DefaultSqlSession.java
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}

// Configuration.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

// MapperRegistry.java
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
return mapperProxyFactory.newInstance(sqlSession);
}

// MapperProxyFactory.java
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

// MapperProxyFactory.java
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

有了准备工作的基础,以上代码理解起来应该没有任何难度:

  • 生成接口代理,需要提供目标对象 sqlSessionXxxMapper.class
  • sqlSession 对象中持有一个 Configuration 对象,从而持有 MapperRegistry 对象,从而持有 knownMappers Map 集合
  • XxxMapper 接口早在核心配置文件解析时,就已经注册到 knowMappers 中,因此可以直接通过 XxxMapper.class 获取到接口对应的 MapperProxyFactory<XxxMapper> 工厂对象,通过工厂对象的 newInstance(sqlSession) 即可生成接口的动态代理对象。

MyBatis 底层是使用 JDK 动态代理技术来实现接口代理机制的,而 JDK 动态代理中,最关键的就是调用处理器的实现。现在,是时候继续深入去看 MyBatis 的调用处理器 MapperProxy 的源码了。

先看 MapperProxy 源码的第一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MapperProxy<T> implements InvocationHandler, Serializable {
// sqlSession 可以认为是最终生成的代理对象的目标对象
// 调用执行器需要持有目标对象,在代理方法中需要通过它调用目标方法
private final SqlSession sqlSession;
// XxxMapper 接口的 Class 对象
private final Class<T> mapperInterface;
// 从 MapperProxyFactory 传入的方法缓存,一个 XxxMapper 在内存中只存在一份 methodCache
// 重点关注该属性何时执行 put 操作
private final Map<Method, MapperMethodInvoker> methodCache;

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}

// 调用处理器需要重写 invoke 方法
// @param method 指向代理对象调用的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
// 如果 method 是顶级父类 Object 的方法,则直接反射执行,无需代理增强
return method.invoke(this, args);
} else {
// 否则 method 是 XxxMapper 接口方法,此时不能直接反射执行,需要先经过处理
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
}

// ...
}

代理对象不一定总是调用接口方法,也有可能调用顶级父类 Object 的方法,比如 toString 方法。所以 invoke 方法中,需要对被调用的方法 Method method 分两种情况进行处理。

我们重点看第二种情况,methodXxxMapper 接口中的方法,为什么这种情况下,method 不能直接 invoke(sqlSession) 呢?

因为目标对象 sqlSession 所属的 DefaultSqlSession 类并没有实现 XxxMapper 接口,假设 method 指向 XxxMapper.selectXxxById 方法,显然 sqlSession 本身是没有 selectXxxById 方法的,自然无法直接反射执行。

我们真正要执行的是目标对象 sqlSession 中的如 selectOne 这样的目标方法,暂且称这些方法为 mapperMethod,那么所谓的处理就是,确定 method 实际要执行的 mapperMethod 是什么。

cachedInvoker(method) 的工作大致就是如此,只不过另外还包含了缓存的逻辑,从 MapperProxyFactory 传入的 methodCache 就是在这里排上用场的。

再来看 MapperProxy 源码的第二部分,我们要深入 cachedInvoker(method) 了。

1
2
3
4
5
6
7
8
9
10
// MapperProxy.java

// cachedInvoker 的主要工作是:根据被调用的代理方法 method,返回对应的 MapperMethodInvoker
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
// 如果 method 在 methodCache 中已经存在,则直接返回对应的 MapperMethodInvoker
// 否则 method 按照第三个函数式接口参数进行处理,处理得到的 MapperMethodInvoker 先存到 methodCache 中,然后返回
return MapUtil.computeIfAbsent(methodCache, method, m -> {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// MapUtil.java
public static <K, V> V computeIfAbsent(Map<K, V> map, K key, Function<K, V> mappingFunction) {
V value = map.get(key);
if (value != null) {
return value;
}
return map.computeIfAbsent(key, mappingFunction);
}

// Map.java
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) { // 如果 method 在 methodCache 中找不到
V newValue;
// method 通过 mappingFunction 转换得到 mapperMethodInvoker
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue); // put 到 methodCache 中
return newValue;
}
}

return v;
}

我们已经看到 cachedInvoker(method) 中方法缓存 methodCache 的作用了,但其实我们更关心没有缓存的情况下,method 是如何转换处理的。

method 通过以下语句,转换为 MapperMethodInvoker

1
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());

我们先来看 MapperProxy 源码的第三部分,了解 MapperMethodInvokerPlainMethodInvoker 分别是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MapperProxy<T> implements InvocationHandler, Serializable {
// ...

// MapperMethodInvoker 是 MapperProxy 中定义的接口成员
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}

// PlainMethodInvoker 是对 MapperMethodInvoker 接口的实现,它是 MapperProxy 中定义的类成员
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);
}
}
}

可以看到,PlainMethodInvoker 只是简单地在 MapperMethod 上又包了一层,invoke 内部还是调用 MapperMethodexecute 方法来完成操作。

看来问题的关键,还是在 MapperMethod 类中。

这里先问一个问题,MapperMethod 是否指向 sqlSession 的某个具体函数呢?

通过 MapperMethod 的构造函数,以及 execute 方法都可以看出来,答案是否定的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class MapperMethod {
// SqlCommand 中包括 sqlId 和对应的 sql 语句的类型(如 select)
private final SqlCommand command;
// MethodSignature 即方法签名,其包含方法的返回值相关的信息
private final MethodSignature method;

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 根据 mapperInterface 和 method,可以确定 method 对应的 sqlId
// 根据 sqlId,可以到 config 中获取到对应的 mappedStatement,从而确定 sql 语句的类型
// 根据 method 可以确定返回值的类型
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据 sql 语句的类型,以及返回值的不同,执行 sqlSession 中的相应方法
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
// sqlSession.delete
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
// ...
} else if (method.returnsMany()) {
// sqlSession.selectList
} else if (method.returnsMap()) {
// sqlSession.selectMap
} else if (method.returnsCursor()) {
// ...
} else {
// sqlSession.selectOne
}
break;
// ...
}
return result;
}
}

MapperMethod 并不指向 sqlSession 中的某个具体函数,而是在真正执行 execute 时,才根据构造初始化时的解析结果(sql 语句类型,返回值类型等),选择适当的函数来执行。

因此,cacheInvoker(method) 返回的 MapperMethodInvoker,其内部包装的 MapperMethod 其实并不是接口方法 method 对应到 sqlSession 中的某个具体方法。

不过直接将 MapperMethod 理解为指向 sqlSession 的某个具体方法也没有问题。这样在理解 MapperProxyinvoke 方法时,会更加形象:如果 methodXxxMapper 接口的方法,通过 cachedInvoker(method) 获取到对应的 mapperMethod 再反射执行。

1
2
3
4
5
6
7
8
9
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// method 是 XxxMapper 接口方法时,获取 method 对应的 mapperMethod,再反射执行
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
}

mapper.selectXxxByXxxx 源码分析

1
List<Student> students = mapper.selectByNameAndSex2("张三", '男');

动态代理对象 mapper 执行代理方法 selectByNameAndSex2,会触发调用处理器 MapperProxy 中的 invoke 方法

1
2
3
4
5
6
7
8
9
// MapperProxy.java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
}

第一次执行 cachedInvoker(method) 时,由于 method 还未被加入 methodCache,因此会进入 MapUtil.computeIfAbsent 的匿名内部类中,进行 methodmapperMethodInvoker 的转换

1
2
3
4
5
6
7
// MapperProxy.java
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
// 进入匿名内部类中,执行转换操作
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
});
}

cachedInvoker(method) 获取到 mapperMethodInvoker 后,调用 invoke 方法。

1
2
// MapperProxy.java
cachedInvoker(method).invoke(proxy, method, args, sqlSession);

mapperMethodInvoker.invoke 底层实际还是调用 mapperMethod.executeStudentMapper.selectByNameAndSex2 对应的 Sql 语句类型是 SELECT,返回值类型是 List<Student>,因此最终实际会调用 sqlSession.selectList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// MapperProxy.java
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}

// MapperMethod.java
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
// ...
case SELECT:
if (method.returnsMany()) {
// 执行
result = executeForMany(sqlSession, args);
}
break;
// ...
}
return result;
}

// MapperMethod.java
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
// 执行
result = sqlSession.selectList(command.getName(), param);
return result;
}

同样的接口方法 selectByNameAndSex2 再执行一次。

1
List<Student> students2 = mapper.selectByNameAndSex2("李四", '女');

第二次进入 cacheInvoker(method) 时,由于 methodCache 中已存在 method 对应的 mapperMethodInvoker,因此不会进入 MapUtil.computeIfAbsent 的匿名内部类中,而是直接获取到并执行 invoke

1
2
3
4
5
6
7
8
9
// MapperProxy.java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
}