code-learning/mybatis/14-mybatis-Binding 模块.md

612 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 MyBatis 源码分析 —— Binding 模块
# 1. 概述
本文,我们来分享 MyBatis 的 Binding 模块,对应 `binding` 包。如下图所示:[`binding` 包](http://static.iocoder.cn/images/MyBatis/2020_02_07/01.png)
在 [《精尽 MyBatis 源码解析 —— 项目结构一览》](http://svip.iocoder.cn/MyBatis/intro) 中,简单介绍了这个模块如下:
> 在调用 SqlSession 相应方法执行数据库操作时,需要指定映射文件中定义的 SQL 节点如果出现拼写错误我们只能在运行时才能发现相应的异常。为了尽早发现这种错误MyBatis 通过 Binding 模块,将用户自定义的 Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免上述问题。
>
> 值得读者注意的是,开发人员无须编写自定义 Mapper 接口的实现MyBatis 会自动为其创建动态代理对象。在有些场景中,自定义 Mapper 接口可以完全代替映射配置文件,但有的映射规则和 SQL 语句的定义还是写在映射配置文件中比较方便,例如动态 SQL 语句的定义。
本文涉及的类如下图所示:[类图](http://static.iocoder.cn/images/MyBatis/2020_02_07/02.png)
# 2. MapperRegistry
`org.apache.ibatis.binding.MapperRegistry` Mapper 注册表。
## 2.1 构造方法
```
// MapperRegistry.java
/**
* MyBatis Configuration 对象
*/
private final Configuration config;
/**
* MapperProxyFactory 的映射
*
* KEYMapper 接口
*/
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
```
## 2.2 addMappers
`#addMappers(String packageName, ...)` 方法,扫描指定包,并将符合的类,添加到 `knownMappers` 中。代码如下:
```
// MapperRegistry.java
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
// <1> 扫描指定包下的指定类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// <2> 遍历,添加到 knownMappers 中
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
```
- `<1>` 处,使用 ResolverUtil 扫描指定包下的指定类。详细解析,在 [《精尽 MyBatis 源码分析 —— IO 模块》](http://svip.iocoder.cn/MyBatis/io-package) 中有。
## 2.3 hasMapper
`#hasMapper(Class<T> type)` 方法,判断是否有 Mapper 。代码如下:
```
// MapperRegistry.java
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
```
## 2.4 addMapper
`#addMapper(Class<T> type)` 方法,添加到 `knownMappers` 中。代码如下:
```
// MapperRegistry.java
public <T> void addMapper(Class<T> type) {
// <1> 判断,必须是接口。
if (type.isInterface()) {
// <2> 已经添加过,则抛出 BindingException 异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// <3> 添加到 knownMappers 中
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// <4> 解析 Mapper 的注解配置
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
// <5> 标记加载完成
loadCompleted = true;
} finally {
// <6> 若加载未完成,从 knownMappers 中移除
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
```
- `<1>` 处,判断 `type` 必须是接口,也就是说 Mapper 接口。
- `<2>` 处,已经添加过,则抛出 BindingException 异常。
- `<3>` 处,添加到 `knownMappers` 中。
- `<4>` 处,解析 Mapper 的注解配置。
- `<5>` 处,创建 MapperAnnotationBuilder 对象,解析 Mapper 的注解配置。
- `<6>` 处,若加载未完成,从 `knownMappers` 中移除。
## 2.5 getMapper
`#getMapper(Class<T> type, SqlSession sqlSession)` 方法,获得 Mapper Proxy 对象。代码如下:
```
// MapperRegistry.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// <1> 获得 MapperProxyFactory 对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 不存在,则抛出 BindingException 异常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
// 创建 Mapper Proxy 对象
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
```
- `<1>` 处,从 `knownMappers` 中,获得 MapperProxyFactory 对象。
- `<2>` 处,调用 `MapperProxyFactory#newInstance(SqlSession sqlSession)` 方法,创建 Mapper Proxy 对象。详细解析,见 [「3. MapperProxyFactory」](https://svip.iocoder.cn/MyBatis/binding-package/#) 。
# 3. MapperProxyFactory
`org.apache.ibatis.binding.MapperProxyFactory` Mapper Proxy 工厂类。
## 3.1 构造方法
```
// MapperProxyFactory.java
/**
* Mapper 接口
*/
private final Class<T> mapperInterface;
/**
* 方法与 MapperMethod 的映射
*/
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
```
## 3.2 newInstance
`#newInstance(...)` 方法,创建 Mapper Proxy 对象。代码如下:
```
// MapperProxyFactory.java
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);
}
```
- 依然是稳稳的基于 JDK Proxy 实现,而 InvocationHandler 参数是 MapperProxy 对象。关于 MapperProxy ,详细解析,见 [「4. MapperProxy」](https://svip.iocoder.cn/MyBatis/binding-package/#) 。
# 4. MapperProxy
`org.apache.ibatis.binding.MapperProxy` ,实现 InvocationHandler、Serializable 接口Mapper Proxy 。关键是 `java.lang.reflect.InvocationHandler` 接口,你懂的。
## 4.1 构造方法
```
// MapperProxy.java
/**
* SqlSession 对象
*/
private final SqlSession sqlSession;
/**
* Mapper 接口
*/
private final Class<T> mapperInterface;
/**
* 方法与 MapperMethod 的映射
*
* 从 {@link MapperProxyFactory#methodCache} 传递过来
*/
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
```
## 4.2 invoke
`#invoke(Object proxy, Method method, Object[] args)` 方法,调用方法。代码如下:
```
// MapperProxy.java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// <1> 如果是 Object 定义的方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
// 见 https://github.com/mybatis/mybatis-3/issues/709 ,支持 JDK8 default 方法
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// <3.1> 获得 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// <3.2> 执行 MapperMethod 方法
return mapperMethod.execute(sqlSession, args);
}
```
- `<1>` 处,如果是 Object 定义的方法,直接调用。
- `<2>` 处,调用 `#isDefaultMethod((Method method)` 方法,判断是否为 `default` 修饰的方法,若是,则调用 `#invokeDefaultMethod(Object proxy, Method method, Object[] args)` 方法,进行反射调用。代码如下:
```
// MapperProxy.java
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
/**
* Backport of java.lang.reflect.Method#isDefault()
*/
private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
&& method.getDeclaringClass().isInterface();
}
```
- JDK8 在接口上,新增了 `default` 修饰符。怎么进行反射调用,见 [《java8 通过反射执行接口的default方法》](https://www.jianshu.com/p/63691220f81f) 一文。
- 关于这段代码,在 https://github.com/mybatis/mybatis-3/issues/709 上有讨论。
- `<3.1>` 处,调用 `#cachedMapperMethod(Method method)` 方法,获得 MapperMethod 对象。代码如下:
```
// MapperProxy.java
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
```
- 默认从 `methodCache` 缓存中获取。如果不存在,则进行创建,并进行缓存。
- `<3.2>` 处,调用 `MapperMethod#execute(SqlSession sqlSession, Object[] args)` 方法,执行 MapperMethod 方法。关于 MapperMethod ,在 [「5. MapperMethod」](https://svip.iocoder.cn/MyBatis/binding-package/#) 中,详细解析。
# 5. MapperMethod
`org.apache.ibatis.binding.MapperMethod` Mapper 方法。在 Mapper 接口中,每个定义的方法,对应一个 MapperMethod 对象。
## 5.1 构造方法
```
// MapperMethod.java
/**
* SqlCommand 对象
*/
private final SqlCommand command;
/**
* MethodSignature 对象
*/
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
```
- `command` 属性SqlCommand 对象。关于它的详细解析,见 [「6. SqlCommand」](https://svip.iocoder.cn/MyBatis/binding-package/#) 。
- `method` 属性MethodSignature 对象。关于它的详细解析,见 [「7. MethodSignature」](https://svip.iocoder.cn/MyBatis/binding-package/#) 。
## 5.2 execute
因为涉及比较多的后面的内容,所以放在 详细解析。
心急的胖友,可以先看看 [《Mybatis3.3.x技术内幕十一执行一个Sql命令的完整流程》](https://my.oschina.net/zudajun/blog/670373) 。
# 6. SqlCommand
SqlCommand ,是 MapperMethod 的内部静态类SQL 命令。
## 6.1 构造方法
```
// SqlCommand.java
/**
* {@link MappedStatement#getId()}
*/
private final String name;
/**
* SQL 命令类型
*/
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// <1> 获得 MappedStatement 对象
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
// <2> 找不到 MappedStatement
if (ms == null) {
// 如果有 @Flush 注解,则标记为 FLUSH 类型
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else { // 抛出 BindingException 异常,如果找不到 MappedStatement
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
// <3> 找到 MappedStatement
} else {
// 获得 name
name = ms.getId();
// 获得 type
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) { // 抛出 BindingException 异常,如果是 UNKNOWN 类型
throw new BindingException("Unknown execution method for: " + name);
}
}
}
```
- `name` 属性,对应 `MappedStatement#getId()` 方法获得的标识。实际上,就是 `${NAMESPACE_NAME}.${语句_ID}` 例如:`"org.apache.ibatis.autoconstructor.AutoConstructorMapper.getSubject2"` 。
- `type` 属性SQL 命令类型。`org.apache.ibatis.mapping.SqlCommandType` 类,代码如下:
```
// SqlCommandType.java
public enum SqlCommandType {
/**
* 未知
*/
UNKNOWN,
/**
* 插入
*/
INSERT,
/**
* 更新
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 查询
*/
SELECT,
/**
* FLUSH
*/
FLUSH
}
```
- `<1>` 处,调用 `#resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration)` 方法,获得 MappedStatement 对象。详细解析,胖友先跳到 [「6.2 resolveMappedStatement」](https://svip.iocoder.cn/MyBatis/binding-package/#) 。
- `<2>` 处,如果找不到 MappedStatement 对象,说明该方法上,没有对应的 SQL 声明。那么在判断是否有 `@Flush` 注解,如果有,说明该方法是用于执行 flush 操作,否则,抛出 BindingException 异常。
- `<3>` 处,如果找到 MappedStatement 对象,则初始化 `name` 和 `type` 属性。
## 6.2 resolveMappedStatement
`#resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration)` 方法,获得 MappedStatement 对象。代码如下:
```
// SqlCommand.java
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// <1> 获得编号
String statementId = mapperInterface.getName() + "." + methodName;
// <2> 如果有,获得 MappedStatement 对象,并返回
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
// 如果没有,并且当前方法就是 declaringClass 声明的,则说明真的找不到
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
// 遍历父接口,继续获得 MappedStatement 对象
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
// 真的找不到,返回 null
return null;
}
```
- `<1>` 处,获得编号。这个编号,和我们上文提到的 `${NAMESPACE_NAME}.${语句_ID}` 。
- `<2>` 处,通过 `Configuration#hasStatement(String statementId)` 方法,判断是否有 MappedStatement 。如果有,则调用 `Configuration#getMappedStatement(String statementId)` 方法,获得 MappedStatement 对象。关于 Configuration 我们在后续的文章中详细解析。在这里胖友只需要知道Configuration 里缓存了所有的 MappedStatement ,并且每一个 XML 里声明的例如 `<select />` 或者 `<update />` 等等,都对应一个 MappedStatement 对象。
- `<3>` 处,如果没有,并且当前方法就是 `declaringClass` 声明的,则说明真的找不到。
- `<4>` 处,遍历父接口,**递归**继续获得 MappedStatement 对象。**因为,该该方法定义在父接口中**。
# 7. MethodSignature
MethodSignature ,是 MapperMethod 的内部静态类,方法签名。
## 7.1 构造方法
```
// MethodSignature.java
/**
* 返回类型是否为集合
*/
private final boolean returnsMany;
/**
* 返回类型是否为 Map
*/
private final boolean returnsMap;
/**
* 返回类型是否为 void
*/
private final boolean returnsVoid;
/**
* 返回类型是否为 {@link org.apache.ibatis.cursor.Cursor}
*/
private final boolean returnsCursor;
/**
* 返回类型是否为 {@link java.util.Optional}
*/
private final boolean returnsOptional;
/**
* 返回类型
*/
private final Class<?> returnType;
/**
* 返回方法上的 {@link MapKey#value()} ,前提是返回类型为 Map
*/
private final String mapKey;
/**
* 获得 {@link ResultHandler} 在方法参数中的位置。
*
* 如果为 null ,说明不存在这个类型
*/
private final Integer resultHandlerIndex;
/**
* 获得 {@link RowBounds} 在方法参数中的位置。
*
* 如果为 null ,说明不存在这个类型
*/
private final Integer rowBoundsIndex;
/**
* ParamNameResolver 对象
*/
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
// 初始化 returnType 属性
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) { // 普通类
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) { // 泛型
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else { // 内部类等等
this.returnType = method.getReturnType();
}
// 初始化 returnsVoid 属性
this.returnsVoid = void.class.equals(this.returnType);
// 初始化 returnsMany 属性
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
// 初始化 returnsCursor 属性
this.returnsCursor = Cursor.class.equals(this.returnType);
// 初始化 returnsOptional 属性
this.returnsOptional = Optional.class.equals(this.returnType);
// <1> 初始化 mapKey
this.mapKey = getMapKey(method);
// 初始化 returnsMap
this.returnsMap = this.mapKey != null;
// <2> 初始化 rowBoundsIndex、resultHandlerIndex
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 初始化 paramNameResolver
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
```
- `<1>` 处,调用 `#getMapKey(Method method)` 方法,获得注解的 `{@link MapKey#value()}` 。代码如下:
```
// MethodSignature.java
private String getMapKey(Method method) {
String mapKey = null;
// 返回类型为 Map
if (Map.class.isAssignableFrom(method.getReturnType())) {
// 使用 @MapKey 注解
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
// 获得 @MapKey 注解的键
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
```
- `<2>` 处,调用 `#getUniqueParamIndex(Method method, Class<?> paramType)` 方法,获得指定参数类型在方法参数中的位置。代码如下:
```
// MethodSignature.java
private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
// 遍历方法参数
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (paramType.isAssignableFrom(argTypes[i])) { // 类型符合
// 获得第一次的位置
if (index == null) {
index = i;
// 如果重复类型了,则抛出 BindingException 异常
} else {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
```
## 7.2 convertArgsToSqlCommandParam
`#convertArgsToSqlCommandParam(Object[] args)` 方法,获得 SQL 通用参数。代码如下:
```
// MethodSignature.java
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
```
- 在内部,会调用 `ParamNameResolver#getNamedParams(Object[] args)` 方法。在 [《精尽 MyBatis 源码分析 —— 反射模块》](http://svip.iocoder.cn/MyBatis/reflection-package) 中,有详细解析。
- 如下图是一个 `args` 和转换后的结果的示例:[示例](http://static.iocoder.cn/images/MyBatis/2020_02_07/03.png)
# 666. 彩蛋
木有彩蛋,哈哈哈哈。
参考和推荐如下文章:
- 祖大俊 [《Mybatis3.3.x技术内幕动态代理之投鞭断流自动映射器Mapper的底层实现原理](https://my.oschina.net/zudajun/blog/666223)
- 徐郡明 [《MyBatis 技术内幕》](https://item.jd.com/12125531.html) 的 [「2.8 bind 模块」](https://svip.iocoder.cn/MyBatis/binding-package/#) 小节