ASM动态创建类

ASM是一个JAVA字节码分析和修改的框架,广泛应用于Spring、Hibernate等。本文介绍了ASM的基本概念,展示了如何使用ASM的Core API动态创建一个包含构造函数和属性访问方法的Person类。通过在Eclipse中预创建类并使用bytecode插件获取字节码指令,简化了ASM的字节码编写过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是ASM

    ASM是一个JAVA字节码分析、创建和修改的开源应用框架。在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和灵活的操作字节码的方式。目前ASM已被广泛的开源应用架构所使用,例如:Spring、Hibernate等。

二、ASM能干什么

    分析一个类、从字节码角度创建一个类、修改一个已经被编译过的类文件

三、ASM初探例子

    这里我们使用ASM的CoreAPI(ASM提供了两组API:Core和Tree,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,并提供了两个public方法来修改和访问name属性。

 

 接下来就要书写创建这个类的代码了,代码如下:

   代码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();
}

 这样是不是很简单

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值