https://asm.ow2.io/javadoc/org/objectweb/asm/MethodVisitor.html#visitParameter(java.lang.String,int)
전체 메서드들을 기재하였으나, 주관적으로 분류하였음.
1. 메서드 및 필드 접근
1-1. visitFieldInsn: 클래스 필드 조작
void visitFieldInsn(int opcode, String owner, String name, String descriptor)
클래스나 인터페이스의 필드를 로드하거나 저장하는데 사용된다. = 필드를 읽거나 쓸 때 필요한 바이트코드 지시어를 삽입하는 데 사용
지역 변수와 달리, 클래스 내에 존재하는 필드 값에 가깝다.
1. opcode :
GETSTATIC 명령은 정적 필드의 값을, GETFIELD 명령은 객체의 필드 값을 스택에 푸시한다.
*(정적 필드 : 클래스 레벨에서 선언되어, 해당 클래스의 모든 인스턴스에 대해 공유되는 필드, 즉 static을 의미)
PUT의 경우는 반대로 스택에서 필드로 값을 가져온다.
2. owner : 필드가 속한 class나 interface의 이름
3. name : 필드의 이름
4. descripter : 필드의 타입을 설명자 형태로 게시
1-2. visitMethodInsn: 클래스의 메서드 호출
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
클래스의 메서드를 호출할 때 사용된다.
이 메서드는 메서드 호출에 필요한 바이트코드 지시어를 삽입하는 데 사용한다.
- visitMethodInsn에서 메서드 호출 시 : 필요한 파라미터들이 스택에서 pop된다.
- 메서드의 결과(리턴 값이 있을 경우) : return 값을 스택에 푸시하는 효과가 있다.
- opcode(int)
- INVOKEVIRTUAL : 인스턴스 메서드를 호출할 때 사용. "objectInstance.method()"와 같이 인스턴스 메서드를 호출
- INVOKESPECIAL : 특수 메서드 호출로, 주로 '생성자' 호출에 사용. 슈퍼 클래스의 생성자를 호출하는 경우나, private 메서드를 호출하는 경우에도 사용 가능
- INVOKESTATIC : 클래스 수준의 정적(static) 메서드를 호출할 때 사용 : "ClassName.staticMethod()"
- INVOKEINTERFACE : 인터페이스 메서드 호출 시 사용 : "interfaceInstance.interfaceMethod()" - owner(String): 호출되는 메서드가 속한 클래스 또는 인터페이스의 내부 이름(internal name), ex : "java/lang/String"
- name(String): 호출되는 메서드의 이름
- desc(String): 호출되는 메서드의 설명자(descriptor)
- itf(boolean): 호출되는 메서드가 인터페이스의 메서드인지 여부
1-3. visitInvokeDynamicInsn : invokedynamic(동적 호출) 관련 생성
public void visitInvokeDynamicInsn(String name,
String descriptor,
Handle bootstrapMethodHandle,
Object... bootstrapMethodArguments)
Parameters:name - the method's name.descriptor - the method's descriptor (see Type).bootstrapMethodHandle - the bootstrap method.bootstrapMethodArguments - the bootstrap method constant arguments. Each argument must be an Integer, Float, Long, Double, String, Type, Handle or ConstantDynamic value. This method is allowed to modify the content of the array so a caller should expect that this array may change.
컴파일 시간이 아니라 런타임 시간에 메서드가 결정되는 "동적으로 호출되는 메서드"를 생성하는 데 사용된다.
이는 정적으로 코드에 메서드 이름을 명시하지 않고, 런타임에 결정되는 방식을 의미한다.
- name(String): 생성할 메서드의 이름
- descriptor(String): 생성할 메서드의 설명자(descriptor)
- bootstrapMethodHandle(Handle): 바이트코드 생성 시 호출되는 부트스트랩(bootstrap) 메서드를 나타내는 Handle 객체. 부트스트랩 메서드는 동적으로 생성된 메서드의 호출에 대한 동적 매핑을 처리한다.
- bootstrapMethodArguments(Object...): 부트스트랩 메서드에 전달되는 인수들의 배열. 이러한 인수들은 부트스트랩 메서드가 호출될 때 전달되며, 동적 매핑에 사용된다.
2. 변수 및 스택 조작
2-1. visitVarInsn: 지역 변수에서 값을 꺼내거나 저장
public void visitVarInsn(int opcode, int varIndex)
- opcode(int): 지역 변수 관련 바이트코드 지시문의 종류를 나타내는 opcode.
- LOAD는 지역 변수의 값을 스택에 푸시하고, STORE명령은 스택에서 값을 POP하여 지역 변수에 저장.
- 앞의 변수는 타입을 나타낸다. ex : ILOAD - Int 불러오기(Operands stack) - var(int): 지역 변수의 인덱스(index).
2-2. visitIincInsn: 지역 변수에 대한 증감 연산
public void visitIincInsn(int varIndex, int increment)
직접적으로 스택을 사용하지 않지만, 지역 변수의 값을 변경하는데 사용된다.
- var(int): 증가 또는 감소할 지역 변수의 인덱스(index).
- increment(int): 지역 변수를 증가 또는 감소시킬 값
2-3. visitIntInsn: 특정 int 값을 스택에 푸시하는 명령어(BIPUSH, SIPUSH).
public void visitIntInsn(int opcode, int operand)
정수형 상수를 스택에 로드하거나, 바이트코드에서 특정 연산을 수행할 때 사용된다.
- opcode(int):
- BIPUSH : 바이트형 상수(byte-sized constant)를 스택에 로드
- SIPUSH : 짧은 정수(short-sized constant)를 스택에 로드
- NEWARRAY : 새로운 배열을 생성 - operand(int): opcode에 따라 다르지만, 보통 로드할 정수형 상수의 값을 나타낸다.
mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_INT); // 정수 배열 생성
2-4. visitInsn: 상수를 Operands 스택에 푸시(ICONST_0, LCONST_1 등)
public void visitInsn(int opcode)
Parameters:opcode - the opcode of the instruction to be visited. This opcode is either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT.
1. opcode(int): 바이트코드 지시문의 종류를 나타내는 opcode
visitVarInsn, visitIntInsn 등과 함께 사용되어 바이트코드에서 다양한 연산을 수행한다.
( visitVarInsn은 지역 변수를 조작하는 지시문을 다루고, visitInsn은 기본적인 연산을 수행하는 지시문에 가깝다.)
ex :
// 지역 변수 인덱스 1번 값을 스택에 로드
mv.visitVarInsn(Opcodes.ILOAD, 1);
// 상수 값(5)을 스택에 로드.
mv.visitIntInsn(Opcodes.BIPUSH, 5);
// 스택에 로드된 상위 두 정수를 더한 뒤 저장
mv.visitInsn(Opcodes.IADD);
2-5. visitLdcInsn: 상수(constant)를 스택에 로드
public void visitLdcInsn(Object value)
String, int, float, long, double과 같은 상수 값을 스택에 푸시한다.
클래스 리터럴이나 메소드 타입, 메소드 핸들도 푸시할 수 있다.
mv.visitLdcInsn(123); // 정수형 상수 123을 스택에 로드
mv.visitLdcInsn("Hello, world!"); // 문자열 상수 "Hello, world!"를 스택에 로드
3. 타입 및 인스턴스 생성/조작
3-1. visitMultiANewArrayInsn : 다차원 배열을 생성
public void visitMultiANewArrayInsn(String descriptor, int numDimensions)
다차원 배열의 인스턴스를 생성하고, 이를 피연산자 스택에 푸시하는 작업을 수행한다.
- desc(String): 생성할 배열의 유형을 설명하는 문자열(descriptor)
- dims(int): 배열의 차원 수
visitMultiANewArrayInsn("[[I", 2); // 2차원 int 배열을 생성하여 스택에 저장
3-2. visitTypeInsn : 타입 관련 지시사항(예: NEW, INSTANCEOF)
public void visitTypeInsn(int opcode, String type)
타입(type)을 조작하는 바이트코드 지시문. 바이트코드에서 클래스나 배열을 생성하거나 조작할 때 사용한다.
- opcode(int):
- NEW : 새로운 클래스 인스턴스 생성
- ANEWARRAY : 새로운 배열을 생성
- CHECKCAST : 객체 유형 확인 후, 해당 유형으로 캐스팅 (불일치시 ClassCastException 발생)
- INSTANCEOF : 객체의 유형을 검사(결과는 boolean으로 return) - type(String): 지시문이 조작하는 타입을 나타내는 문자열(descriptor), 클래스의 경로를 나타냄.
mv.visitTypeInsn(Opcodes.NEW, "java/util/ArrayList"); // ArrayList 클래스의 새 인스턴스를 생성
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String"); // String 배열을 생성
// 클래스 A와 B를 정의.
class A {}
class B extends A {}
// CHECKCAST
mv.visitTypeInsn(Opcodes.CHECKCAST, "classname/B"); // classname은 클래스 B의 패키지 경로.
// -> 만약 객체가 B 클래스의 인스턴스가 아니라면 ClassCastException이 발생
// INSTANCEOF
mv.visitTypeInsn(Opcodes.INSTANCEOF, "classname/B");
// -> 스택에 있는 객체가 B 클래스의 인스턴스인지 T/F의 값으로 스택에 푸시
3-3. visitLocalVariable : 지역 변수를 생성
public void visitLocalVariable(String name, String descriptor,
String signature, Label start, Label end, int index)
1. name : 지역 변수명
2. descriptor : 유형을 설명하는 문자열(descriptor)
3. start : 변수의 시작 위치
4. end : 변수의 끝 위치
=> 해당 변수가 유효한 범위. 메서드 내의 지역변수라고 한다면, 이 메서드 안에서만 변수가 유효하다. 이 범위값을 잡아주어야 한다. 주로 label을 사용해서 잡는다.
5. index : 변수의 인덱스(해당 메서드 내에서 사용되는 지역 변수의 순서 )
public void visitLocalVariableAnnotation() {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC + ACC_STATIC, "visitLocalVariableAnnotation", "()V", null, null);
mv.visitCode();
// 로컬 변수의 시작과 끝을 나타내는 레이블 생성
Label start = new Label();
Label end = new Label();
mv.visitLabel(start);
mv.visitLocalVariable("myLocalVal", "F", null, start, end, 0); //변수 생성
mv.visitInsn(FCONST_2); //스택에 상수 넣기
mv.visitVarInsn(FSTORE, 0); //스택 값을 지역 변수에 적용
mv.visitLabel(end);
mv.visitMaxs(0, 0);
mv.visitInsn(RETURN);
}
4. 어노테이션 및 메타데이터
4-1. visitTypeAnnotation : 타입 어노테이션을 방문
public AnnotationVisitor visitTypeAnnotation(int typeRef,
TypePath typePath,
String descriptor,
boolean visible)
타입 어노테이션은 Java 8부터 도입된, 타입 사용 위치에 적용할 수 있는 어노테이션이다.
이 메서드는 바이트코드에서 클래스, 인터페이스, 제네릭 유형 등의 타입에 적용된 어노테이션을 방문할 때 사용한다.
visitTypeAnnotation은 TypeAnnotationVisitor 인스턴스를 반환할 수 있으며, 이 인스턴스를 사용하여 어노테이션 값에 대해 더 상세하게 방문할 수 있다.
- typeRef: 어노테이션이 적용된 타입에 대한 참조
- typePath: 어노테이션이 적용된 타입 내의 경로
- desc: 어노테이션의 설명자(descriptor)
- visible: 어노테이션의 가시성 여부 ( true인 경우 런타임에 리플렉션을 통해 어노테이션 정보에 접근 가능)
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.TypePath;
MethodVisitor mv = ...; // MethodVisitor 객체 생성
// 타입 어노테이션을 방문
mv.visitTypeAnnotation(
typeRef, // 어노테이션이 적용된 타입에 대한 참조(reference)
typePath, // 어노테이션이 적용된 타입 내의 경로(path)
desc, // 어노테이션의 설명자(descriptor)
visible // 어노테이션의 가시성 여부
);
// 어노테이션을 처리하기 위한 AnnotationVisitor 생성
AnnotationVisitor av = mv.visitTypeAnnotation(
typeRef, // 어노테이션이 적용된 타입에 대한 참조(reference)
typePath, // 어노테이션이 적용된 타입 내의 경로(path)
desc, // 어노테이션의 설명자(descriptor)
visible // 어노테이션의 가시성 여부
);
av.visit("value", "newValue"); // 어노테이션의 값을 "newValue"로 변경
av.visitEnd(); // AnnotationVisitor 종료
4-2. visitAnnotation : 메서드나 클래스에 적용된 어노테이션을 방문
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible)
1. descriptor : 어노테이션의 설명자(descriptor)
2. visible : 어노테이션의 가시성 여부 ( true인 경우 런타임에 리플렉션을 통해 어노테이션 정보에 접근 가능)
메서드, 클래스에 적용된 어노테이션을 방문할 때 호출된다.
public void addAnnotation() {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "haveAnnotation", "(ILjava/lang/String;Ljava/lang/String;)V", null, null);
mv.visitCode();
AnnotationVisitor av = mv.visitAnnotation("Ljava/lang/Deprecated;", true); //mv 위에 어노테이션 등록
av.visitEnd();
mv.visitMaxs(0, 0);
mv.visitInsn(RETURN);
}
4-3. visitParameterAnnotation : 특정 파라미터에 적용된 어노테이션을 방문
public AnnotationVisitor visitParameterAnnotation(int parameter,
String descriptor,
boolean visible)
이 메서드는 바이트코드에서 특정 지시문에 적용된 어노테이션을 방문할 때 호출된다.
1. parameter : 어노테이션이 적용될 메서드 파라미터의 인덱스. 파라미터는 0부터 시작하는 인덱스를 사용
2. descriptor : 어노테이션의 설명자(descriptor)
3. visible : 어노테이션의 가시성 여부 ( true인 경우 런타임에 리플렉션을 통해 어노테이션 정보에 접근 가능)
public void visitParameterAnnotation() {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "visitParameterAnnotation", "(ILjava/lang/String;Ljava/lang/String;)V", null, null);
mv.visitCode();
// 첫 번째 파라미터(인덱스 0)에 @NotNull 어노테이션 추가, 예를 들어
AnnotationVisitor av1 = mv.visitParameterAnnotation(0, "Ljavax/validation/constraints/Size;", true);
av1.visit("min", 1); // @Size 크기 지정
av1.visit("max", 10);
AnnotationVisitor av2 = mv.visitParameterAnnotation(1, "Ljavax/validation/constraints/NotNull;", true);
av1.visitEnd();
av2.visitEnd();
mv.visitMaxs(0, 0);
mv.visitInsn(RETURN);
}
4-4. visitLocalVariableAnnotation : 지역 변수에 적용된 어노테이션을 방문
public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
TypePath typePath,
Label[] start,
Label[] end,
int[] index,
String descriptor,
boolean visible)
로컬 변수 타입에 어노테이션을 적용할 때 사용된다. 메서드 내에서 선언된 로컬 변수의 특정 범위에 어노테이션을 추가하는 기능을 제공한다.
- typeRef: 어노테이션이 적용될 타입의 참조.
TypeReference.LOCAL_VARIABLE 또는 TypeReference.RESOURCE_VARIABLE 중 하나여야 한다. - typePath: 어노테이션이 적용될 타입 내의 구체적인 위치. 복잡한 타입 구조 내의 특정 부분에 어노테이션을 적용할 필요가 있을 때 사용. 전체 타입에 어노테이션이 적용될 경우 null로 사용함.
- start, end: 로컬 변수가 유효한 범위의 시작과 끝을 나타내는 레이블 배열. start는 범위의 시작을, end는 범위의 끝을 나타낸다.
- index: 각 범위에서 로컬 변수의 인덱스를 나타내는 배열. start/end/index의 각 배열의 크기는 일반적으로 같다.
- descriptor: 어노테이션 클래스의 디스크립터.
- visible: 어노테이션이 런타임에 보일지 여부. true인 경우, 런타임에 리플렉션을 통해 어노테이션 정보에 접근 가능
public void visitLocalVariableAnnotation() {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC + ACC_STATIC, "visitLocalVariableAnnotation", "()V", null, null);
mv.visitCode();
// 로컬 변수의 시작과 끝을 나타내는 레이블 생성
Label start = new Label();
Label end = new Label();
//1. 지역 변수 생성
mv.visitLabel(start); //여기서부터 지역 변수 접근 가능
mv.visitLocalVariable("myLocalVal", "F", null, start, end, 1); //변수 생성
mv.visitInsn(FCONST_2); //스택에 상수 넣기
mv.visitVarInsn(FSTORE, 0); //스택 값을 지역 변수에 적용
//2. 지역변수에 어노테이션 달기
AnnotationVisitor av = mv.visitLocalVariableAnnotation(TypeReference.LOCAL_VARIABLE, null, new Label[] {start}, new Label[] {end}, new int[] {1}, "Ljava/lang/Deprecated;", true);
av.visitEnd();
mv.visitLabel(end); //이제 지역 변수에 접근할 수 없음
mv.visitMaxs(0, 0);
mv.visitInsn(RETURN);
}
4-5. visitAnnotationDefault : 어노테이션 인터페이스 메서드의 기본 값을 방문
public AnnotationVisitor visitAnnotationDefault()
메서드나 생성자의 기본값을 나타내는 어노테이션을 방문할 때 호출한다.
어노테이션의 기본값은 해당 어노테이션을 사용할 때 명시적으로 값을 지정하지 않은 경우에 사용된다.
@interface MyAnnotation {
String value() default "default value";
}
만일 이렇게 어노테이션을 정의했다 가정하자.
@MyAnnotation의 요소 'value'에는 "default value"가 사용된다.
visitAnnotationDefault 메서드를 사용하면 어노테이션의 기본값을 방문하고 처리할 수 있다.
4-6. visitTryCatchAnnotation : try-catch 블록에 적용된 어노테이션을 방문
public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath,
String descriptor, boolean visible)
try-catch 블록에 적용된 어노테이션을 방문할 때 호출된다.
- typeRef: 어노테이션이 적용된 try-catch 블록에 대한 참조(reference). 예외 핸들러(try-catch 블록)에 어노테이션을 적용하는 데 사용
- typePath: 어노테이션이 적용된 try-catch 블록 내의 경로(path)를 나타낸다.
- descriptor: 어노테이션의 설명자(descriptor)를 나타낸다.
- visible: 어노테이션의 가시성 여부를 나타낸다.
return : AnnotationVisitor 객체를 반환한다. 이것을 사용하여 어노테이션 처리를 진행한다.
4-7. visitInsnAnnotation : 특정 바이트코드 명령어에 어노테이션을 추가
public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath,
String descriptor, boolean visible)
타입 어노테이션을 특정 바이트코드 명령어에 적용할 때 사용한다.
visitInsnAnnotation을 사용하면, 지역 변수의 할당, 타입 캐스트, instanceof 검사, 메서드 호출 등 특정 바이트코드 명령어에 대한 추가적인 메타데이터 정보를 제공할 수 있다.
예를 들어, INSTANCEOF, NEW, CAST 등의 명령어에 어노테이션을 추가할 수 있다.
- typeRef: 어노테이션이 적용될 바이트코드 명령어의 유형을 나타내는 참조. 이 값은 JVM 명세에서 정의된 타입 참조 종류 중 하나를 사용한다.
- typePath: 타입 내에서 어노테이션이 적용될 구체적인 위치. 예를 들어, 제네릭 타입의 특정 매개변수에 어노테이션을 적용할 때 사용.
- descriptor: 어노테이션의 설명자(descriptor)
- visible: 어노테이션의 가시성 여부 ( true인 경우 런타임에 리플렉션을 통해 어노테이션 정보에 접근 가능)
Returns:a visitor to visit the annotation values, or null if this visitor is not interested in visiting this annotatio
MethodVisitor mv = ...;
// 타입 캐스트 명령어 바로 전에 어노테이션을 적용
AnnotationVisitor av = mv.visitInsnAnnotation(TypeReference.CAST, null, "Lorg/checkerframework/checker/nullness/qual/NonNull;", true);
av.visitEnd();
// 타입 캐스트 명령어 (예: String 객체를 Integer로 캐스트하려고 시도)
mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
//=> 타입 캐스트 명령어에 @NonNull 어노테이션을 적용하여, 해당 객체가 널이 아님을 명시적으로 표시
그 외 (간략한 내용)
5. 제어 흐름
visitLabel : 분기 지점을 나타내는 레이블을 방문
visitJumpInsn: 조건부 점프(label) 명령어.
이 명령어들은 비교 연산을 수행하기 위해 스택의 값을 사용한다.(예: IFEQ, IFLT 등).
visitTableSwitchInsn : 테이블 기반 스위치 지시사항을 방문
visitLookupSwitchInsn : 조회 테이블 기반 스위치 지시사항을 방문
visitTryCatchBlock : 예외 처리 블록을 방문
6. 구조 및 디버깅 정보
visitParameter : 메서드 파라미터에 대한 정보(이름, 모드)를 방문
visitAnnotableParameterCount : 어노테이션이 적용 가능한 메서드 파라미터의 수를 방문
visitAttribute : 메서드에 속성을 방문한다. 속성은 바이트코드의 메타데이터를 담는 컨테이너
visitCode() : 메서드의 코드 시작 부분을 방문
이는 메서드의 바이트코드 명령어들을 방문하기 시작함을 나타낸다.
visitFrame : 스택 프레임 정보를 방문
이는 주로 메서드의 상태 변화를 나타내는 데 사용 (스택 프레임 갱신)
visitLineNumber : 소스 코드의 줄 번호와 바이트코드 지시사항을 연결
visitMaxs : 메서드의 최대 스택 크기와 지역 변수의 최대 수를 설정
visitEnd : 메서드 방문의 끝
미분류
getDelegate :
특정 구현에서 대리자(delegate) 객체를 가져온다.
ASM API의 일부가 아니며, 사용자 정의 래퍼나 확장 기능일 수 있다.
getDelegate는 ASM의 표준 API에 포함되지 않는, 특정 구현 또는 확장에서 사용될 수 있는 메서드이다.
'기술스택 > ASM - [Bytecode]' 카테고리의 다른 글
[ASM]]MethodVisitor를 통해 특정 메서드 변조하기 (1) | 2024.04.03 |
---|---|
[ASM] static과 인스턴스 필드 생성자 호출 - <clinit>, <init> (1) | 2024.04.03 |
[ASM 바이트코드 작성] if문과 예외를 포함한 메서드 추가 예제(중요) (0) | 2024.04.03 |
[A Java bytecode engineering library] - [Core API] 3. Method[2/3] (1) | 2024.04.03 |
[Agent / ASM] agent의 transform 메서드에 ASM 바이트코드 적용하기 1 (0) | 2024.04.03 |