当前版本:jdk1.8
、javassist-3.24.0-GA、eclipse
1. 声明
当前内容主要为学习和复习之用,使用javassit来为一个class类添加无参构造函数,并赋予默认值的操作
当前内容涉及:
- 读取class文件
- 为class文件中添加无参构造函数
- 使用eclipse调用无参构造函数,并打印结果
- 基本的字节码操作指令的使用
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"
,然而实际的字节码代码为:
- code.addAload(0); // 加载this
- code.addLdc(“unknown”); // 加载字符串unknown
- 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的无参构造函数添加都会出现问题,导致结果出现问题,可能有没有赋值的问题