ASMSupport教程4:生成常用操作

4.1前言

在教程开始之前首先简单介绍下生成操作的字节码命令的原理。我们知道在java代码中我们最基本的运算就是操作,比如四则运算,方法调用等比如一下代码:

String a = "str";

int i = 1 + 2;
i++;
System.out.println(a + i);


这里存在6种操作,让我们一句一句解析出操作具体如下:

1.String a = "str";
  = : 赋值
2.int i = 1 + 2;
  + : 算数运算操作
  = : 赋值
3.i++; 
  ++ : 增量操作
4 System.out.println(a + i);
  _第二个"." : 方法调用
  + : 字符串拼接操作

既然是操作就有操作因子,什么是操作因子?见下表:

代码 操作因子
String a = "str"; "str"
int i = 1 + 2; 1, 2, 1+2
i++; i
System.out.println(a + i) a, i, a + 1

总共存在10种操作,具体如下(这里只列举每种操作的其中一个,并不全部列举):

1. String a = "str";
   = : 赋值

2. i++;
   ++ : 增量操作

3. int i = 1 + 2;
   + : 算数运算操作
   = : 赋值

4. 9&7
   & :位操作

5. a > 1
   > : 关系运算操作

6. true && false
   && : 逻辑运算操作
 
7. k = i < 0 ? -i : i
   _三元操作

8. a instanceof String
   instanceof : instanceof操作
  
9. "Hello " + "asmsupport!"
   + : 字符串拼接操作
  
10. System.out.println(a + i);
   _第二个"." : 方法调用操作

在我们的程序中我们将所有的操作都通过IBlockOperators中的方法调用实现。而所有的操作因子是通過方法参数传入的。并且有的操作同样能作为其他操作的操作因子,如上面的1+2就是作为赋值操作"="的操作因子。在asmsupport中只要是继承了jw.asmsupport.Parameterized接口的都可以是作为操作因子。其实在使用asmsupport的时候大部分只要在java代码中能作为操作因子的在asmsupport中同样也能作为操作因子。因为asmsupport保留了java代码编写的方式。这里是在使用ASMSupport的角度上去介绍的。

这里将简单的介绍这些操作在底层是如何实现的。在ASMSupport中我们将java操作都抽象成为类AbstractOperator.class,每个具体的操作都是继承自它,比如instanceof操作我们将其抽象成jw.asmsupport.operators.InstanceofOperator.AbstractOperator同时也继承了jw.asmsupport.Executeable接口,这个接口有两个方法。

  • prepare()
  • execute()

每一个操作都相当于一个Executeable接口。ASMSupport会用一个List按照我们期望生成的代码的顺序将每个对应的Executeable存入到这个List中,我们称之为执行队列(执行队列中我们还会存储程序块jw.asmsupport.block.ProgramBlock),在我们将所有的操作都存入到执行队列之后先遍历所有操作,对所有操作执行prepare方法,然后再一次遍历执行队列,对每个元素执行execute方法。prepare方法的作用是整合和重新排列执行队列,execute方法是生成字节码指令。比如我们想要生成如下代码:

int value = 100 + 10;
System.out.println(value + 1);

通过ASMSupport生成这段的代码如下:

LocalVariable value = createVariable("value", AClass.INT_ACLASS, false, this.add(Value.value(100), Value.value(10)));
invoke(AClassFactory.getProductClass(System.class).getGlobalVariable("out"), "println", add(value, Value.value(1)));

我们可以看到我们分别将100,10,1通过Value.value()方法生成对应jw.asmsupport.definition.value.Value对象。而add方法则返回jw.asmsupport.operators.numerical.arithmetic.Addition对象,createVariable方法返回jw.asmsupport.definition.variable.LocalVariable对象,invoke方法返回jw.asmsupport.operators.method.MethodInvoker。我用列表的形式列出所有对象创建的顺序,顺序是最上面的最先创建。

序号 ASMSupport代码 返回结果类型 对应java代码
1 Value.value(100) jw.asmsupport.definition.value.Value 数值100
2 Value.value(10) jw.asmsupport.definition.value.Value 数值10
3 this.add() jw.asmsupport.operators.numerical.arithmetic.Addition 加法操作(100+10)
4 createVariable() jw.asmsupport.definition.variable.LocalVariable 创建变量,并且将上面的加法操作的结果存入到这个变量value。这里虽然返回的是LocalVariable,但是执行这段代码的时候在内部依然会创建一个jw.asmsupport.operators.variable.LocalVariableCreator对象和jw.asmsupport.operators.assign.Assigner对象,前者的作用是创建一个变量,后者的作用是在JVM层面将变量注册到方法的局部变量中。
5 ...getGlobalVariable("out") jw.asmsupport.definition.variable.GlobalVariable 获取System.out
6 Value.value(1) jw.asmsupport.definition.value.Value 数值1
7 add(value, Value.value(1)) jw.asmsupport.operators.numerical.arithmetic.Addition 加法操作:value+1
8 invoke(...) jw.asmsupport.operators.method.MethodInvoker 方法调用操作,第一个参数是方法的拥有者,方法名为第二参数,剩下的参数表示所要调用的方法的参数,这里表示System.out.println(value + 1)

当按照上面的顺序执行完之后,ASMSupport会用一个执行队列(List 类型)保存上面所创建出来的Executeable类型的对象(可见源码:jw.asmsupport.block.ProgramBlock的executeQueue),通过源码可以知道:所有的程序块ProgramBlock及其子类,所有的操作(AbstractOperator的子类),以及创建全局变量操作GlobalVariableCreator和方法的创建MethodCreator是实现了Executeable的方法的。所以我们将会得到如下的执行队列.

对象 别名 对应代码
Addition add1 100 + 10
LocalVariableCreator    
Assigner ass1  
Addition add2  
MethodInvoker mi System.out.println(...)

至于其他的对象呢,其实通过代码我们可以看到,都传入到相应的操作类当中去了,比如Value.value(100)和Value.value(10)传入到了add1当中去了。当所有对象都已就位之后逐一执行prepare方法,执行之后的执行队列如下:

对象 别名 对应代码
Assigner ass1 value = add1
MethodInvoker mi System.out.println(...);

为什么执行了prepare之后执行队列会将add1,add2移除呢,是因为prepare遍历这个队列的时候对每个队列中的对象判断时候被其他对象引用,如果被其他的引用了,就将其移除,而将执行execute方法的控制权转交到被引用的对象上去。程序是如何判断是否是被其他操作引用了呢,通过Parameterized接口的asArgument()方法。通过代码可以看到this.add(Value.value(100), Value.value(10))这个代码的返回值是被createVariable方法所引用的。add(value, Value.value(1))是被invoke()方法引用的。

在执行完prepare之后我们再一次遍历这个列表,然后逐一执行execute方法,执行完execute方法之后就生成了字节码了。

你可能感兴趣的:(ASMSupport教程4:生成常用操作)