背景
一次偶爾發現了工作的項目中有人寫的代碼Dao層接口傳遞多個參數沒有用@param注解,xml文件中直接通過參數名竟然可以拿到參數值,示例如下(公司代碼不便透露寫了個測試用例):
Dao層接口
public interface UserMapper { /** * 添加用戶 * @param username * @param password * @return */ int insertUser(String username,String password); }
xml
<insert id="insertUser" > insert into user (username,password) values (#{username},#{password}) </insert>
初步研究
因為在博主眼里平時傳遞多個參數時都會加上@Param注解的,這樣xml獲取參數時才能對應上,發現這個現象后開始在網上尋求答案,大體上都是說jdk1.8之后提供了Parameter反射類可以通過其getName()方法獲取到。於是博主自己寫了個測試類進行探究:
測試代碼
/** * @author blue */ public class TestUtil { public void test(String aaa,String bbb){ } public static void main(String[] args) throws NoSuchMethodException { //獲取TestUtil的class對象 Class c = TestUtil.class; //獲取TestUtil的test方法 Method method = c.getMethod("test", String.class, String.class); //獲取test方法中的參數 Parameter[] parameters = method.getParameters(); for (Parameter parameter : parameters) { System.out.println(parameter.getName()); } } }
運行結果

進一步研究(源碼解析)
網上找到的答案對這個問題只有一個大概的解析,其中真正的原理還需要自己一步一步解析源碼才能了解清楚,於是博主又開始通過@Param一步一步找到其中的原理。
1.通過Param接口找到使用它的類(根據類的名字很容易猜到是ParamNameResolver這個類來獲取參數的)

2.發現ParamNameResolver的構造方法有使用到Param,大體上分析一下,先判斷方法上是否有@Param注解,如果有直接獲取其value值作為語句參數名稱,
否則就通過getActualParamName方法去獲取參數。具體看下面代碼的注解
public ParamNameResolver(Configuration config, Method method) { final Class<?>[] paramTypes = method.getParameterTypes(); final Annotation[][] paramAnnotations = method.getParameterAnnotations(); final SortedMap<Integer, String> map = new TreeMap<Integer, String>(); 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]) { // 如果方法名有Param注解,直接獲取注解上value作為參數值作為語句參數名稱(也就是) if (annotation instanceof Param) { hasParamAnnotation = true; name = ((Param) annotation).value(); break; } } //如果沒有獲取到Param注解上面的value值 if (name == null) { // @Param was not specified. //isUseActualParamName:mybatis的全局參數,是否允許使用方法簽名中的名稱(也就是方法的參數名稱)作為語句參數名稱 //mybaties3.4.1版本開始默認為TRUE if (config.isUseActualParamName()) { //獲取參數名...(接着往下看) name = getActualParamName(method, paramIndex); } //如果獲取不到就設置成"0","1",... if (name == null) { // use the parameter index as the name ("0", "1", ...) // gcode issue #71 name = String.valueOf(map.size()); } } map.put(paramIndex, name); } names = Collections.unmodifiableSortedMap(map); }
3.進入getActualParamName方法中 判斷了當前jdk版本中是否有Parameter類
private String getActualParamName(Method method, int paramIndex) { //判斷jdk版本是否存在Parameter類(具體是通過 Resources.classForName("java.lang.reflect.Parameter")判斷,拋異常了就不存在,感興趣的可以自己點進去看下源碼) if (Jdk.parameterExists) { return ParamNameUtil.getParamNames(method).get(paramIndex); } return null; }
4.進入到ParamNameUtil.getParamNames()終於找到了Parameter類,先通過method獲取Parameter數組,然后循環遍歷將參數名存入到list中。
@UsesJava8 public class ParamNameUtil { public static List<String> getParamNames(Method method) { return getParameterNames(method); } public static List<String> getParamNames(Constructor<?> constructor) { return getParameterNames(constructor); } //Executable是Method和Constructor的公共父類 private static List<String> getParameterNames(Executable executable) { final List<String> names = new ArrayList<String>(); //獲取到方法中的參數數組 final Parameter[] params = executable.getParameters(); for (Parameter param : params) { names.add(param.getName()); } return names; } private ParamNameUtil() { super(); } }
總結
通過對mybaties@Param注解相關源碼進行分析,知道了沒有添加@Param注解時會通過Parameter反射類去獲取方法的參數名,眾所周知java反射機制效率很低,所以在jdk1.8及以上版本項目中如果沒有設置Mybatie的isUseActualParamName=false
在傳遞參數時盡量使用@Param注解
