ASM動的作成クラス

13537 ワード

一、ASMとは
    ASMはJAVAバイトコードの分析、作成、修正のオープンソースアプリケーションフレームワークである.ASMには、クラスのコンテンツをバイトコード操作するための多くのAPIが提供されている.従来のBCELやSERLとは異なり、ASMではバイトコードをより優雅かつ柔軟に操作する方法が提供されている.現在、ASMはSpring、Hibernateなどのオープンソースアプリケーションアーキテクチャで広く使用されています.
二、ASMは何ができるか
    クラスを分析し、バイトコードの角度からクラスを作成し、コンパイルされたクラスファイルを変更します.
三、ASM初探例
    ここではASMのCoreAPI(ASMはAPI:CoreとTreeの2つのグループを提供し、Coreはアクセス者モードに基づいてクラスを操作し、Treeはツリーノードに基づいてクラスを操作する)を使用してPersonクラスを作成します.ターゲットクラスは次のとおりです.
 
package test;

public class Person {
	private String name;  
    
    public Person(){  
        this.name = "Sum";  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
}

 このクラスは、構築メソッドで属性nameを初期化し、name属性を変更およびアクセスするための2つのpublicメソッドを提供します.
 
 次に、このクラスを作成するコードを書きます.コードは次のとおりです.
   コード1:
 
package create;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

public class CreateClass implements Opcodes{
	public static void createClass() throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException{
		ClassWriter cw = new ClassWriter(0);
		//Opcodes.V1_6        
        //Opcodes.ACC_PUBLIC      public,  
        //“test/Person”         
        //   null            ,  
        //“java/lang/Object”        
        //   null                 
		cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Person", null, "java/lang/Object", null);
		ClassVisitor cv = new CreateClassAdapter(cw);
		cv.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
		cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null).visitCode();;
		cv.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null).visitCode();;
		cv.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null).visitCode();;
		cv.visitEnd();
		
		byte[] code = cw.toByteArray();
		MyClassLoader classLoader = new MyClassLoader();
		Class<?> exampleClass = classLoader.definClassFromClassFile("test.Person", code);
		for(Method method : exampleClass.getMethods()){
			System.out.println(method);
		}
		System.out.println(exampleClass.getMethod("getName").invoke(exampleClass.newInstance(), null));
	}
	

	
	public static void main(String args[]) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException{
		createClass();
	}
}

class MyClassLoader extends ClassLoader{
	public Class definClassFromClassFile(String className, byte[] classFile) 
			throws ClassFormatError {
		return defineClass(className, classFile, 0, classFile.length);
	}
}

 コード2:
 
 
package create;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class CreateClassAdapter extends ClassVisitor implements Opcodes{

	public CreateClassAdapter(ClassVisitor cv) {
		super(Opcodes.ASM4, cv);
	}

	@Override
	public void visit(int version, int access, String name, String signature,
            String superName, String[] interfaces) {
        if (cv != null) {
            cv.visit(version, access, name, signature, superName, interfaces);
        }
    }
	
	@Override
    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {
		MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
		if(name.equals("<init>")){
			return new CreateInitMethodAdapter(mv);
		}else if(name.equals("getName")){
			return new CreateGetMethodAdapter(mv);
		}else if(name.equals("setName")){
			return new CreateSetMethodAdapter(mv);
		}else{
			return super.visitMethod(access, name, desc, signature, exceptions);
		}
	}
}

 コード3:
 
package create;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class CreateInitMethodAdapter extends MethodVisitor implements Opcodes{

	public CreateInitMethodAdapter(MethodVisitor mv) {
		super(Opcodes.ASM4, mv);
	}

	@Override
	public void visitCode(){
		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
		mv.visitVarInsn(ALOAD, 0);
		mv.visitLdcInsn("zhangzhuo");
		mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;");
		mv.visitInsn(RETURN);
		mv.visitMaxs(2, 1);
		mv.visitEnd();
	}
}

 コード4:
package create;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class CreateGetMethodAdapter extends MethodVisitor implements Opcodes{

	public CreateGetMethodAdapter(MethodVisitor mv) {
		super(Opcodes.ASM4, mv);
	}

	@Override
	public void visitCode(){
		mv.visitVarInsn(ALOAD, 0);
		mv.visitFieldInsn(GETFIELD, "test/Person", "name", "Ljava/lang/String;");
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 1);
		mv.visitEnd();
	}
}

 コード5:
package create;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class CreateSetMethodAdapter extends MethodVisitor implements Opcodes{

	public CreateSetMethodAdapter(MethodVisitor mv) {
		super(Opcodes.ASM4, mv);
	}

	@Override
	public void visitCode(){
		mv.visitVarInsn(ALOAD, 0);
		mv.visitVarInsn(ALOAD, 1);
		mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;");
		mv.visitInsn(RETURN);
		mv.visitMaxs(2, 2);
		mv.visitEnd();
	}
}

実行結果:
public java.lang.String test.Person.getName()
public void test.Person.setName(java.lang.String)
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
zhangzhuo

 
     ASMのバイトコード命令を書くのは、多くの人にとって頭が痛くて、うっかりすると書き間違えやすく、しかも調べるのも大変です.ここには実用的なコツがあります.ASMで動的にクラスを作成するには、eclipseでクラスを新規作成し、クラス内のすべての方法を書き上げることができます.bytecodeプラグインでこのクラスのASMバイトコード命令を表示し、その中の命令を直接試験すれば使えます.
     Person.java
     ASMプラグインでクラス全体のバイトコード命令が表示されます
package asm.test;
import java.util.*;
import org.objectweb.asm.*;
public class PersonDump implements Opcodes {

public static byte[] dump () throws Exception {

ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;

cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Person", null, "java/lang/Object", null);

cw.visitSource("Person.java", null);

{
fv = cw.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
fv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(6, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(7, l1);
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn("Sum");
mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;");
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLineNumber(8, l2);
mv.visitInsn(RETURN);
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l3, 0);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(11, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "test/Person", "name", "Ljava/lang/String;");
mv.visitInsn(ARETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(15, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(16, l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l2, 0);
mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 1);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
cw.visitEnd();

return cw.toByteArray();
}
}

 Personの構造関数の命令を見ました
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(6, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(7, l1);
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn("Sum");
mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;");
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLineNumber(8, l2);
mv.visitInsn(RETURN);
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l3, 0);
mv.visitMaxs(2, 1);
mv.visitEnd();
}

 この段落は直接CreateInitMethodAdapterに合格できます.もちろん削除します.
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();

  それから順番に類推して、Set方法、CreateSetMethodAdapterに試験します
{
mv = cw.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(15, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(16, l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l2, 0);
mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 1);
mv.visitMaxs(2, 2);
mv.visitEnd();
}

 getメソッドCreateGetMethodAdapter
{
mv = cw.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(11, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "test/Person", "name", "Ljava/lang/String;");
mv.visitInsn(ARETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}

 これは簡単ではありませんか.