欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java1.8中javassist获取接口函数参数名称

程序员文章站 2022-06-18 11:11:51
...

前提条件

在java8中要获取类函数参数名称必须在编译时增加参数

编译器时加上-parameters参数

具体内容详见 java1.8获取类和接口函数参数名称

尝试使用javassist获取接口函数名称

1 添加依赖

  <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.23.1-GA</version>
    </dependency>

2 在编译时增加-parameters参数

3 编写测试接口和测试方法

package js.oop.parameter;

import javassist.*;
import javassist.bytecode.*;

/**
* @Title: IParameterNameJavassist
* @Description: 演示 Javassist 获取接口的函数参数名称
* @author chy
* @date 2018/8/16 16:44
*/
public interface IParameterNameJavassist {

    public abstract void test1(String myMsg);

    public abstract void test2(String myMsg,int myAge);

    public abstract void test3(String myMsg,int myAge, String mySex);

    /**
     * 获取类方法参数名称
     * @param funName
     */
    public static void getClassParamterName(String funName){
        Class<?> clazz = ParameterNameJavassist.class;
        ClassPool pool = ClassPool.getDefault();
        try {
            CtClass ctClass = pool.get(clazz.getName());
            CtMethod ctMethod = ctClass.getDeclaredMethod(funName);
            // 使用javassist的反射方法的参数名
            MethodInfo methodInfo = ctMethod.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
                    .getAttribute(LocalVariableAttribute.tag);
            if (attr != null) {
                int len = ctMethod.getParameterTypes().length;
                // 非静态的成员函数的第一个参数是this
                int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
                System.out.print(funName+" : ");
                for (int i = 0; i < len; i++) {
                    System.out.print(attr.variableName(i + pos) + ' ');
                }
                System.out.println();
            }

            System.out.println(funName+"获取结束");

        } catch (NotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取接口方法参数名称
     * @param clazz
     * @param funName
     */
    public static void getInterfaceParamterName(Class<?> clazz,String funName){
        ClassPool pool = ClassPool.getDefault();
        try {
            CtClass ctClass = pool.get(clazz.getName());
            CtMethod ctMethod = ctClass.getDeclaredMethod(funName);
            // 使用javassist的反射方法的参数名
            MethodInfo methodInfo = ctMethod.getMethodInfo();

            MethodParametersAttribute methodParameters= (MethodParametersAttribute)methodInfo.getAttribute("MethodParameters");

            //获取参数的个数
            int count =ctMethod.getParameterTypes().length;

            CtClass[] pCtClass= ctMethod.getParameterTypes();

            for(int i=0;i<count;i++) {
                String str = methodParameters.getConstPool().getUtf8Info(ByteArray.readU16bit(methodParameters.get(), i * 4 + 1));
                System.out.println("参数名称:" + str + ",参数类型" + pCtClass[i].getName());
            }

            System.out.println("");
            System.out.println(funName+"获取结束");
            System.out.println("");

        } catch (NotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        try {
            /**
             * 抽象函数和接口函数
             */
            getInterfaceParamterName(IParameterNameJavassist.class,"test1");
            getInterfaceParamterName(IParameterNameJavassist.class,"test2");
            getInterfaceParamterName(IParameterNameJavassist.class,"test3");
        } catch (Exception e) {
            System.out.println("获取抽象类/接口函数参数出错");
        }
    }
}

4 如果使用 getClassParamterName  函数只能获取 类里面的函数参数名称

5 如果使用 getInterfaceParamterName 函数能获取 类以及抽象类以及接口的函数名称

6 实现原理编译时增加-parameters参数后,字节码中增加了  MethodParameters 元信息,只要能读取元信息就行

 public abstract void test1(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_ABSTRACT
    MethodParameters:
      Name                           Flags
      myMsg

  public abstract void test2(java.lang.String, int);
    descriptor: (Ljava/lang/String;I)V
    flags: ACC_PUBLIC, ACC_ABSTRACT
    MethodParameters:
      Name                           Flags
      myMsg
      myAge 

  public abstract void test3(java.lang.String, int, java.lang.String);
    descriptor: (Ljava/lang/String;ILjava/lang/String;)V
    flags: ACC_PUBLIC, ACC_ABSTRACT
    MethodParameters:
      Name                           Flags
      myMsg
      myAge
      mySex

7  javassist 中虽然实现了 MethodParametersAttribute 类,但是没有实现获取参数名称的功能,这点很坑啊!!!!

8 无奈看源码,参考 LineNumberAttribute 实现方式,获取 LineNumberAttribute 属性的值

public class LineNumberAttribute extends AttributeInfo {
    public static final String tag = "LineNumberTable";

    ............

    public int tableLength() {
        return ByteArray.readU16bit(this.info, 0);
    }

    ............

    public int lineNumber(int i) {
        return ByteArray.readU16bit(this.info, i * 4 + 4);
    }

    ............
}

   LineNumber  的存储数据格式如下:

public void fun(java.lang.String, int);
    descriptor: (Ljava/lang/String;I)V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=3
        ................................
        37: return
      LineNumberTable:
        line 16: 0
        line 17: 37
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      38     0  this   Ljs/oop/parameter/ParameterNameJavassist;
            0      38     1 name1   Ljava/lang/String;
            0      38     2  age1   I
    MethodParameters:
      Name                           Flags
      name1
      age1

9 照葫芦画瓢

//取而第一个参数
str= methodParameters.getConstPool().getUtf8Info(ByteArray.readU16bit(methodParameters.get(), 0 * 4 + 1));

System.out.println(str);

//取而第二个参数
str= methodParameters.getConstPool().getUtf8Info(ByteArray.readU16bit(methodParameters.get(), 1 * 4 + 1));

System.out.println(str);

  methodParameters.get()  就是  MethodParameters 的元信息,但是是字节数组,需要转换成字符串

  如果有谁知道为什么是  n*4+1 请告知我? n是参数序号从0开始,我是实验出来的!!!!!

10 运行结果

java1.8中javassist获取接口函数参数名称