资讯专栏INFORMATION COLUMN

Java通过ASM运行时读取方法参数名称

DangoSky / 2392人阅读

摘要:据说已经原生支持参数名读取了。本文以为例进行说明通过字节码操作工具我们可以实现运行时参数名的读写。简单说说原理字节码为每个方法保存了一份方法本地变量列表。

据说Java8已经原生支持参数名读取了。具体不是很清楚。本文以java7为例进行说明.
通过ASM字节码操作工具我们可以实现运行时参数名的读写。
简单说说原理:java字节码为每个方法保存了一份方法本地变量列表。可以通过ASM获取这个列表。但是可能会获得列表顺序与期望的不一致。我们获取的本地变量了列表使用不同的编译器编译得到的顺序可能不同。一般而言,通过javac编译出来的列表顺序是按照本地变量使用的顺序。而我们期望的是声明的顺序。如下面这个方法:

public String handle(String a,String b){
    String c;
    c="str";
    return a+b;
}

得到的localVariable列表顺序:我们期望的是this,a,b,c.而实际上读取到的会是this ,c ,a,b
这个this是非静态方法中的第一个本地变量。所以我们会在下面的代码中对这个顺序进行重排序以解决该问题。

不多说,直接看代码:

package net.xby1993.springmvc.controller;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;

public class MethodParamNamesScanner {

    /**
     * 获取方法参数名列表
     * 
     * @param clazz
     * @param m
     * @return
     * @throws IOException
     */
    public static List getMethodParamNames(Class clazz, Method m) throws IOException {
        try (InputStream in = clazz.getResourceAsStream("/" + clazz.getName().replace(".", "/") + ".class")) {
            return getMethodParamNames(in,m);
        }

    }
    public static List getMethodParamNames(InputStream in, Method m) throws IOException {
        try (InputStream ins=in) {
            return getParamNames(ins,
                    new EnclosingMetadata(m.getName(),Type.getMethodDescriptor(m), m.getParameterTypes().length));
        }

    }
    /**
     * 获取构造器参数名列表
     * 
     * @param clazz
     * @param constructor
     * @return
     */
    public static List getConstructorParamNames(Class clazz, Constructor constructor) {
        try (InputStream in = clazz.getResourceAsStream("/" + clazz.getName().replace(".", "/") + ".class")) {
            return getConstructorParamNames(in, constructor);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        return new ArrayList();
    }
    public static List getConstructorParamNames(InputStream ins, Constructor constructor) {
        try (InputStream in = ins) {
            return getParamNames(in, new EnclosingMetadata(constructor.getName,Type.getConstructorDescriptor(constructor),
                    constructor.getParameterTypes().length));
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        return new ArrayList();
    }
    /**
     * 获取参数名列表辅助方法
     * 
     * @param in
     * @param m
     * @return
     * @throws IOException
     */
    private static List getParamNames(InputStream in, EnclosingMetadata m) throws IOException {
        ClassReader cr = new ClassReader(in);
        ClassNode cn = new ClassNode();
        cr.accept(cn, ClassReader.EXPAND_FRAMES);// 建议EXPAND_FRAMES
        // ASM树接口形式访问
        List methods = cn.methods;
        List list = new ArrayList();
        for (int i = 0; i < methods.size(); ++i) {
            List varNames = new ArrayList();
            MethodNode method = methods.get(i);
            // 验证方法签名
            if (method.desc.equals(m.desc)&&method.name.equals(m.name)) {
//                System.out.println("desc->"+method.desc+":"+m.desc);
                List local_variables = method.localVariables;
                for (int l = 0; l < local_variables.size(); l++) {
                    String varName = local_variables.get(l).name;
                    // index-记录了正确的方法本地变量索引。(方法本地变量顺序可能会被打乱。而index记录了原始的顺序)
                    int index = local_variables.get(l).index;
                    if (!"this".equals(varName)) // 非静态方法,第一个参数是this
                        varNames.add(new LocalVariable(index, varName));
                }
                LocalVariable[] tmpArr = varNames.toArray(new LocalVariable[varNames.size()]);
                // 根据index来重排序,以确保正确的顺序
                Arrays.sort(tmpArr);
                for (int j = 0; j < m.size; j++) {
                    list.add(tmpArr[j].name);
                }
                break;

            }

        }
        return list;
    }

    /**
     * 方法本地变量索引和参数名封装
     * @author xby Administrator
     */
    static class LocalVariable implements Comparable {
        public int index;
        public String name;

        public LocalVariable(int index, String name) {
            this.index = index;
            this.name = name;
        }

        public int compareTo(LocalVariable o) {
            return this.index - o.index;
        }
    }

    /**
     * 封装方法描述和参数个数
     * 
     * @author xby Administrator
     */
    static class EnclosingMetadata {
              //method name
              public String name;
        // method description
        public String desc;
        // params size
        public int size;

        public EnclosingMetadata(String name,String desc, int size) {
                      this.name=name;
            this.desc = desc;
            this.size = size;
        }
    }

    public static void main(String[] args) throws IOException {
        for (Method m : AdminController.class.getDeclaredMethods()) {
            List list = getMethodParamNames(AdminController.class, m);
            System.out.println(m.getName() + ":");
            for (String str : list) {
                System.out.println(str);
            }
            System.out.println("------------------------");
        }
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65802.html

相关文章

  • 【深度好文】深度分析如何获取方法参数

    摘要:但是这种方式对于接口和抽象方法是不管用的,因为抽象方法没有方法体,也就没有局部变量,自然也就没有局部变量表了是通过接口跟语句绑定然后生成代理类来实现的,因此它无法通过解析字节码来获取方法参数名。 声明:本文属原创文章,首发于公号:程序员自学之道,转载请注明出处! 发现问题 对Java字节码有一定了解的朋友应该知道,Java 在编译的时候,默认会将方法参数名丢弃,因此我们无法在运行时获取...

    vslam 评论0 收藏0
  • 为何Spring MVC可获取到方法参数名,而MyBatis却不行?【享学Spring MVC】

    每篇一句 胡适:多谈些问题,少聊些主义 前言 Spring MVC和MyBatis作为当下最为流行的两个框架,大家平时开发中都在用。如果你往深了一步去思考,你应该会有这样的疑问: 在使用Spring MVC的时候,你即使不使用注解,只要参数名和请求参数的key对应上了,就能自动完成数值的封装 在使用MyBatis(接口模式)时,接口方法向xml里的SQL语句传参时,必须(当然不是100%的必须,...

    孙淑建 评论0 收藏0
  • Java动态编程初探

    摘要:动态编程使用场景通过配置生成代码,减少重复编码,降低维护成本。动态生成字节码操作字节码的工具有,其中有两个比较流行的,一个是,一个是。 作者简介 传恒,一个喜欢摄影和旅游的软件工程师,先后从事饿了么物流蜂鸟自配送和蜂鸟众包的开发,现在转战 Java,目前负责物流策略组分流相关业务的开发。 什么是动态编程 动态编程是相对于静态编程而言的,平时我们讨论比较多的静态编程语言例如Java, 与动态...

    赵连江 评论0 收藏0
  • 深入字节码 -- 计算方法执行

    摘要:什么是字节码程序通过编译之后生成文件就是字节码集合正是有这样一种中间码字节码,使得等函数语言只用实现一个编译器即可运行在上。 什么是字节码? java程序通过javac编译之后生成文件.class就是字节码集合,正是有这样一种中间码(字节码),使得scala/groovy/clojure等函数语言只用实现一个编译器即可运行在JVM上。看看一段简单代码。 public long ...

    娣辩孩 评论0 收藏0
  • Java 运行获取方法参数

    摘要:原文如果觉得我的文章对你有用,请随意赞赏本文整理运行时获取方法参数名的两种方法,的最新的方法和之前的方法。文件中的调试信息上文介绍了通过新增的反射运行时获取方法参数名。 原文:http://nullwy.me/2017/04/java...如果觉得我的文章对你有用,请随意赞赏 本文整理 Java 运行时获取方法参数名的两种方法,Java 8 的最新的方法和 Java 8 之前的方法。 ...

    cfanr 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<