Java反射方法中的参数名

花了两天撸了个Spring Mvc框架,核心内容就是反射和反射和反射….(调皮一波。SpringMVC有强大的数据绑定,它可以将提交的数据封装并绑定到方法参数中。如果是一个Javabean对象,只需要参数类型反射出来,然后将表单封装到该类型的对象,运行时注入到方法的参数中。而如果方法参数是单个字段,那就需要根据字段名来获取提交的数据。

通过翻阅Spring框架的源代码,对于反射方法参数名,spring提供了两种方法:如果JDK是1.8的话就使用Java 8新的反射API反射出方法参数名,否则就是用ASM框架读取class文件来获取参数名。具体实现如下:实现类为DefaultParameterNameDiscoverer

public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {

private static final boolean standardReflectionAvailable =
        (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_18);


public DefaultParameterNameDiscoverer() {
    if (standardReflectionAvailable) {
        addDiscoverer(new StandardReflectionParameterNameDiscoverer());
    }
    addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
}

}

可以清清楚楚的看到如果jdk版本大于等于1.8则使用StandardReflectionParameterNameDiscoverer 反射出方法参数名。

public class StandardReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {

@Override
public String\[\] getParameterNames(Method method) {
    Parameter\[\] parameters = method.getParameters();
    String\[\] parameterNames = new String\[parameters.length\];
    for (int i = 0; i < parameters.length; i++) {
        Parameter param = parameters\[i\];
        if (!param.isNamePresent()) {
            return null;
        }
        parameterNames\[i\] = param.getName();
    }
    return parameterNames;
}

@Override
public String\[\] getParameterNames(Constructor<?> ctor) {
    Parameter\[\] parameters = ctor.getParameters();
    String\[\] parameterNames = new String\[parameters.length\];
    for (int i = 0; i < parameters.length; i++) {
        Parameter param = parameters\[i\];
        if (!param.isNamePresent()) {
            return null;
        }
        parameterNames\[i\] = param.getName();
    }
    return parameterNames;
}

}

如果jdk版本低于1.8那么就是用asm框架读取class文件,获取方法参数名。具体的实现类:LocalVariableTableParameterNameDiscoverer

我们之所以可以反射出方法参数名,是因为在Java编译后的class文件中有个LocalVariableTable 属性,这个属性保存的就是方法参数和方法内的本地变量。但是jdk并没有提供读取LocalVariableTable 属性的api,这就需要借助第三方api。其中操作class文件的主要第三方类库有 ObjectWeb 的 ASM,Apache 的 Commons BCEL还有 Javassist等,其中ASM知名度比较高,AspectJ, CGLIB使用的都是ASM类库。

那么我们看一看如何使用这些类库反射出方法参数名,首先我们准备一个类,类里面只有个sum方法,我们就反射出sum方法参数的名字

public class SumClass {
public int sum(int i, int j){
int sum = i + j;
return sum;
}
}

JDK1.8反射方法名

如果使用jdk的方式反射出方法参数名,首先你的jdk版本必须是1.8以上,而且还需要在编译的时候(javac)需要添加-parameters参数否则我们是反射不到方法参数名的,我们在cmd命令下使用javac -help命令查看帮助。但是在实际开发中我们都是使用maven管理项目,当然maven也为我们提供了方式,只需修改 pom 文件的 maven-compiler-plugin 插件配置即可,就是加上了 compilerArgs 节点。


org.apache.maven.plugins
maven-compiler-plugin
3.0

1.8
1.8

-parameters


然后我们在写一个类,看一下实际效果:

public class Jdk8Reflect {
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
//方法名和参数类型列表 parameterTypes
Method method = SumClass.class.getDeclaredMethod(“sum”, int.class, int.class);

    Parameter\[\] parameters = method.getParameters();
    for(Parameter p :parameters){
        System.out.println(p.getType().getName() + "--" + p.getName());
    }
}

}

我们只需要给出方法名和方法类型列表,就可以反射出方法参数信息。

BCEL反射方法名

BCEL首先要在maven添加依赖,然后获取到反射的Class和方法,然后传递给BCEL,最后会得到局部变量表,这个局部变量表会存放方法参数和局部变量,这里sum方法有一个局部变量就是sum,运行下面方法也会打印出来。当然每个方法都隐藏一个this指针,这个指针也会被打印出来。

public class BcelReflect {

public static void main(String\[\] args) throws NoSuchMethodException, SecurityException, ClassNotFoundException {
    Class<?> clazz = SumClass.class;

    //获取反射的方法 
    java.lang.reflect.Method method = clazz.getDeclaredMethod("sum", int.class, int.class);

    //设置反射的类
    JavaClass lookupClass = Repository.lookupClass(clazz);
    //设置方法
    Method bcelMethod = lookupClass.getMethod(method);

    //L获取局部变量表
    LocalVariableTable localVariableTables = bcelMethod.getLocalVariableTable();
    //所有局部变量
    LocalVariable\[\] localVariables = localVariableTables.getLocalVariableTable();
    for(LocalVariable l:localVariables){
        String signature = l.getSignature();
        //打印  属性名 签名  属性类型
        System.out.println(l.getName() + " ---" + signature +"---" + Type.getReturnType(signature));
    }
}

}

效果如下:

ASM反射方法参数名

public class AsmReflect {
public static void main(String[] args) throws IOException {
Class<?> clazz = SumClass.class;
ClassReader classReader = new ClassReader(clazz.getName());
classReader.accept(new ParameterNameDiscoveringVisitor(“sum”, “(II)I”), 0);
}

private static class ParameterNameDiscoveringVisitor  extends ClassVisitor{
    private final String methodName;
    private final String methodDesc;
    public ParameterNameDiscoveringVisitor(String name, String desc) {
        super(Opcodes.ASM5);
        this.methodName = name;
        this.methodDesc = desc;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String\[\] exceptions) {
        if(name.equals(methodName) && desc.equals(methodDesc)){
            return new LocalVariableTableVisitor();
        }
        return null;
    }

}

public static class LocalVariableTableVisitor extends MethodVisitor {
    public LocalVariableTableVisitor() {
        super(Opcodes.ASM5);
    }

     @Override
    public void visitLocalVariable(String name, String description, String signature, Label start, Label end, int index) {
        System.out.println(name + "  " + description);
    }
}

}

效果如下:

由于asm的包比较小 53 KB,这就意味着封装的很少,自己要写的代码比较多。本文github地址:传送门

坚持原创技术分享,您的支持将鼓励我继续创作!
  • 本文作者: XiuYu.Ge
  • 本文链接: 394.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!