5

나는 ASM을 사용을 .class 파일에 정적 최종 필드를 추가하려면, 소스 파일은ASM을 사용하여 초기화 프로그램과 함께 정적 최종 필드를 추가하는 방법은 무엇입니까?

public class Example { 

    public Example(int code) { 
     this.code = code; 
    } 

    public int getCode() { 
     return code; 
    } 

    private final int code; 

} 

이 같아야 디 컴파일되어 생성 된 클래스입니다 :

public class Example { 

    public static final Example FIRST = new Example(1); 

    public static final Example SECOND = new Example(2); 

    public Example(int code) { 
     this.code = code; 
    } 

    public int getCode() { 
     return code; 
    } 

    private final int code; 

} 

그리고 결론으로 , ASM을 사용하여 .class 파일에 FIRST 및 SECOND 상수를 추가하고 싶습니다. 어떻게하면됩니까?

+0

이 자바인가? manen-assembly-plugin과 관련된 질문입니까? 그런 다음 태그를 붙이십시오. –

답변

17

이 답변이는 나에게 가장 친숙한 API이기 때문에합니다 (ASM homepageASM 4.0 자바 바이트 코드 엔지니어링 라이브러리의 2.2 절 참조) ASM의 방문자 API를 사용하여 수행 할 수있는 방법을 보여줍니다. ASM에는이 경우 일반적으로 사용하기 쉬운 오브젝트 모델 api (동일한 문서의 파트 II 참조) 변형이 있습니다. 객체 모델은 메모리에 전체 클래스 파일의 트리를 구성하기 때문에 약간 느릴 수 있지만 성능 변환을 필요로하는 클래스의 양이 적 으면 무시할 수 있어야합니다.

값이 숫자와 같은 상수가 아닌 static final 개의 필드를 만들 때 해당 초기화는 실제로 "static initializer block"으로 이동합니다. 클래스가 할 수있는 파일 만하면서,

public class Example { 

    public static final Example FIRST; 

    public static final Example SECOND; 

    static { 
    FIRST = new Example(1); 
    SECOND = new Example(2); 
    } 

    ... 
} 

자바 파일에서 여러 정적 {...} 블록을 가질 수있다 : 따라서, 두 번째 (변형) 코드 목록은 다음과 같은 자바 코드에 해당 하나가 되라. Java 컴파일러는이 요구 사항을 충족시키기 위해 여러 정적 블록을 자동으로 하나의 블록으로 병합합니다. 바이트 코드를 조작 할 때 이것은 이전에 정적 블록이없는 경우 새로운 블록을 생성한다는 것을 의미합니다. 정적 블록이 이미 있으면 기존 블록의 시작 부분에 코드를 추가해야합니다 (앞에 붙이는 것이 더하기보다 쉽습니다).

ASM에서 정적 블록은 이라는 특수한 이름을 가진 정적 메소드처럼 보이며, 생성자는 특별한 이름이 <init> 인 메소드처럼 보입니다.

visitor API를 사용할 때 이전에 정의 된 메소드인지 여부를 확인하는 방법은 모든 visitMethod() 호출을 수신하고 각 호출에서 메소드 이름을 확인하는 것입니다. 모든 메소드가 방문 된 후에 visitEnd() 메소드가 호출되므로 그때까지 메소드가 방문되지 않았다면 새로운 메소드를 작성해야한다는 것을 알게됩니다.

우리가 바이트 [] 형식으로 상기 시킴으로 빨리 클래스가 가정, 요구 된 변환은 다음과 같이 수행 할 수 있습니다

import org.objectweb.asm.*; 
import static org.objectweb.asm.Opcodes.*; 

public static byte[] transform(byte[] origClassData) throws Exception { 
    ClassReader cr = new ClassReader(origClassData); 
    final ClassWriter cw = new ClassWriter(cr, Opcodes.ASM4); 

    // add the static final fields 
    cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "FIRST", "LExample;", null, null).visitEnd(); 
    cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "SECOND", "LExample;", null, null).visitEnd(); 

    // wrap the ClassWriter with a ClassVisitor that adds the static block to 
    // initialize the above fields 
    ClassVisitor cv = new ClassVisitor(ASM4, cw) { 
    boolean visitedStaticBlock = false; 

    class StaticBlockMethodVisitor extends MethodVisitor { 
     StaticBlockMethodVisitor(MethodVisitor mv) { 
     super(ASM4, mv); 
     } 
     public void visitCode() { 
     super.visitCode(); 

     // here we do what the static block in the java code 
     // above does i.e. initialize the FIRST and SECOND 
     // fields 

     // create first instance 
     super.visitTypeInsn(NEW, "Example"); 
     super.visitInsn(DUP); 
     super.visitInsn(ICONST_1); // pass argument 1 to constructor 
     super.visitMethodInsn(INVOKESPECIAL, "Example", "<init>", "(I)V"); 
     // store it in the field 
     super.visitFieldInsn(PUTSTATIC, "Example", "FIRST", "LExample;"); 

     // create second instance 
     super.visitTypeInsn(NEW, "Example"); 
     super.visitInsn(DUP); 
     super.visitInsn(ICONST_2); // pass argument 2 to constructor 
     super.visitMethodInsn(INVOKESPECIAL, "Example", "<init>", "(I)V"); 
     super.visitFieldInsn(PUTSTATIC, "Example", "SECOND", "LExample;"); 

     // NOTE: remember not to put a RETURN instruction 
     // here, since execution should continue 
     } 

     public void visitMaxs(int maxStack, int maxLocals) { 
     // The values 3 and 0 come from the fact that our instance 
     // creation uses 3 stack slots to construct the instances 
     // above and 0 local variables. 
     final int ourMaxStack = 3; 
     final int ourMaxLocals = 0; 

     // now, instead of just passing original or our own 
     // visitMaxs numbers to super, we instead calculate 
     // the maximum values for both. 
     super.visitMaxs(Math.max(ourMaxStack, maxStack), Math.max(ourMaxLocals, maxLocals)); 
     } 
    } 

    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 
     if (cv == null) { 
     return null; 
     } 
     MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 
     if ("<clinit>".equals(name) && !visitedStaticBlock) { 
     visitedStaticBlock = true; 
     return new StaticBlockMethodVisitor(mv); 
     } else { 
     return mv; 
     } 
    } 

    public void visitEnd() { 
     // All methods visited. If static block was not 
     // encountered, add a new one. 
     if (!visitedStaticBlock) { 
     // Create an empty static block and let our method 
     // visitor modify it the same way it modifies an 
     // existing static block 
     MethodVisitor mv = super.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); 
     mv = new StaticBlockMethodVisitor(mv); 
     mv.visitCode(); 
     mv.visitInsn(RETURN); 
     mv.visitMaxs(0, 0); 
     mv.visitEnd(); 
     } 
     super.visitEnd(); 
    } 
    }; 

    // feed the original class to the wrapped ClassVisitor 
    cr.accept(cv, 0); 

    // produce the modified class 
    byte[] newClassData = cw.toByteArray(); 
    return newClassData; 
} 

귀하의 질문에 정확히 최종 목표는 무엇의 세부 지침을 제공하지 않았기 때문에, I Example 클래스 케이스에서 작동하도록 하드 코딩 된 기본 예제로 가기로 결정했습니다. 변환중인 클래스의 인스턴스를 만들고 싶다면 실제로 변형되는 클래스의 전체 클래스 이름을 사용하려면 위의 "예제"가 포함 된 모든 문자열을 변경해야합니다. 또는 변형 된 모든 클래스에서 Example 클래스의 두 인스턴스를 구체적으로 원하면 위의 예제가있는 그대로 작동합니다.

+2

소원 나는이 답변을 10 upvotes 줄 수 있습니다. ASM으로 메소드를 추가/제거하는 것은 쉽습니다. 이 답변은이를 수정하기위한 중요한 기술을 보여줍니다. –