Java:使用javassist操作class文件添加无参构造函数并赋予默认值和一个奇怪的bug

当前版本:jdk1.8javassist-3.24.0-GA、eclipse

1. 声明

当前内容主要为学习和复习之用,使用javassit来为一个class类添加无参构造函数,并赋予默认值的操作

当前内容涉及:

  1. 读取class文件
  2. 为class文件中添加无参构造函数
  3. 使用eclipse调用无参构造函数,并打印结果
  4. 基本的字节码操作指令的使用

2. 基本demo

首先准备一个实体类User(保存的时候自动编译为class文件)

public class User {
	public /* static */ String name /* = "unknown" */;

	public User(String _name) {
		name = _name;
	}
	
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "[User name=" + name + "]";
	}
}

然后开始使用demo修改class文件

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.Bytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.DuplicateMemberException;
import javassist.bytecode.MethodInfo;

/**
 * 
 * @author hy
 * @createTime 2021-07-17 13:42:58
 * @description 当前内容主要为使用javaassit的ClassFile来获取当前的文件信息
 *
 */
public class JavaassitModifyUserClassFileTest {
	public static void main(String[] args) {
		String classFilePath = "D:\\eclipse-workspace\\Java8BasicReStudy\\target\\classes\\com\\hy\\java\\bytecode\\classfile\\User.class";
		try (DataInputStream dis = new DataInputStream(new FileInputStream(new File(classFilePath)))) {
			ClassFile classFile = new ClassFile(dis);
			ConstPool cp = classFile.getConstPool();

			Bytecode code = new Bytecode(cp);
			code.addAload(0);
			code.addInvokespecial("java/lang/Object", MethodInfo.nameInit, "()V");
			code.addAload(0);
			code.addLdc("unknown");
			code.addPutfield(User.class.getName(), "name", "Ljava/lang/String;");
			code.addReturn(null);
			code.setMaxLocals(1);

			MethodInfo minfo = new MethodInfo(cp, MethodInfo.nameInit, "()V");
			minfo.setCodeAttribute(code.toCodeAttribute());
			minfo.setAccessFlags(AccessFlag.PUBLIC);
			classFile.addMethod(minfo);
			classFile.write(new DataOutputStream(new FileOutputStream(new File(classFilePath))));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (DuplicateMemberException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}

这里的Bytecode 就是当前方法中的实现指令,aload_0就是加载的this,invoke_special就是调用Object的无参构造函数()V

主要业务逻辑为:在里面使用this.name="unknown",然而实际的字节码代码为:

  1. code.addAload(0); // 加载this
  2. code.addLdc(“unknown”); // 加载字符串unknown
  3. code.addPutfield(User.class.getName(), “name”, “Ljava/lang/String;”); //为name执行赋值指令

对应的字节码为:
在这里插入图片描述

开始执行当前的字节码修改指令:使用jad反编译得到
在这里插入图片描述
此时开始创建测试

public class UserTest {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		//User user = (User) Class.forName(User.class.getName()).newInstance();
		User user = new User();
		System.out.println(user);
		User user2 = new User("admin");	
		System.out.println(user2);

	}

}

一个非常简单的demo,但是还是出现了编译报错,但是却没有在文件上报错
在这里插入图片描述

在这里插入图片描述

执行结果为:
在这里插入图片描述

测试添加构造方法并赋值成功!

3. 一个字节码修改后出现的问题

1. 如果我们为当前的name字段赋予了初始值,那么只是为User这个类创建public方法后会发生一个奇怪的事情

public class User {
	public /* static */ String name  = "unknown" ;

	public User(String _name) {
		name = _name;
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "[User name=" + name + "]";
	}
}

自动编译后是这样的:
在这里插入图片描述
取消赋值(this.name="unknown")的这三行字节码指令后,这个时候调用修改的操作后
在这里插入图片描述
在这里插入图片描述

结果发现当前的类中的实例字段赋值已经没有了,直接跑到上面的这个方法中了,所以测试结果也是不正确的
在这里插入图片描述

但是直接使用无参加赋值的结果又不一样

public class User {
	public /* static */ String name  = "unknown" ;
	public User(String _name) {
		name = _name;
	}
	public User() {  }	 		
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "[User name=" + name + "]";
	}
}

jad反编译的结果为:
在这里插入图片描述

执行测试的结果:
在这里插入图片描述

这个可以肯定的就是使用Javassist的无参构造函数添加都会出现问题,导致结果出现问题,可能有没有赋值的问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
对于Java中的构造函数,我们不能像普通方法一样直接使用Clone方法进行克隆,因为构造函数是在创建对象时自动调用的。但是,我们可以使用Javassist库来实现静态构造函数的克隆。 Javassist一个用于在运行时修改字节码的Java库。我们可以使用Javassist创建一个新的类,并将原始类的所有方法和字段复制到新类中。然后,我们可以在新类中修改构造函数的实现,以实现克隆。 以下是一个示例代码,演示如何使用Javassist创建静态构造函数的克隆: ``` import javassist.*; public class ConstructorClone { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass origClass = pool.get("com.example.OriginalClass"); CtClass newClass = pool.makeClass("com.example.NewClass"); // Copy all fields and methods from original class to new class newClass.setSuperclass(origClass); CtField[] fields = origClass.getDeclaredFields(); for (CtField field : fields) { newClass.addField(field); } CtMethod[] methods = origClass.getDeclaredMethods(); for (CtMethod method : methods) { newClass.addMethod(method); } // Clone constructor CtConstructor origConstructor = origClass.getDeclaredConstructor(new CtClass[]{}); CtConstructor newConstructor = CtNewConstructor.copy(origConstructor, newClass, null); newClass.addConstructor(newConstructor); // Modify constructor implementation newConstructor.setBody("{ super(); }"); // Create instance of new class Object newObject = newClass.toClass().newInstance(); } } ``` 在上面的代码中,我们首先获取了原始类的CtClass对象,并创建了一个新的CtClass对象。然后,我们将原始类的所有字段和方法复制到新类中。接下来,我们使用CtNewConstructor.copy方法克隆原始类的构造函数,并将其添加到新类中。最后,我们修改新构造函数的实现,以调用原始构造函数并执行其他操作。 请注意,这只是一个示例代码,实际应用中可能需要更复杂的处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值