bytebuddy实现原理分析 &源码分析
bytebuddy实现原理分析 &源码分析(一)
bytebuddy实现原理分析 &源码分析 (二)
bytebuddy实现原理分析 &源码分析 (三)
bytebuddy实现原理分析 &源码分析 (四)
四、字节码的操作(implementation ) pkg
byte buddy中对字节码的操作,直接调用了asm的api。
比如 FixedValue
是修改方法时定义返回的值,比如toString()=="hello"
改成toString()=="changed"
,这个意味着要把.class
字节码中的变量"hello"
替换成"changed"
。
如果是修改方法的定义,比如methodCall
。就要修改字节码指令,本地变量栈和操作数栈。
方法的编译成字节码后就是,字节码指令和本地变量栈和操作数栈。完成方法的逻辑,就是顺序的执行字节码指令操作栈帧。不了解的同学可以先去补充字节码的知识。
asm 本身包含了字节码的各类模型,并且提供API来修改。但是这是low-level
级别的。比如我要定一个加法运算,就要使用API生成多个字节码指令
。
byte buddy 就是对这样大量重复的操作做了上层的封装。
源码关于字节码的实现可以大概分为两部分: 字节码封装
,操作字节码的API
4.1 bytecode :pkg
对字节码的封装
4.1.1 StackManipulation :cls
代表生成方法的操作数栈
方法
方法名 | 描述 | |
---|---|---|
boolean isValid() |
判断生成的操作数栈是否合法 | |
Size apply (MethodVisitor methodVisitor, Implementation.Context implementationContext); |
methodVisitor 是asm的接口用来生成字节码,Implementation.Context 中定义了字节码。这个函数的作用是应用定义的字节码 |
内部类
方法名 | 类型 | 描述 |
---|---|---|
Illegal | 实现 | 非法的实现 |
Trivial | 实现 | 合法的实现,但是不会对原先的字节码有任何更改 |
Compound | 实现 | 可以对多个方法的栈大小进行合并运算 |
4.1.1.1 StackManipulation的子类实现
类名 | 类型 | 描述 |
---|---|---|
Addition | 实现 | 操作数栈的数字想加 |
Duplication | 实现 | 复制栈定的数 |
Multiplication | 实现 | 操作数栈的数字相乘 |
Removal | 实现 | 移出操作数栈的值 |
TypeCreation | 实现 | 当一个构造器方法被调用时,创建一个未定义的类型 |
Throw | 实现 | 必须位于栈顶,当StackManipulation 被调用时抛出一个异常java.lang.Throwable |
看一个具体的例子Duplication
, 其他的方法也类似: 封装字节码指令;重写apply
。
示例
复制一个在操作数栈,复制一个double 类型的数。和普通int不一样,long和double需要占1到2卡槽(2* 4 byte)。 所以这个枚举变量DOUBLE
,和字节码指令int DUP2 = 92;
关联。
封装字节码指令
/**
* A duplication of a double-sized stack value.
*/
DOUBLE(StackSize.DOUBLE, Opcodes.DUP2) {
@Override
public StackManipulation flipOver(TypeDefinition typeDefinition) {
switch (typeDefinition.getStackSize()) {
case SINGLE:
return WithFlip.DOUBLE_SINGLE;
case DOUBLE:
return WithFlip.DOUBLE_DOUBLE;
default:
throw new IllegalArgumentException("Cannot flip: " + typeDefinition);
}
}
};
新建一个Duplication
,有两个方法
/** 直接输入栈大小,和字节码指令
*/
Duplication(StackSize stackSize, int opcode) {
size = stackSize.toIncreasingSize();
this.opcode = opcode;
}
/**
* 根据类型的定义,获取栈大小
*/
public static Duplication of(TypeDefinition typeDefinition) {
switch (typeDefinition.getStackSize()) {
case SINGLE:
return SINGLE;
case DOUBLE:
return DOUBLE;
case ZERO:
return ZERO;
default:
throw new AssertionError("Unexpected type: " + typeDefinition);
}
}
实现apply
看看apply的实现,核心是methodVisitor.visitInsn(opcode);
这就是asm原生的方法,作用实在字节码码中生成这么一个指令。
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
methodVisitor.visitInsn(opcode);
return size;
}
4.1.2 assign :pkg
这个包封装了jvm规范中的两个基本类型:基本类型,引用类型
。负责控制不同类型之间的转换
4.1.2.1 Assigner : cls
一个Assigner负责把一个类型A
转换成类型B
。比如一个Assigner
可以负责类型转化:装箱,把一个基本类型(int),转化为包转类(Integer);拆箱,正好相反。
1 Typing: in & cls
指示,累型的转化是静态的还是动态的。静态的意思是,调用前已经确定了。动态类型时,生成是才确定。默认值是动态生成。
/**
* 静态生成
*/
STATIC(false),
/**
* 动态生成
*/
DYNAMIC(true);
2 核心方法: in
- StackManipulation
assign
(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing);
3 EqualTypesOnly : in & impl
Assigner的实现。
enum EqualTypesOnly implements Assigner {
/**
* An type assigner that only considers equal generic types to be assignable.
*/
GENERIC {
/** {@inheritDoc} */
public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
return source.equals(target)
? StackManipulation.Trivial.INSTANCE
: StackManipulation.Illegal.INSTANCE;
}
},
/**
* A type assigner that considers two generic types to be equal if their erasure is equal.
*/
ERASURE {
/** {@inheritDoc} */
public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
return source.asErasure().equals(target.asErasure())
? StackManipulation.Trivial