大家好,我是瑞英。
本篇会讲述如何进行构造方法热修。能够实现修复构造方法中,super()调用之后的逻辑。
如前文所述,热修一个方法的前提是,要在方法体内预先插入代理执行逻辑。如下示例:
但构造方法与非构造方法的区别在于,进入方法体的第一条语句时,“this”对象是否被初始化。热修代理方法调用,需要传递的一个参数是,当前类的实例对象。
构造方法内的第一条语句
java中
构造方法必须进行直接或间接(调用当前类其他构造方法)调用父类构造方法。在编写代码时,会要求若构造方法体内存在super调用,必须放在第一条语句。如果不是则会报错,如下:
若没有显示调用父类构造方法的语句,则编译器会自动补充调用父类无参构造方法super()
,若父类没有无参构造方法,那么子类必须显示调用父类构造方法,可以通过间接调用当前类某个构造方法,或者直接调用父类构造方法两种途径实现。
class Base {
public Base(int a) {
}
}
class SubClass extends Base {
public SubClass() {
super(3); // 直接显示调用父类构造方法
}
public SubClass(int a){
this(); // 调用当前类构造方法实现间接调用父类构造方法
// super(4); // 也可以直接调用父类构造方法
}
}
kotlin中
本质上同java一样,构造方法必须要直接或间接调用父类构造方法,但是kotlin中将构造方法分为主构造方法和次构造方法。一个类可以有0或1个主构造方法,若干个次构造方法。同样若父类有无参构造方法,开发者没有显示调用构造方法会自动补充调用无参构造方法。
- 若类没有主构造方法
- 那么所有的次构造方法,都必须直接或间接(调用当前类的其他次构造方法)调用父类构造方法
- 若类有主构造方法
- 主构造方法必须显示调用父类构造方法
- 那么所有的次构造方法,都必须直接或间接(调用当前类的其他次构造方法)调用主构造方法,子类的次构造方法不能直接调用父类构造方法
open class Parent(val name: String)
// 有主构造方法的子类
class Child(name: String, val age: Int) : Parent(name) { //主构造方法显示调用父类构造方法
init {
println("Child initialized with name: $name and age: $age")
}
```
// 次构造方法,调用主构造方法
constructor(name: String) : this(name, 0) {
println("Child initialized with name: $name and default age: 0")
}
}
// 没有主构造方法的子类
class Child2 : Parent {
// 次构造方法,调用父类构造方法
constructor(name: String, age: Int) : super(name) {
println("Child initialized with name: $name and age: $age")
}
// 次构造方法,调用当前类其他次构造方法
constructor(name: String) : this(name, 0) {
println("Child initialized with name: $name and default age: 0")
}
// 次构造方法,调用父类构造方法
constructor(age: Int) : super("DefaultName") {
println("Child initialized with default name and age: $age")
}
}
热修构造方法的插入点
在父类构造方法/当前类其他构造方法调用语句之后,进行热修proxy插桩。换言之,构造方法热修,修复的是,父类初始化完成后的逻辑。若直接在构造方法第一条语句插入热修代理调用会抛出java.lang.VerifyError异常。如下所示:
使用了一个没有被初始化的引用
ASM如何落实热修构造方法的插入
定义一个ConstructorVisitor,在ClassVisitor访问类时,对构造方法进行访问,并完成热修代理调用语句插入
class ConstructorVisitor(private val superClassName: String, private val currentClassName: String) : MethodVisitor(ASM_API) {
private var flag = false //标识是否已经插入了代码
override fun visitMethodInsn(opcode: Int, owner: String, name: String, descriptor: String, isInterface: Boolean) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
if (!flag && (name == "<init>" && (owner == superClassName || owner == currentClassName ) )) {
}
}
}
关于通过热修对类新增构造方法,将会在后续篇章中讲述。