자바
String getParamName(String param) throws Exception {
StackTraceElement[] strace = new Exception().getStackTrace();
String methodName = strace[0].getMethodName();
int lineNum = strace[1].getLineNumber();
String className = strace[1].getClassName().replaceAll(".{5}$", "");
String classPath = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath() + className + ".class";
StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));
List<String> javapLines = Arrays.asList(javapOut.toString().split("\\r?\\n"));
int byteCodeStart = -1;
Map<Integer, Integer> byteCodePointerToJavaPLine = new HashMap<Integer, Integer>();
Pattern byteCodeIndexPattern = Pattern.compile("^\\s*(\\d+): ");
for (int n = 0;n < javapLines.size();n++) {
String javapLine = javapLines.get(n);
if (byteCodeStart > -1 && (javapLine == null || "".equals(javapLine))) {
break;
}
Matcher byteCodeIndexMatcher = byteCodeIndexPattern.matcher(javapLine);
if (byteCodeIndexMatcher.find()) {
byteCodePointerToJavaPLine.put(Integer.parseInt(byteCodeIndexMatcher.group(1)), n);
} else if (javapLine.contains("line " + lineNum + ":")) {
byteCodeStart = Integer.parseInt(javapLine.substring(javapLine.indexOf(": ") + 2));
}
}
int varLoadIndex = -1;
int varTableIndex = -1;
for (int i = byteCodePointerToJavaPLine.get(byteCodeStart) + 1;i < javapLines.size();i++) {
if (varLoadIndex < 0 && javapLines.get(i).contains("Method " + methodName + ":")) {
varLoadIndex = i;
continue;
}
if (varLoadIndex > -1 && javapLines.get(i).contains("LocalVariableTable:")) {
varTableIndex = i;
break;
}
}
String loadLine = javapLines.get(varLoadIndex - 1).trim();
int varNumber;
try {
varNumber = Integer.parseInt(loadLine.substring(loadLine.indexOf("aload") + 6).trim());
} catch (NumberFormatException e) {
return null;
}
int j = varTableIndex + 2;
while(!"".equals(javapLines.get(j))) {
Matcher varName = Pattern.compile("\\s*" + varNumber + "\\s*([a-zA-Z_][a-zA-Z0-9_]*)").matcher(javapLines.get(j));
if (varName.find()) {
return varName.group(1);
}
j++;
}
return null;
}
이것은 현재 몇 가지 문제와 함께 작동합니다.
- IDE를 사용하여 컴파일하면 임시 클래스 파일이 저장된 위치에 따라 관리자로 실행하지 않으면 작동하지 않을 수 있습니다.
- 다음을 사용하여 컴파일해야합니다
javac
와 -g
플래그. 컴파일 된 클래스 파일에 로컬 변수 이름을 포함한 모든 디버깅 정보가 생성됩니다.
- 이것은
com.sun.tools.javap
클래스 파일의 바이트 코드를 구문 분석하고 사람이 읽을 수있는 결과를 생성 하는 내부 Java API 를 사용합니다 . 이 API는 JDK 라이브러리에서만 액세스 할 수 있으므로 JDK java 런타임을 사용하거나 클래스 경로에 tools.jar을 추가해야합니다.
프로그램에서 메소드가 여러 번 호출 되더라도 이제 작동합니다. 불행히도 한 줄에 여러 번 호출하면 아직 작동하지 않습니다. (그렇다면 아래 참조)
온라인으로 사용해보십시오!
설명
StackTraceElement[] strace = new Exception().getStackTrace();
String methodName = strace[0].getMethodName();
int lineNum = strace[1].getLineNumber();
String className = strace[1].getClassName().replaceAll(".{5}$", "");
String classPath = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath() + className + ".class";
이 첫 번째 부분은 우리가 속한 클래스와 함수의 이름에 대한 일반적인 정보를 얻습니다. 이는 예외를 작성하고 스택 추적의 처음 2 개 항목을 구문 분석하여 수행됩니다.
java.lang.Exception
at E.getParamName(E.java:28)
at E.main(E.java:17)
첫 번째 항목은 methodName을 가져올 수있는 예외가 발생하는 행이며 두 번째 항목은 함수가 호출 된 위치입니다.
StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));
이 줄에서 JDK와 함께 제공되는 javap 실행 파일을 실행합니다. 이 프로그램은 클래스 파일 (바이트 코드)을 구문 분석하고 사람이 읽을 수있는 결과를 제공합니다. 우리는 이것을 기초적인 "구문 분석"에 사용할 것입니다.
List<String> javapLines = Arrays.asList(javapOut.toString().split("\\r?\\n"));
int byteCodeStart = -1;
Map<Integer, Integer> byteCodePointerToJavaPLine = new HashMap<Integer, Integer>();
Pattern byteCodeIndexPattern = Pattern.compile("^\\s*(\\d+): ");
for (int n = 0;n < javapLines.size();n++) {
String javapLine = javapLines.get(n);
if (byteCodeStart > -1 && (javapLine == null || "".equals(javapLine))) {
break;
}
Matcher byteCodeIndexMatcher = byteCodeIndexPattern.matcher(javapLine);
if (byteCodeIndexMatcher.find()) {
byteCodePointerToJavaPLine.put(Integer.parseInt(byteCodeIndexMatcher.group(1)), n);
} else if (javapLine.contains("line " + lineNum + ":")) {
byteCodeStart = Integer.parseInt(javapLine.substring(javapLine.indexOf(": ") + 2));
}
}
우리는 여기서 몇 가지 다른 일을하고 있습니다. 먼저 javap 출력을 한 줄씩 목록으로 읽습니다. 둘째, javap 라인 인덱스에 대한 바이트 코드 라인 인덱스 맵을 작성합니다. 이는 나중에 분석 할 메소드 호출을 판별하는 데 도움이됩니다. 마지막으로 스택 추적에서 알려진 라인 번호를 사용하여보고자하는 바이트 코드 라인 인덱스를 결정합니다.
int varLoadIndex = -1;
int varTableIndex = -1;
for (int i = byteCodePointerToJavaPLine.get(byteCodeStart) + 1;i < javapLines.size();i++) {
if (varLoadIndex < 0 && javapLines.get(i).contains("Method " + methodName + ":")) {
varLoadIndex = i;
continue;
}
if (varLoadIndex > -1 && javapLines.get(i).contains("LocalVariableTable:")) {
varTableIndex = i;
break;
}
}
여기서는 메소드가 호출되는 위치와 로컬 변수 테이블이 시작되는 지점을 찾기 위해 javap 행을 한 번 더 반복합니다. 메소드를 호출하기 전에 행에 변수를로드하기위한 호출이 포함되고로드 할 변수 (인덱스 별)를 식별하기 때문에 메소드가 호출되는 행이 필요합니다. 로컬 변수 테이블은 실제로 파악한 인덱스를 기반으로 변수 이름을 찾는 데 도움이됩니다.
String loadLine = javapLines.get(varLoadIndex - 1).trim();
int varNumber;
try {
varNumber = Integer.parseInt(loadLine.substring(loadLine.indexOf("aload") + 6).trim());
} catch (NumberFormatException e) {
return null;
}
이 부분은 실제로 변수 인덱스를 얻기 위해로드 호출을 구문 분석합니다. 함수가 실제로 변수로 호출되지 않으면 예외가 발생하여 여기서 null을 반환 할 수 있습니다.
int j = varTableIndex + 2;
while(!"".equals(javapLines.get(j))) {
Matcher varName = Pattern.compile("\\s*" + varNumber + "\\s*([a-zA-Z_][a-zA-Z0-9_]*)").matcher(javapLines.get(j));
if (varName.find()) {
return varName.group(1);
}
j++;
}
return null;
마지막으로 로컬 변수 테이블의 행에서 변수 이름을 구문 분석합니다. 왜 이런 일이 발생했는지 알지 못했지만 null을 찾지 못하면 null을 반환하십시오.
함께 모아서
public static void main(java.lang.String[]);
Code:
...
18: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_1
22: aload_2
23: invokevirtual #25 // Method getParamName:(Ljava/lang/String;)Ljava/lang/String;
...
LineNumberTable:
...
line 17: 18
line 18: 29
line 19: 40
...
LocalVariableTable:
Start Length Slot Name Signature
0 83 0 args [Ljava/lang/String;
8 75 1 e LE;
11 72 2 str Ljava/lang/String;
14 69 3 str2 Ljava/lang/String;
18 65 4 str4 Ljava/lang/String;
77 5 5 e1 Ljava/lang/Exception;
이것이 기본적으로 우리가보고있는 것입니다. 예제 코드에서 첫 번째 호출은 17 행입니다. LineNumberTable의 17 행은 해당 행의 시작이 바이트 코드 행 인덱스 18임을 나타냅니다. 이것이 System.out
로드입니다. 그런 다음 aload_2
메소드 호출 직전에 LocalVariableTable의 슬롯 2에서 변수를 찾습니다 str
.
재미있게, 다음은 같은 라인에서 여러 함수 호출을 처리하는 것입니다. 이로 인해 함수가 not 등 적이지는 않지만 그 점이 중요합니다. 온라인으로 사용해보십시오!