본문 바로가기

프로그래밍/BCI

[Java] ASM을 이용한 클래스 생성

이전 글에서는 ClassReader를 사용한 예를 보았다.


이번에는 ClassWriter를 사용한 예제를 만들어 보자.


우선,  ClassWriter의 사용 예제인 만큼 복잡한 클래스는 제쳐두고 필드 몇개와 메소드를 선언하는 인터페이스 클래스를 만들어 본다.


만들어 볼 클래스


public interface Comparable {
int LESS = -1;
int EQUAL = 0;
int GREATER = 1;
int compareTo(Object o);
}


위의 클래스를 직접 바이트 코드로 생성하기 위한 클래스


import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import java.io.FileOutputStream;
public class ComparableWriter {
public byte[] toByteArray() {
ClassWriter cw = new ClassWriter(0);
/*
define class header
arguments
V1_5 : class version, Java 1.5
ACC_XXX : java modifier
"Comparable" : class name in internal form.
null : generics
"java/lang/Object" : super class in internal form.
new String[0] : array of interfaces that are extended,
specified by their internal name
ps.
internal form or name : replace dot(.) with slash(/)
new String[0] : same as null
*/
cw.visit(Opcodes.V1_5, Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE + Opcodes.ACC_PUBLIC,
"Comparable", null, "java/lang/Object", new String[0]);
/*
define field
arguments
ACC_XXX : java modifier
"LESS" : field name
"I" : type in type descriptor form, i.e. I = int
null : generics
new Integer(-1) : constant value only for final static field otherwise must be null
ps.
Since there are no annotations, call visitEnd method of the returned FieldVisitor,
i.e. without any call to its visitAnnotation or visitAttribute methods.
*/
cw.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
"LESS", "I", null, new Integer(-1)).visitEnd();
cw.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
"EQUAL", "I", null, new Integer(1)).visitEnd();
cw.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
"GREATER", "I", null, new Integer(1)).visitEnd();
/*
define compareTo method
arguments
ACC_XXX : java modifier
"compareTo" : method name
"(Ljava/lang/Object;)I" : descriptor of method
null : generics
null : an array of the exceptions that can be thrown by the method, specified by their internal name
ps.
internal name : replace dot(.) with slash(/)
The visitMethod method returns a MethodVisitor,
which can be used to define the method’s annotations and
attributes, and most importantly the method’s code. Here, since there are no
annotations and since the method is abstract, we call the visitEnd method
of the returned MethodVisitor immediately.
*/
cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "compareTo",
"(Ljava/lang/Object;)I", null, null).visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
public static void main(String[] args) {
ComparableWriter cwr = new ComparableWriter();
FileOutputStream stream = null;
try {
stream = new FileOutputStream("Comparable.class");
stream.write(cwr.toByteArray());
} catch (Throwable t) {
t.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
}

위의 두 경우를 비교해 보면 직접 바이트 코드로 생성하는 것이 훨씬 복잡하다. 따라서, 일반적인 코딩을 위한 목적으로는 사용하지 않고, 동적으로 코드를 생성하거나 기존의 클래스를 변환하는 용도로 사용하는 것이 일반적이다.


이를 위해 ClassLoader를 사용하거나 ClassFileTransformer를 이용하는 방법이 있다. 이는 다음 기회에 알아보기로...