使用过 Mybatis 框架的人都很好奇接口方法的实现逻辑及具体方法执行逻辑,因此本篇文章从接口对象的获取开始,最后对方法执行逻辑对框架逻辑进行解析。
1. 接口代理类创建
MapperProxy 的创建逻辑使用 DefaultSqlSession 中的 getMapper 方法进行创建;其中创建的 MapperProxy 核心类为 MapperProxyFactory 工厂;
1.1. MapperProxyFactory 类
MapperProxyFactory 类中拥有的两个属性:mapperInterface 属性为当前代理接口的类对象以及 methodCache 属性为代理对象所使用的方法缓存;mapperInterface 属性是为了每次创建代理对象使用,而 methodCache 属性则是为了避免重复创建方法对象而将已创建过的方法缓存起来用于复用;
核心方法为使用 SqlSession 对象创建接口代理对象的 newInstance 方法,其首先创建接口代理对象 MapperProxy,然后使用 java 动态代理对象 Proxy 创建接口的动态实现;
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
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;
}
@SuppressWarnings("unchecked")
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);
}
}
1.2. MapperProxy 类的创建
MapperProxy 类使用 MethodHandle 对类方法进行反射调用;其拥有的三个常量全部用于创建 MethodHandle 对象:其中 ALLOWED_MODES 常量标识 MethodHandle 可以调用的方法级别,lookupConstructor 常量用于 java 1.8 版本中创建 MethodHandles 对象,而在 1.9 及以后的版本使用 MethodHandles 类中的 privateLookupIn 方法创建 MethodHandles ,MapperProxy 使用 privateLookupInMethod 常量存储;
lookupConstructor 常量与 privateLookupInMethod 常量的赋值是在静态代码块中赋值,首先尝试获取 MethodHandles 中的 privateLookupIn 方法对象并将其存储到 privateLookupInMethod 常量中,如果当前 java 版本为 1.8 ,其 MethodHandles 类中没有 privateLookupInMethod 方法,则将 MethodHandles 中 lookup 类中 class 与 int 参数的构造方法对象赋值给 lookupConstructor 常量,其中 class 类型参数为需要查询方法的类,int 参数则是允许查询方法级别 mode;
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException e) {
privateLookupIn = null;
}
privateLookupInMethod = privateLookupIn;
Constructor<Lookup> lookup = null;
if (privateLookupInMethod == null) {
// JDK 1.8
try {
lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
lookup.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
e);
} catch (Exception e) {
lookup = null;
}
}
lookupConstructor = lookup;
}
}
MapperProxy 类拥有三个类属性,其为当前使用的 SqlSession 对象 sqlSession 属性、当前 MapperProxy 对象所代理的接口类型 Class 对象以及当前已经缓存了的方法缓存 MapperProxy 属性;其只拥有一个构造参数,这意味着在创建 MapperProxy 对象时,必须对对象上述三个属性进行赋值。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
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;
}
}
2. 方法代理
2.1 invoke 方法
java 动态代理代理类必须实现 InvocationHandler 接口,其中 invoke 方法就是动态代理实现类具体执行方法;invoke 拥有三个参数,第一个参数为 java 运行时动态创建对象,第二个参数为当前所调用的方法,第三个参数为方法调用方传入的方法实参;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
MapperProxy 中的 invoke 方法首先判断当前执行方法所属对象是否为 Object,若该方法所属对象为 Object ,直接调用 method 对象的 invoke 方法执行原始方法;当所属对象不为 Object 时,调用 cachedInvoker 方法创建对应的 MapperMethodInvoker 对象,然后调用其 invoke 方法执行代理方法实现。
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
cachedInvoker 方法只有一个方法逻辑,就是调用 MapUtil 的 computeIfAbsent 静态方法获取方法对应的 MapperMethodInvoker 对象,computeIfAbsent 方法首先尝试从 methodCache 属性中获取 MapperMethodInvoker 对象,未获取到时调用第三个参数定义的方法进行创建并将其保存到 methodCache 属性中去;MapperMethodInvoker 对象的创建首先判断是否为 default 方法,对于非 default 方法使用 PlainMethodInvoker 对象,default 方法使用 DefaultMethodInvoker 对象;其中注意的是 java 1.8 版本使用 lookupConstructor 属性创建 MethodHandle 对象,java 1.9 及以上版本使用 privateLookupInMethod 属性创建 MethodHandle 对象。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (!m.isDefault()) {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
}
return new DefaultMethodInvoker(getMethodHandleJava9(method));
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
private MethodHandle getMethodHandleJava9(Method method)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
declaringClass);
}
private MethodHandle getMethodHandleJava8(Method method)
throws IllegalAccessException, InstantiationException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
}
}
2.2 MapperMethodInvoker 接口
MapperMethodInvoker 接口用于动态代理对象方法的实现,其中只有一个 invoke 方法用于执行代理方法;
在 mybatis 中 MapperMethodInvoker 接口拥有两个 MapperMethodInvoker 接口的实现类,PlainMethodInvoker 类用于执行代理接口的非 default 方法,DefaultMethodInvoker 类则是用于 default 方法;
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
PlainMethodInvoker 使用 MapperMethod 对象实现接口方法,invoke 调用 MapperMethod 对象中的 excute 方法执行代理方法;
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
PlainMethodInvoker 使用 MethodHandle 对象实现接口方法,invoke 调用 MethodHandle 对象中的 invokeWithArguments 方法执行代理方法;
private static class DefaultMethodInvoker implements MapperMethodInvoker {
private final MethodHandle methodHandle;
public DefaultMethodInvoker(MethodHandle methodHandle) {
this.methodHandle = methodHandle;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return methodHandle.bindTo(proxy).invokeWithArguments(args);
}
}
2.3 MapperMethod 类
MapperMethod 对象用于执行代理接口的非 default 方法,他拥有两个类属性,command 属性存储与数据库交互相关属性、method 属性则是存储 java 方法相关信息,如返回数据类型、请求参数及注解相关参数;这两个属性都是使用调用方传入的 Configuration 配置对象、被代理接口类型的 class 对象以及接口方法 Method 对象参数创建;
public class MapperMethod {
private final SqlCommand command;
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);
}
}
execute 方法根据 command 属性解析出的 sql 类型执行对应的方法获取结果,其中增删改三种类型的方法拥有相似逻辑,首先调用 method 的 convertArgsToSqlCommandParam 方法将方法传参转化为 sql 执行所需参数,然后调用 sqlSession 对象执行对应方法同时调用 rowCountResult 方法将结果转化为方法需要的参数;查询类型方法根据返回类型执行对应方法:没有返回类型且方法拥有结果处理器时执行 executeWithResultHandler 方法、集合或数组时执行 executeForMany 方法、Map 时执行 executeForMap 方法、Cursor 时执行 executeForCursor 方法其他情况则直接调用 sqlSession 中的 selectOne 方法然后将其返回结果直接返回。
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
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: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ "' attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
}
rowCountResult 方法根据方法返回值类型来确定具体返回值,将数量数据库更新的数量转化为 int、long 以及 boolean。
public class MapperMethod {
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long) rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException(
"Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
}
通过对 executeWithResultHandler 方法进行分析,首先会验证对应 Statement 对象是否为 CALLABLE 类型或该方法设置了 ResultMap 或 ResultSet;在完成验证后调用 sqlSession 对象的 select 方法进行查询;
public class MapperMethod {
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException(
"method " + command.getName() + " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
}
executeForMany 首先调用 sqlSession 对象的 selectList 方法获取查询结果集,然后在返回类型为数组时调用 convertToArray 方法将结果集转化为数组,而返回值为 Collection 集合类型(非 List 类型)时调用 convertToDeclaredCollection 方法将结果集转化为对应集合对象;
public class MapperMethod {
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
}
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
return result;
}
}
executeForCursor 方法直接调用 sqlSession 对象的 selectCursor 方法获取结果集的 Cursor 指针对象;executeForMap 方法则是直接调用 sqlSession 对象的 selectMap 方法获取 mapKey 与 结果对象映射 Map;
public class MapperMethod {
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
Cursor<T> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectCursor(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectCursor(command.getName(), param);
}
return result;
}
}
convertToDeclaredCollection 方法首先利用传入的 config 参数中的 create 方法创建返回值类型对象,然后调用 config 参数中的 newMetaObject 方法创建返回值的 MetaObject 封装对象同时调用 addAll 方法将查询结果集添加到创建的集合中;
public class MapperMethod {
private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
metaObject.addAll(list);
return collection;
}
}
convertToArray 方法在返回值为非基础类型数组时,直接调用 list 对象的 toArray 方法将查询到的结果集转化为数组,而在返回值为基础类型数组时,则是通过遍历查询结果集,调用 Array 类的 set 方法设置数组对应值;
public class MapperMethod {
@SuppressWarnings("unchecked")
private <E> Object convertToArray(List<E> list) {
Class<?> arrayComponentType = method.getReturnType().getComponentType();
Object array = Array.newInstance(arrayComponentType, list.size());
if (!arrayComponentType.isPrimitive()) {
return list.toArray((E[]) array);
}
for (int i = 0; i < list.size(); i++) {
Array.set(array, i, list.get(i));
}
return array;
}
}
2.3.1 SqlCommand 类
SqlCommand 类用于标识 MappedStatement 属性,他拥有两个类属性:name 与 type;name 属性为关联 MappedStatement 的 id 值,type 则是 MappedStatement 的 type 类型值,即 mapper 方法对应的增删改查方法解析值(select、update、insert 以及 delete)或用于刷新 Statement 对象的 FLUSH。
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
}
2.3.1.1 SqlCommand 对象的创建
SqlCommand 类只有一个带三个参数(配置类对象、代理接口类型 Class 对象以及关联方法 Method 对象)的构造方法;创建 SqlCommand 对象首先调用 resolveMappedStatement 方法获取方法对应的 MappedStatement 对象,method 方法拥有对应 MappedStatement 对象时,直接从 MappedStatement 对象获取相关属性值,没有对应 MappedStatement 对象时该方法必须使用 Flush 注解修饰,然后将 type 属性设置为 FLUSH;
public static class SqlCommand {
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) == null) {
throw new BindingException(
"Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
}
name = null;
type = SqlCommandType.FLUSH;
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
}
resolveMappedStatement 首先尝试使用接口全限定类名 + “.” + 方法名获取 configurationtion 对象对应的 MappedStatement 对象,未获取到对应 MappedStatement 对象时,遍历代理接口的父接口列表,若方法所属类型为父接口的超类时,尝试使用接口获取对应 MappedStatement 对象;
public static class SqlCommand {
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass,
Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
}
if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
2.3.2 MethodSignature 类
MethodSignature 对象用于标识方法中相关签名对象,其中包含返回参数、方法参数及方法所使用的 mapKey 值;其中与返回参数相关参数有标识返回值类型的 returnType 属性以及 5 个标识具体返回值类型的属性(returnsMany 属性标识返回值是否为列表类型、returnsMap 属性标识返回值是否为 Map 类型、returnsVoid 属性标识是否没有返回值、returnsCursor 属性标识返回值是否为 Cursor 指针以及 returnsOptional 属性标识返回值是否为 Optional 对象)。
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
}
2.3.2.1 MethodSignature 对象的创建
在创建 MethodSignature 对象时首先会调用 TypeParameterResolver 的 resolveReturnType 方法解析方法返回值类型,然后将对应值赋值给相关的返回值属性中;之后调用 getMapKey 方法获取 MapKey 值;随后调用 getUniqueParamIndex 为 rowBoundsIndex 与 resultHandlerIndex 属性赋值,最后则是使用 configuration 与 method 对象创建 ParamNameResolver 对象并将其存储于 paramNameResolver 属性中;
public static class MethodSignature {
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
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();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
}
从 getMapKey 方法可以看出只有返回值类型为 Map 及其子类同时方法使用 MapKey 注解进行修饰时,才会使用 MapKey 中的 value 方法值作为 mapKey 属性值。
public static class MethodSignature {
private String getMapKey(Method method) {
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
}
getUniqueParamIndex 方法用于获取 paramType 在 method 形参列表的索引值;其做了两件事情,第一件事情是验证 paramType 是否为 method 形参列表中唯一类型,第二件事情则是获取 paramType 在参数列表中的索引位置。
public static class MethodSignature {
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) {
throw new BindingException(
method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
index = i;
}
}
return index;
}
}
2.3.2.2 ParamNameResolver 类
ParamNameResolver 类拥有一个字符串类型常量 GENERIC_NAME_PREFIX 以及一个大小为 10 的字符串数组类型常量 GENERIC_NAME_CACHE,GENERIC_NAME_CACHE 在静态代码块中使用 GENERIC_NAME_PREFIX 常量进行初始化,GENERIC_NAME_CACHE 每个元素值为 GENERIC_NAME_PREFIX + 索引 + 1 的格式;
public class ParamNameResolver {
public static final String GENERIC_NAME_PREFIX = "param";
public static final String[] GENERIC_NAME_CACHE = new String[10];
static {
for (int i = 0; i < 10; i++) {
GENERIC_NAME_CACHE[i] = GENERIC_NAME_PREFIX + (i + 1);
}
}
}
2.3.2.2.1 对象的创建
ParamNameResolver 类只有一个拥有两个参数(Configuration 配置对象 config 与被解析方法对象 method)的构造方法,在创建 ParamNameResolver 过程中首先使用 config 属性中的是否使用真实属性名配置(在 xml 配置文件中 Setting 标签中的 useActualParamName 子标签进行定义)对 useActualParamName 属性赋值;然后对方法的形参列表进行遍历,在遍历的过程中,只会处理非 RowBounds 与 ResultHandler 类型的参数,在通过验证之后首先尝试使用 Param 注解定义的参数名,一旦有一个参数使用 Param 注解进行命名,则将 hasParamAnnotation 置为真;如果当前参数未使用 Param 注解修饰,则在 useActualParamName 置为 true 时直接使用真实的参数名否则则使用索引 + 1 作为参数名。
public class ParamNameResolver {
private final boolean useActualParamName;
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
if (useActualParamName) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
private String getActualParamName(Method method, int paramIndex) {
return ParamNameUtil.getParamNames(method).get(paramIndex);
}
private static boolean isSpecialParameter(Class<?> clazz) {
return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
}
}
2.3.2.2.2 getNamedParams 方法
MethodSignature 对象的 convertArgsToSqlCommandParam 方法调用 paramNameResolver 对象的 getNamedParams 方法获取执行 Sql 所需的参数类型格式;
public static class MethodSignature {
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
}
getNamedParams 方法形参数为 0 或实参为空时直接返回 null,随后在方法形参数为 1 且参数未使用 Param 注解修饰时,调用 wrapToMapIfCollection 方法将集合类型参数封装为 Map,其中传入的参数有调用方传入的实参值,在使用实际参数名传入实际参数名否则传 null;在实际形参数不为 1 或存在使用使用 Param 注解修饰的参数时遍历实际形参列表将定义形参名与实参值映射添加到返参 map 中,同时在实际形参索引值小于时,将 GENERIC_NAME_CACHE 对应位置值与实参值映射添加到返参 map 中。
public class ParamNameResolver {
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
}
if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = i < 10 ? GENERIC_NAME_CACHE[i] : GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
wrapToMapIfCollection 方法在 object 值类型为 Collection 集合时,将 "collection" 与 object 映射保存到返参结果中,同时如果 object 值类型为 List 列表时还需将 "list" 与 object 映射添加到返参结果中,最后在 actualParamName 不会空时还会将 actualParamName 值与 object 映射添加到返参结果中;
在 object 值类型为数组时,需将 "array" 与 object 映射添加到返参结果同时如 Collection 集合的数据处理一样在 actualParamName 不会空时将 actualParamName 值与 object 映射添加到返参结果中;
其余情况下则直接将值返回;
public class ParamNameResolver {
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
if (object instanceof Collection) {
ParamMap<Object> map = new ParamMap<>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
}
if (object != null && object.getClass().isArray()) {
ParamMap<Object> map = new ParamMap<>();
map.put("array", object);
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
}
return object;
}
}