背景
一次偶尔发现了工作的项目中有人写的代码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注解