Mybatis参数解析分析
这个例子很简单,日常调用接口的时候,传递的参数,是被Mybatis怎么来解析的,为什么在写XML中,idea中会有提示,param1,param2。有的时候还有默认的参数,现在就来分析分析
例子就不举了,太简单了。直接开冲
组装参数
看过之前文章的朋友,肯定会很熟悉下面的代码。
在说一个小tips,在分析组装参数的时候,要盯着args。因为在反射调用的时候,会将args传递过来,所以,主要盯着args,肯定能找到对应的代码
// 这是返回值为Map的查询。别的基本都一样。
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
// 这里做转化。这个method就是 `MethodSignature`,所以,直接看这里面写了什么东西就好了
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;
}
MethodSignature.convertArgsToSqlCommandParam(args)
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
哎呀,上面的代码平平无奇,调用了ParamNameResolver
的方法,直接看
ParamNameResolver.getNamedParams
这里的代码不难,看看就能通,注意注释
先别看这个,看完下面的,再看这个
public ParamNameResolver(Configuration config, Method method) {
// 这个值默认就是true,
this.useActualParamName = config.isUseActualParamName();
// 拿到参数类型的数组
final Class<?>[] paramTypes = method.getParameterTypes();
// 拿到参数的注解
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
//treeMap用于排序
final SortedMap<Integer, String> map = new TreeMap<>();
//数量
int paramCount = paramAnnotations.length;
//解析@param注解。开始遍历了
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
//判断方法的参数是否有 RowBounds和ResultHandler,跳过这些
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
// 遍历注解列表,如果有一个是param的。赋属性,和获取值给name。,注意,这里比较骚的是paramAnnotations是一个二维数组。
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
//没找到名字
if (name == null) {
// 这个参数的意思是,是否要实际的参数名字
if (useActualParamName) {
//这还不是字段的名字,这是这上面注解的值,这是JDK的方法, 之后我看看这里干了什么事情
name = getActualParamName(method, paramIndex);
}
// 要还是没有的话,
if (name == null) {
//就直接给一个String.valueOf(map.size());
name = String.valueOf(map.size());
}
}
// 所以这里的map也就是Names,是用treemap来做排序的,并且name如果是@Param的话,就是他,没有的话就是arg0,arg1......
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
private String getActualParamName(Method method, int paramIndex) {
return ParamNameUtil.getParamNames(method).get(paramIndex);
}
看完上面的代码,已经知道了names里面的值是怎么来的了,继续看下面的。
看这第一行的代码发现有一个names,这个names是在MethodSignature创建的时候创建的。所以,先看看他是怎么创建的,主要干了什么事情。关于MethodSignature看上面的代码。
public Object getNamedParams(Object[] args) {
//这个names在哪里用的呢?还是我们的老办法,直接点开看,看看哪里能引用他。最后找到了在创建MethodSignature的时候会创建ParamNameResolver,并且解析Param注解。看上面的代码分析
final int paramCount = names.size();
// 如果两个中都没有,就说明,这个查询没有参数。方法上没有参数。其实我感觉args校验没有啥意思,如果说方法上有参数,但是这里方法调用的时候依然没有,那也idea的编译都过不了。
if (args == null || paramCount == 0) {
return null;
//如果说没有param的注解,并且参数的数量为一个,就会对于collection或者array类型的增加默认值。
} else if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
// 否则,就遍历Names,注意Names是Treemap,他的遍历是中序遍历。如果key是数字的话, 那就是1,2,3,4,5
//
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// 将之前的的值放进去,value就是注解的值,或者arg1,arg2,value就是真正的值
param.put(entry.getValue(), args[entry.getKey()]);
// 同时也会添加以 param开头的key
// public static final String GENERIC_NAME_PREFIX = "param";
final String genericParamName = 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;
}
}
//
//如果是collection的话,name就有一个collection,如果是list就是list,如果是array就是array。
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;
} else 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;
}
总结,分为两个阶段,一个节点是解析Param,一个阶段是通过Param组装Map
-
在创建
ParamNameResolver
的时候,会解析方法上的@param
,如果没有的话,就调用getActualParamName
方法(值就是arg1,arg2)获取名字。并且方法参数的下标为key,名字为value,放在treeMap中。 -
组装参数,如果当前的方法没有被
@param
注解,并且参数的值为一个,如果是collection的话,name就有一个collection,如果是list就是list,如果是array就是array。如果不是。就会按照上面组装的那样,key是treeMap中的value,value就是实际的值,并且会放置param开头的参数,就会通过上面组装TreeMap遍历时候通过下标获取值,组装map。返回