ASM #4: Ready For Action


Setelah mempelajari cara kerja JVM dan bytecode Java, aku siap untuk melakukan modifikasi class Java secara dinamis. Sebagai contoh kasus, aku akan mengubah method toString() untuk setiap bean agar menampilkan nilai dari setiap field secara otomatis.

Seperti yang telah aku catat sebelumnya, sebuah class dikenali oleh JVM melalui kombinasi dari classloader dan nama class-nya. Untuk melakukan instrumentasi bytecode, aku pasti membuat classloader sendiri. Sebagai efek sampingnya, class hasil instrumentasi tidak akan bisa dipakai oleh class yang di-load oleh JVM sebelumnya. Yup, tidak dapat dipakai secara langsung, tetapi dapat dipakai melalui superclass atau interface-nya. Aku akan mencoba memakai metode interface di tulisan kali ini. Next time, aku akan mencoba memakai agent untuk melakukan instrumentasi class yang di-load JVM. Dengan agent, aku tidak perlu memakai superclass atau interface, tetapi proses menjalankan program menjadi lebih panjang.

Byte code instrumentation dengan interface memang tidak begitu cocok untuk contoh kasus kali ini, tapi setidaknya ini cara yang paling gampang. Aku akan membuat interface Music dan implementasi-nya MusicImpl.

public interface Music {

  public String getMusicID();
  public void setMusicID(String musicId);
  public String getTitle();
  public void setTitle(String title);

}

Lalu, aku akan membuat classloader yang melakukan instrumentasi untuk MusicImpl seperti berikut:

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

public class MyClassLoader extends ClassLoader {

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    if (name.endsWith("Impl")) {
      try {
        ClassReader reader = new ClassReader(name);
        ClassWriter writer = new ClassWriter(reader,0);
        ToStringAdapter adapter = new ToStringAdapter(writer);
        reader.accept(adapter,0);
        byte b[] = writer.toByteArray();
        return defineClass(name,b,0,b.length);

      } catch (Exception e) {
        e.printStackTrace();
      }

    }
    return super.findClass(name);
  }

  protected class ToStringAdapter extends ClassAdapter {

    private Map<String,String> mapFields;
    private String className;

    public ToStringAdapter(ClassVisitor cv) {
      super(cv);
      mapFields = new HashMap<String,String>();
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {
      if (name.equals("toString")) {
        return null;
      }
      return super.visitMethod(access, name, desc, signature, exceptions);
    }

    @Override
    public void visitEnd() {
      MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
      mv.visitCode();
      mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
      mv.visitInsn(DUP);
      mv.visitLdcInsn(className + ": ");
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");

      for (String field : mapFields.keySet()) {
        mv.visitLdcInsn(field + "=[");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/String;)Ljava/lang/StringBuilder;");

        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, className, field, mapFields.get(field));
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/Object;)Ljava/lang/StringBuilder;");

        mv.visitLdcInsn("]; ");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/String;)Ljava/lang/StringBuilder;");

      }

      mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString",
        "()Ljava/lang/String;");
      mv.visitInsn(ARETURN);
      mv.visitMaxs(3,1);
      mv.visitEnd();
      super.visitEnd();
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc,
        String signature, Object value) {
      if (access==ACC_PRIVATE) {
        mapFields.put(name,desc);
      }
      return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public void visit(int version, int access, String name,
        String signature, String superName, String[] interfaces) {
      className = name;
      super.visit(version, access, name, signature, superName, interfaces);
    }

  }

}

Pada classloader tersebut, aku akan menambahkan method toString() pada semua class yang namanya diakhiri dengan “Impl”. Jika class tersebut sudah memiliki method toString(), aku akan menghapus method toString() yang sudah ada sebelum menambahkan yang baru. Untuk mempersingkat kode program, aku menganggap bahwa class yang di-transformasi memiliki field turunan dari Object saja, bukan tipe native seperti int, char, dsb. Hal ini karena aku men-hard code method append() yang aku panggil dari class StringBuilder dengan descriptor (Ljava/lang/Object;)Ljava/lang/StringBuilder;

Sekarang aku dapat mencoba memakai classloader di atas, misalnya dengan kode sederhana berikut:

public class Main {

  public Main() throws Exception {
    MyClassLoader myCL = new MyClassLoader();
    Music m = (Music) myCL.findClass("latihan.MusicImpl").newInstance();
    m.setMusicID("MID-123");
    m.setTitle("TITLE");
    System.out.println("Music = " + m);
  }

  public static void main(String[] args) throws Exception {
    new Main();
  }

}

Dan outputnya adalah:

Music = latihan/MusicImpl: title=[TITLE]; musicID=[MID-123];

Hal ini juga berlaku untuk seluruh class lain yang namanya diakhir dengan “Impl”.. Aku tidak perlu lagi membuat method toString() secara manual..

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: