<clinit> : 정적 초기화 생성자 블록, <init> : 생성자 호출 블록
변수를 만드는 것과, 생성자를 불러서 초기 값을 지정하는 것은 다른 프로세스이다.
쉽게 말해서 Obj obj; 와 Obj obj = new Obj(); 은 JVM 동작 자체가 다르다.
asm으로 변수를 생성하는 것과는 별개로, clinit과 init 호출을 통해 이것의 생성자를 mv으로 호출하고, 이 생성자들을 통해 값을 넣어줄 수 있다
clinit과 init은 Java 바이트코드에서 특별한 의미를 가지는 메서드 이름이다.
이들은 클래스 단위로 작동하며, 특정 객체 인스턴스에 대한 것이 아니다. 이 메서드를 통해 정의된 코드는 클래스가 JVM에 의해 로딩되는 시점에 한 번만 실행된다.
<clinit> : static에서의 생성자 호출
<clinit>는 Java 클래스의 정적 초기화 블록(static initializer block)에 해당하는 특별한 메서드이다.
Java에서 정적 초기화 블록은 클래스가 로딩되어 JVM(Java Virtual Machine)에 의해 처음으로 참조될 때 정확히 한 번 실행된다.
이 블록은 주로 정적 변수의 복잡한 초기화에 사용되며, 모든 정적 변수 할당과 정적 초기화 블록 내의 명령어들은 <clinit> 메서드로 컴파일된다.
public void example() {
//2. 정적 변수(static)에 값 넣기
//먼저 Java 클래스의 정적 초기화 블록을 호출한다.
MethodVisitor mv = cv.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
mv.visitLdcInsn(123); //stack에 상수 123 입력
//이 static 변수에 123을 넣자.
mv.visitFieldInsn(PUTSTATIC, "com/dummy/jdbcserver/example_asm/chap3/BasicExample" , "staticVal", "I");
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
<init> : 일반 인스턴스 필드의 생성자 호출
반대로 <init>은 인스턴스 생성자를 나타낸다.
이는 객체가 생성될 때마다 호출되며, 인스턴스 필드의 초기화나 생성자에서 정의된 다른 초기화 코드를 실행한다. 각각의 객체 인스턴스에 대해 <init> 메서드가 호출되며, 클래스 내에 여러 생성자가 정의될 수 있다. 각 생성자는 서로 다른 시그니처(매개변수 목록)를 가질 수 있다.
다른 생성자 생성 예시
public void example() {
//1. 일반 필드 추가 및 값 넣기 (변수가 있다면 visitField 수행, 아니라면 새로 생성)
FieldVisitor fv = cv.visitField(ACC_PUBLIC, "makeNewField", "I", null, null);
if (fv != null) {
//1-1. 커스텀 생성자 만들기
//1-2. 기본 생성자 예시
// MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); //생성자 만들기
//하지만 위처럼 실행하면 ClassFormatErr 발생, 이미 기본 생성자가 java에서는 알아서 생성되기 때문(똑같은 생성자 만들면 중복 에러)
//1-3. 커스텀 생성자
//그렇기에 매개변수를 다르게 하여, 다른 생성자를 추가하겠음. : 이제 Int형 변수를 받는다.
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "(I)V", null, null); //생성자 만들기
mv.visitCode(); //방문 시작
//1-4. Object(상위)의 기본 생성자 호출 : java에서는 하위 클래스 생성자 호출 시 상위도 같이 호출하기 때문.
// 이는 우리가 암묵적으로 사용하던 부모 클래스 생성자 호출 super()와 동일하다고 이해하면 된다.
mv.visitVarInsn(ALOAD, 0); // this 참조
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
//1-5. 커스텀 생성자를 통한 매개변수 값 할당
mv.visitVarInsn(ALOAD, 0); //this(자기자신 - 커스텀 생성자) stack에 넣기
mv.visitLdcInsn(1000); //stack에 값 넣기
mv.visitFieldInsn(PUTFIELD, "com/dummy/jdbcserver/example_asm/chap3/BasicExample", "makeNewField", "I");
mv.visitInsn(RETURN);
mv.visitMaxs(0,0);
mv.visitEnd();
fv.visitEnd();
}
결과 :
public class BasicExample {
public int val;
public static int staticVal = 123;
public int vFive;
public int vSix;
public int makeNewField;
public BasicExample() {
this.vFive = 5;
this.vSix = 6;
}
public void main() throws Exception {
System.out.println("기초 연습용");
}
//이 커스텀 생성자가 추가되었음
public BasicExample(int var1) {
this.makeNewField = 1000;
}
Class A 안에 a, b, c의 인스턴스가 있다면?
public class A {
int a, b, c; // 인스턴스 필드
public A() { // 생성자
a = 1;
b = 2;
c = 3;
}
}
1. 클래스 A 안에 a, b, c 인스턴스 필드가 있다고 할 때, <clinit>를 통해 정의된 값은 이 필드들을 직접 초기화하지 않는다.
2. 대신, 클래스 레벨의 정적 필드 초기화에 사용된다. 예를 들어, A 클래스의 정적 필드를 초기화하는 데 사용된다.
3. 인스턴스 필드 a, b, c를 초기화하려면, <init> 메서드(즉, 생성자) 내에서 초기화 코드를 작성해야 한다. 각 객체 인스턴스에 대해 생성자 <init>가 호출되며, 이 때 객체의 인스턴스 필드를 초기화할 수 있다.
ASM에서는 다음과 같이 나타낼 수 있다.
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
// 객체 자신에 대한 참조와 필드 초기화 코드
mv.visitVarInsn(ALOAD, 0); // 객체 자신(this)을 로드
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); // 슈퍼클래스의 생성자 호출
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn(1);
mv.visitFieldInsn(PUTFIELD, "A", "a", "I");
// b, c 필드에 대한 초기화 코드 생략
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1); // COMPUTE_MAXS 옵션 사용 시 실제 값은 자동 계산됨
mv.visitEnd();
'기술스택 > ASM - [Bytecode]' 카테고리의 다른 글
[A Java bytecode engineering library] - [Core API] 3. Method[3/3] (0) | 2024.04.03 |
---|---|
[ASM]]MethodVisitor를 통해 특정 메서드 변조하기 (1) | 2024.04.03 |
[ASM Docs]MethodVisitor 하위 메서드들 Docs를 통해 알아보기 (0) | 2024.04.03 |
[ASM 바이트코드 작성] if문과 예외를 포함한 메서드 추가 예제(중요) (0) | 2024.04.03 |
[A Java bytecode engineering library] - [Core API] 3. Method[2/3] (1) | 2024.04.03 |