- 3、获得结果并输出
*/
byte[] newClassBytes = cw.toByteArray();
File file = new File(“xxx/test/java2/”);
file.mkdirs();
FileOutputStream fos = new FileOutputStream
(“xxx/test/java2/InjectTest.class”);
fos.write(newClassBytes);
fos.close();
关于ASM框架本身的设计,我们这里先不讨论。上面的代码会获取上一步生成的class,然后由ASM执行完插桩之后,将结果输出到 `test/java2`目录下。其中关键点就在于第2步中,如何进行插桩。
把class数据交给 `ClassReader`,然后进行分析,类似于XML解析,分析结果会以事件驱动的形式告知给`accept`的第一个参数 `ClassAdapterVisitor`。
public class ClassAdapterVisitor extends ClassVisitor {
public ClassAdapterVisitor(ClassVisitor cv) {
super(Opcodes.ASM7, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
System.out.println(“方法:” + name + " 签名:" + desc);
MethodVisitor mv = super.visitMethod(access, name, desc, signature,
exceptions);
return new MethodAdapterVisitor(api,mv, access, name, desc);
}
}
分析结果通过 `ClassAdapterVisitor`获得,一个类中会存在方法、注解、属性等,因此 `ClassReader`会将调用 `ClassAdapterVisitor`中对应的 `visitMethod`、 `visitAnnotation`、 `visitField`这些 `visitXX`方法。
我们的目的是进行函数插桩,因此重写 `visitMethod`方法,在这个方法中我们返回一个 `MethodVisitor`方法分析器对象。一个方法的参数、注解以及方法体需要在 `MethodVisitor`中进行分析与处理。
package com.enjoy.asminject.example;
import com.enjoy.asminject.ASMTest;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.commons.Method;
/**
- AdviceAdapter: 子类
- 对methodVisitor进行了扩展, 能让我们更加轻松的进行方法分析
/
public class MethodAdapterVisitor extends AdviceAdapter {
private Boolean inject;
protected MethodAdapterVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}
/* - 分析方法上面的注解
- 在这里干嘛???
-
- 判断当前这个方法是不是使用了injecttime,如果使用了,我们就需要对这个方法插桩
- 没使用,就不管了。
- @param desc
- @param visible
- @return
*/
@Override
public AnnotationVisitor visitAnnotation(String desc, Boolean visible) {
if (Type.getDescriptor(ASMTest.class).equals(desc)) {
System.out.println(desc);
inject = true;
}
return super.visitAnnotation(desc, visible);
}
private int start;
@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (inject) {
//执行完了怎么办?记录到本地变量中
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));
start = newLocal(Type.LONG_TYPE);
//创建本地 LONG类型变量
//记录 方法执行结果给创建的本地变量
storeLocal(start);
}
}
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
if (inject){
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));
int end = newLocal(Type.LONG_TYPE);
storeLocal(end);
getStatic(Type.getType(“Ljava/lang/System;”),“out”,Type.getType(“Ljava/io” +
“/PrintStream;”));
//分配内存 并dup压入栈顶让下面的INVOKESPECIAL 知道执行谁的构造方法创建StringBuilder
newInstance(Type.getType(“Ljava/lang/StringBuilder;”));
dup();
invokeConstructor(Type.getType(“Ljava/lang/StringBuilder;”),new Method("","()V"));
visitLdcInsn(“execute:”);
invokeVirtual(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“append”,"(Ljava/lang/String;)Ljava/lang/StringBuilder;"));
//减法
loadLocal(end);
loadLocal(start);
math(SUB,Type.LONG_TYPE);
invokeVirtual(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“append”,"(J)Ljava/lang/StringBuilder;"));
invokeVirtual(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“toString”,"()Ljava/lang/String;"));
invokeVirtual(Type.getType(“Ljava/io/PrintStream;”),new Method(“println”,"(Ljava/lang/String;)V"));
}
}
}
`MethodAdapterVisitor`继承自 `AdviceAdapter`,其实就是 `MethodVisitor` 的子类, `AdviceAdapter`封装了指令插入方法,更为直观与简单。
上述代码中 `onMethodEnter`进入一个方法时候回调,因此在这个方法中插入指令就是在整个方法最开始加入一些代码。我们需要在这个方法中插入 `longs=System.currentTimeMillis();`。在 `onMethodExit`中即方法最后插入输出代码。
@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (inject) {
//执行完了怎么办?记录到本地变量中
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));
start = newLocal(Type.LONG_TYPE);
//创建本地 LONG类型变量
//记录 方法执行结果给创建的本地变量
storeLocal(start);
}
}
这里面的代码怎么写?其实就是 `longs=System.currentTimeMillis();`这句代码的相对的指令。我们可以先写一份代码
void test(){
//插入的代码
long s = System.currentTimeMillis();
/**
- 方法实现代码…
*/
//插入的代码
long e = System.currentTimeMillis();
System.out.println(“execute:”+(e-s)+" ms.");
}
然后使用 `javac`编译成Class再使用 `javap-c`查看字节码指令。也可以借助插件来查看,就不需要我们手动执行各种命令。

安装完成之后,可以在需要插桩的类源码中点击右键:

点击**ASM Bytecode Viewer**之后会弹出

所以第20行代码: `longs=System.currentTimeMillis();`会包含两个指令: `INVOKESTATIC`与 `LSTORE`。
再回到 `onMethodEnter`方法中
### 最后
**如果大家需要这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档的话,[可以戳这里即可免费获取!](https://gitee.com/vip204888/java-p7)**
祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!
感谢大家的支持!!

份清华大牛整理的进大厂必备的redis视频、面试题和技术文档的话,[可以戳这里即可免费获取!](https://gitee.com/vip204888/java-p7)**
祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!
感谢大家的支持!!
[外链图片转存中...(img-Yhaptg10-1628482680295)]
最后
以上就是独特小馒头最近收集整理的关于Android程序员的硬通货,三面腾讯,已拿offer的全部内容,更多相关Android程序员内容请搜索靠谱客的其他文章。
发表评论 取消回复