ASM字节码操作类库(打开java语言世界通往字节码世界的大门) | 京东云技术团队

前言:授人以鱼不如授人以渔,应用asm的文章有很多,简单demo的也很多,那么ASM都具备哪些能力呢?如何去学习编写ASM代码呢?什么样的情景需要用到ASM呢?让我们带着这些问题阅读这篇文章吧。

这里由于篇幅限制做了删减(第六部分TreeApi和CoreApi的比较、核心API类的介绍等),如果有兴趣可以联系作者进行交流,

个人认为核心在于第五部分如何查看一个想写的类的ASM代码如何写,以及全面了解ASM都有哪些能力,这样在后面的特定场景下我们才会知道可以通过它来实现想做的功能

一、ASM介绍

1、ASM 是什么

ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从中构建定制的复杂转换和代码分析工具。ASM提供了与其他Java字节码框架类似的功能,但侧重于性能。由于它的设计和实现尽可能小和快,因此非常适合在动态系统中使用(但当然也可以以静态方式使用,例如在编译器中)。

一个.java文件经过Java编译器(javac)编译之后会生成一个.class文件。在.class文件中,存储的是字节码(ByteCode)数据。ASM所操作的对象是字节码(ByteCode),而在许多情况下,字节码(ByteCode)的具体表现形式是.class文件。

ASM处理字节码(ByteCode)的方式是“拆分-修改-合并”。

字节码工具类创建实现接口方法调用类扩展父类方法调用优点缺点常见使用学习成本
java-proxy支持支持支持不支持不支持简单动态代理首选功能有限,不支持扩展spring-aop,MyBatis1星
asm支持支持支持支持支持任意字节码插入,几乎不受限制学习难度大,编写代码多cglib5星
javaassit支持支持支持支持支持java原始语法,字符串形式插入,写入直观不支持jdk1.5以上的语法,如泛型,增强forFastjson,MyBatis2星
cglib支持支持支持支持支持与bytebuddy看起来差不多正在被bytebuddy淘汰EasyMock,jackson-databind3星
bytebuddy支持支持支持支持支持支持任意维度的拦截,可以获取原始类、方法,以及代理类和全部参数不太直观,学习理解有些成本,API非常多SkyWalking,Mockito,Hibernate,powermock3星

比较表格参考: http://xingyun.jd.com/shendeng/article/detail/7826

  • ASM官网:https://asm.ow2.io/
  • ASM源码:https://gitlab.ow2.org/asm/asm
  • 开发者指南:https://asm.ow2.io/developer-guide.html

2、ASM能做什么

生成、修改、删除(接口、类、字段、方法…)ASM能够对字节码数据进行analyze、generate、transformation,ASM可以形象的理解为“Java语言世界”边缘上一扇大门,通过这扇大门,可以帮助我们进入到“字节码的世界”。

3、ASM实际的使用场景

3.1、Spring当中的ASM

第一个应用场景,是Spring框架当中的AOP。 在很多Java项目中,都会使用到Spring框架,而Spring框架当中的AOP(Aspect Oriented Programming)是依赖于ASM的。具体来说,Spring的AOP,可以通过JDK的动态代理来实现,也可以通过CGLIB实现。其中,CGLib (Code Generation Library)是在ASM的基础上构建起来的,所以,Spring AOP是间接的使用了ASM。(参考自 Spring Framework Reference Documentation的 8.6 Proxying mechanisms)。

3.2、JDK当中的ASM

第二个应用场景,是JDK当中的Lambda表达式。 在Java 8中引入了一个非常重要的特性,就是支持Lambda表达式。Lambda表达式,允许把方法作为参数进行传递,它能够使代码变的更加简洁紧凑。但是,我们可能没有注意到,其实,在现阶段(Java 8版本),Lambda表达式的调用是通过ASM来实现的。

在rt.jar文件的jdk.internal.org.objectweb.asm包当中,就包含了JDK内置的ASM代码。在JDK 8版本当中,它所使用的ASM 5.0版本。

如果我们跟踪Lambda表达式的编码实现,就会找到InnerClassLambdaMetafactory.spinInnerClass()方法。在这个方法当中,我们就会看到:JDK会使用jdk.internal.org.objectweb.asm.ClassWriter来生成一个类,将lambda表达式的代码包装起来。

LambdaMetafactory.metafactory() 第一步,找到这个方法​ InnerClassLambdaMetafactory.buildCallSite() 第二步,找到这个方法

​ InnerClassLambdaMetafactory.spinInnerClass() 第三步,找到这个方法

4、 ASM的两个组成部分

从组成结构上来说,ASM分成两部分,一部分为Core API,另一部分为Tree API。

其中,Core API包括asm.jar、asm-util.jar和asm-commons.jar;其中,Tree API包括asm-tree.jar和asm-analysis.jar。

asm.jar内核心类:ClassReader、ClassVisitor、ClassWriter、FieldVisitor、FieldWriter、MethodVisitor、MethodWriter、Label、Opcodes、Type

ClassReader类,负责读取.class文件里的内容,然后拆分成各个不同的部分。ClassVisitor类,负责对.class文件中某一部分里的信息进行修改。ClassWriter类,负责将各个不同的部分重新组合成一个完整的.class文件。

asm-util.jar内核心类

以Check开头的类,主要负责检查(Check)生成的.class文件内容是否正确。以Trace开头的类,主要负责将.class文件的内容打印成文字输出。根据输出的文字信息,可以探索或追踪(Trace).class文件的内部信息。

5、ClassFile

我们都知道,在.class文件中,存储的是ByteCode数据。但是,这些ByteCode数据并不是杂乱无章的,而是遵循一定的数据结构。

这个.class文件遵循的数据结构就是由 Java Virtual Machine Specification中定义的 The class File Format

6、常见的字节码类库

Apache Commons BCEL:其中BCEL为Byte Code Engineering Library首字母的缩写。

Javassist:Javassist表示Java programming assistant

ObjectWeb ASM:本课程的研究对象。

Byte Buddy:在ASM基础上实现的一个类库。

二、无中生有

1、生成新的接口

预期目标:

生成一个正常接口结构定义的.class文件

public interface ASMInterface {byte byteType = 1;short shortType = 1;int intType = 1;char charType = 's';float floatType = 1.1F;double doubleType = 1.2;long longType = 1L;boolean booleanType = false;Byte ByteType = 1;Short ShortType = Short.valueOf((short)1);Integer IntegerType = 1;String StringType = "s";Float FloatType = 1.1F;Double DoubleType = 1.1;Long LongType = 1L;Boolean BooleanType = true;void function();default String defaultFunction(Integer integer) {System.out.println("param = " + integer);return String.valueOf(integer);}static Integer getInteger(String str) {return Integer.valueOf(str);}
}
编码实现:
public class InterfaceGenerateCore {public static void main(String[] args) throws Exception {String relative_path = "sample/ASMGenerateInterface.class";String filepath = FileUtils.getFilePath(relative_path);// (1) 生成byte[]内容byte[] bytes = dump();// (2) 保存byte[]到文件FileUtils.writeBytes(filepath, bytes);}public static byte[] dump() throws Exception {// (1) 创建ClassWriter对象ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);// (2) 调用visitXxx()方法,调用顺序和说明如下/**  visit*  [visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]*  (visitAnnotation |*   visitTypeAnnotation |*   visitAttribute)**  (visitNestMember |*   visitInnerClass |*   visitRecordComponent |*   visitField |*   visitMethod)**  visitEnd*  []: 表示最多调用一次,可以不调用,但最多调用一次。*  ()和|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。*  *: 表示方法可以调用0次或多次。* *///定义接口/**visit(version, access, name, signature, superName, interfaces)*version: 表示当前类的版本信息。在下述示例代码中,其取值为Opcodes.V1_8,表示使用Java 8版本。*access: 表示当前类的访问标识(access flag)信息。在下面的示例中,access的取值是ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,也可以写成ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE。如果想进一步了解这些标识的含义,可以参考Java Virtual Machine Specification的Chapter 4. The class File Format部分。*name: 表示当前类的名字,它采用的格式是Internal Name的形式。在.java文件中,我们使用Java语言来编写代码,使用类名的形式是Fully Qualified Class Name,例如java.lang.String;将.java文件编译之后,就会生成.class文件;在.class文件中,类名的形式会发生变化,称之为Internal Name,例如java/lang/String。因此,将Fully Qualified Class Name转换成Internal Name的方式就是,将.字符转换成/字符。*signature: 表示当前类的泛型信息。因为在这个接口当中不包含任何的泛型信息,因此它的值为null。*superName: 表示当前类的父类信息,它采用的格式是Internal Name的形式。*interfaces: 表示当前类实现了哪些接口信息。**/cw.visit(V1_8,                                        // versionACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,   // access"sample/ASMGenerateInterface",               // namenull,                                        // signature"java/lang/Object",                          // superNamenull                                         // interfaces);//定义字段-基本类型/** visitField(access, name, descriptor, signature, value)*access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。*name参数:表示当前字段或方法的名字。*descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。byte-B、short-S、int-I、char-C、具体可以参考如下示例代码*signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。*value参数:是visitField()方法的第5个参数。这个参数的取值,与当前字段是否为常量有关系。如果当前字段是一个常量,就需要给value参数提供某一个具体的值;如果当前字段不是常量,那么使用null就可以了。* */{FieldVisitor fv1 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "byteType", "B", null, new Integer(1));fv1.visitEnd();}{FieldVisitor fv2 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "shortType", "S", null, new Integer(1));fv2.visitEnd();}{FieldVisitor fv3 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "intType", "I", null, new Integer(1));fv3.visitEnd();}{FieldVisitor fv4 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "charType", "C", null, 's');fv4.visitEnd();}{FieldVisitor fv5 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "floatType", "F", null, new Float("1.1"));fv5.visitEnd();}{FieldVisitor fv6 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "doubleType", "D", null, new Double("1.2"));fv6.visitEnd();}{FieldVisitor fv7 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "longType", "J", null, new Long(1L));fv7.visitEnd();}{FieldVisitor fv8 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "booleanType", "Z", null, new Integer(0));fv8.visitEnd();}//定义变量-包装类型{FieldVisitor fv11 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "ByteType", "Ljava/lang/Byte;", null, null);fv11.visitEnd();}{FieldVisitor fv12 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "ShortType", "Ljava/lang/Short;", null,null);fv12.visitEnd();}{FieldVisitor fv13 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "IntegerType", "Ljava/lang/Integer;", null,null);fv13.visitEnd();}{FieldVisitor fv14 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "StringType", "Ljava/lang/String;", null, "s");fv14.visitEnd();}{FieldVisitor fv15 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "FloatType", "Ljava/lang/Float;", null,null);fv15.visitEnd();}{FieldVisitor fv16 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "DoubleType", "Ljava/lang/Double;", null,null);fv16.visitEnd();}{FieldVisitor fv17 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "LongType", "Ljava/lang/Long;", null,  null);fv17.visitEnd();}{FieldVisitor fv18 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "BooleanType", "Ljava/lang/Boolean;", null, null);fv18.visitEnd();}//定义方法-抽象方法/** visitMethod(access, name, descriptor, signature, exceptions)*access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。*name参数:表示当前字段或方法的名字。*descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。()内为入参,后面为反参*signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。*exceptions参数:是visitMethod()方法的第5个参数。这个参数的取值,与当前方法声明中是否具有throws XxxException相关。* */{MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "function", "()V", null, null);mv1.visitEnd();}//定义方法-默认方法{MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "defaultFunction", "(Ljava/lang/Integer;)Ljava/lang/String;", null, null);mv2.visitCode();mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitTypeInsn(NEW, "java/lang/StringBuilder");mv2.visitInsn(DUP);mv2.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);mv2.visitLdcInsn("param = ");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv2.visitVarInsn(ALOAD, 1);mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false);mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);mv2.visitVarInsn(ALOAD, 1);mv2.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);mv2.visitInsn(ARETURN);mv2.visitMaxs(3, 2);mv2.visitEnd();}//定义方法-静态方法{MethodVisitor mv3 = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "getInteger", "(Ljava/lang/String;)Ljava/lang/Integer;", null, null);mv3.visitCode();mv3.visitVarInsn(ALOAD, 0);mv3.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(Ljava/lang/String;)Ljava/lang/Integer;", false);mv3.visitInsn(ARETURN);mv3.visitMaxs(1, 1);mv3.visitEnd();}{MethodVisitor mv4 = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);mv4.visitCode();mv4.visitInsn(ICONST_1);mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "ByteType", "Ljava/lang/Byte;");mv4.visitInsn(ICONST_1);mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "ShortType", "Ljava/lang/Short;");mv4.visitInsn(ICONST_1);mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "IntegerType", "Ljava/lang/Integer;");mv4.visitLdcInsn(new Float("1.1"));mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "FloatType", "Ljava/lang/Float;");mv4.visitLdcInsn(new Double("1.1"));mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "DoubleType", "Ljava/lang/Double;");mv4.visitInsn(LCONST_1);mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "LongType", "Ljava/lang/Long;");mv4.visitInsn(ICONST_1);mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "BooleanType", "Ljava/lang/Boolean;");mv4.visitInsn(RETURN);mv4.visitMaxs(2, 0);mv4.visitEnd();}cw.visitEnd(); // 注意,最后要调用visitEnd()方法// (3) 调用toByteArray()方法return cw.toByteArray();}
}
验证结果:

生成的接口是否正确

public class HelloWorldRun {public static void main(String[] args) throws Exception {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();Class<?> clazz = classLoader.loadClass("sample.ASMGenerateInterface");Field[] declaredFields = clazz.getDeclaredFields();if (declaredFields.length > 0) {System.out.println("fields:");for (Field f : declaredFields) {Object value = f.get(null);System.out.println("    " + f.getName() + ": " + value);}}Method[] declaredMethods = clazz.getDeclaredMethods();if (declaredMethods.length > 0) {System.out.println("methods:");for (Method m : declaredMethods) {System.out.println("    " + m.getName());}}}
}
效果图如下:

2、生成新的类

预期目标:

生成一个正常类结构定义的.class文件

public class ASMClass {//定义变量-基本类型byte byteType = 1;short shortType = 1;int intType = 1;char charType = 's';float floatType = 1.1f;double doubleType = 1.2;long longType = 1;boolean booleanType = false;//定义变量-包装类型Byte ByteType = 1;Short ShortType = 1;Integer IntegerType = 1;String StringType = "string";Float FloatType = 1.1f;Double DoubleType = 1.1;Long LongType = 1l;@DeprecatedBoolean BooleanType = true;/** 静态方法* */public static Integer getInteger(String str) {return Integer.valueOf(str);}/** 实例方法* */public String instanceMethod(Integer integer) {return String.valueOf(integer);}
}
编码实现:
public class ClassGenerateCore {public static void main(String[] args) throws Exception {String relative_path = "sample/ASMGenerateClass.class";String filepath = FileUtils.getFilePath(relative_path);// (1) 生成byte[]内容byte[] bytes = dump();// (2) 保存byte[]到文件FileUtils.writeBytes(filepath, bytes);}public static byte[] dump() throws Exception {// (1) 创建ClassWriter对象ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);// (2) 调用visitXxx()方法,调用顺序和说明如下/**  visit*  [visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]*  (visitAnnotation |*   visitTypeAnnotation |*   visitAttribute)**  (visitNestMember |*   visitInnerClass |*   visitRecordComponent |*   visitField |*   visitMethod)**  visitEnd*  []: 表示最多调用一次,可以不调用,但最多调用一次。*  ()和|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。*  *: 表示方法可以调用0次或多次。* *///定义接口/**visit(version, access, name, signature, superName, interfaces)*version: 表示当前类的版本信息。在下述示例代码中,其取值为Opcodes.V1_8,表示使用Java 8版本。*access: 表示当前类的访问标识(access flag)信息。在下面的示例中,access的取值是ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,也可以写成ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE。如果想进一步了解这些标识的含义,可以参考Java Virtual Machine Specification的Chapter 4. The class File Format部分。*name: 表示当前类的名字,它采用的格式是Internal Name的形式。在.java文件中,我们使用Java语言来编写代码,使用类名的形式是Fully Qualified Class Name,例如java.lang.String;将.java文件编译之后,就会生成.class文件;在.class文件中,类名的形式会发生变化,称之为Internal Name,例如java/lang/String。因此,将Fully Qualified Class Name转换成Internal Name的方式就是,将.字符转换成/字符。*signature: 表示当前类的泛型信息。因为在这个接口当中不包含任何的泛型信息,因此它的值为null。*superName: 表示当前类的父类信息,它采用的格式是Internal Name的形式。*interfaces: 表示当前类实现了哪些接口信息。**/cw.visit(V1_8,                                        // versionACC_PUBLIC + ACC_SUPER,   // access"sample/ASMGenerateClass",               // namenull,                                        // signature"java/lang/Object",                          // superNamenull                                         // interfaces);//定义字段-基本类型/** visitField(access, name, descriptor, signature, value)*access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。*name参数:表示当前字段或方法的名字。*descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。byte-B、short-S、int-I、char-C、具体可以参考如下示例代码*signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。*value参数:是visitField()方法的第5个参数。这个参数的取值,与当前字段是否为常量有关系。如果当前字段是一个常量,就需要给value参数提供某一个具体的值;如果当前字段不是常量,那么使用null就可以了。* */{FieldVisitor fv1 = cw.visitField(0, "byteType", "B", null, new Byte("1"));fv1.visitEnd();}{FieldVisitor fv2 = cw.visitField(0, "shortType", "S", null, new Short("1"));fv2.visitEnd();}{FieldVisitor fv3 = cw.visitField(0, "intType", "I", null, new Integer(1));fv3.visitEnd();}{FieldVisitor fv4 = cw.visitField(0, "charType", "C", null, "s");fv4.visitEnd();}{FieldVisitor fv5 = cw.visitField(0, "floatType", "F", null, new Float("1.1"));fv5.visitEnd();}{FieldVisitor fv6 = cw.visitField(0, "doubleType", "D", null, new Double("1.2"));fv6.visitEnd();}{FieldVisitor fv7 = cw.visitField(0, "longType", "J", null, new Long(1));fv7.visitEnd();}{FieldVisitor fv8 = cw.visitField(0, "booleanType", "Z", null, false);fv8.visitEnd();}//定义变量-包装类型{FieldVisitor fv11 = cw.visitField(0, "ByteType", "Ljava/lang/Byte;", null, 1);fv11.visitEnd();}{FieldVisitor fv12 = cw.visitField(0, "ShortType", "Ljava/lang/Short;", null, 1);fv12.visitEnd();}{FieldVisitor fv13 = cw.visitField(0, "IntegerType", "Ljava/lang/Integer;", null, 1);fv13.visitEnd();}{FieldVisitor fv14 = cw.visitField(0, "StringType", "Ljava/lang/String;", null, "s");fv14.visitEnd();}{FieldVisitor fv15 = cw.visitField(0, "FloatType", "Ljava/lang/Float;", null, 1.1f);fv15.visitEnd();}{FieldVisitor fv16 = cw.visitField(ACC_PUBLIC, "DoubleType", "Ljava/lang/Double;", null, 1.1);fv16.visitEnd();}{FieldVisitor fv17 = cw.visitField(0, "LongType", "Ljava/lang/Long;", null, 1l);fv17.visitEnd();}{FieldVisitor fv18 = cw.visitField(ACC_DEPRECATED, "BooleanType", "Ljava/lang/Boolean;", null, true);{AnnotationVisitor annotationVisitor0 = fv18.visitAnnotation("Ljava/lang/Deprecated;", true);annotationVisitor0.visitEnd();}fv18.visitEnd();}/** visitMethod(access, name, descriptor, signature, exceptions)*access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。*name参数:表示当前字段或方法的名字。*descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。()内为入参,后面为反参*signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。*exceptions参数:是visitMethod()方法的第5个参数。这个参数的取值,与当前方法声明中是否具有throws XxxException相关。* *///定义方法-静态代码块{MethodVisitor mv2 = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);mv2.visitCode();mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitLdcInsn("class initialization method");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);mv2.visitInsn(RETURN);mv2.visitMaxs(2, 0);mv2.visitEnd();}//定义方法-无参构造器{MethodVisitor methodVisitor = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);methodVisitor.visitCode();methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitInsn(ICONST_1);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "byteType", "B");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitInsn(ICONST_1);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "shortType", "S");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitInsn(ICONST_1);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "intType", "I");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitIntInsn(BIPUSH, 115);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "charType", "C");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitLdcInsn(new Float("1.1"));methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "floatType", "F");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitLdcInsn(new Double("1.2"));methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "doubleType", "D");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitInsn(LCONST_1);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "longType", "J");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitInsn(ICONST_0);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "booleanType", "Z");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitInsn(ICONST_1);methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "ByteType", "Ljava/lang/Byte;");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitInsn(ICONST_1);methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "ShortType", "Ljava/lang/Short;");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitInsn(ICONST_1);methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "IntegerType", "Ljava/lang/Integer;");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitLdcInsn("string");methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "StringType", "Ljava/lang/String;");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitLdcInsn(new Float("1.1"));methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "FloatType", "Ljava/lang/Float;");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitLdcInsn(new Double("1.1"));methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "DoubleType", "Ljava/lang/Double;");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitInsn(LCONST_1);methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "LongType", "Ljava/lang/Long;");methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitInsn(ICONST_1);methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "BooleanType", "Ljava/lang/Boolean;");methodVisitor.visitInsn(RETURN);methodVisitor.visitMaxs(3, 1);methodVisitor.visitEnd();}//定义方法-静态方法{MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "getInteger", "(Ljava/lang/String;)Ljava/lang/Integer;", null, null);mv1.visitCode();mv1.visitVarInsn(ALOAD, 0);mv1.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(Ljava/lang/String;)Ljava/lang/Integer;", false);mv1.visitInsn(ARETURN);mv1.visitMaxs(1, 1);mv1.visitEnd();}//定义方法-实例方法{MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "instanceMethod", "(Ljava/lang/Integer;)Ljava/lang/String;", null, null);mv2.visitCode();mv2.visitVarInsn(ALOAD, 1);mv2.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);mv2.visitInsn(ARETURN);mv2.visitMaxs(1, 2);mv2.visitEnd();}cw.visitEnd(); // 注意,最后要调用visitEnd()方法// (3) 调用toByteArray()方法return cw.toByteArray();}
}
验证结果:
public class HelloWorldRun {public static void main(String[] args) throws Exception {Class<?> clazz = Class.forName("sample.ASMGenerateClass");Method method = clazz.getDeclaredMethod("instanceMethod",Integer.class);Object instance = clazz.newInstance();Object invoke = method.invoke(instance, new Integer(12));Class<?> aClass = invoke.getClass();System.out.println("aClass = " + aClass);}
}
或者
public class HelloWorldRun {public static void main(String[] args) throws Exception {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();Class<?> clazz = classLoader.loadClass("sample.ASMGenerateClass");Field[] declaredFields = clazz.getDeclaredFields();if (declaredFields.length > 0) {for (Field f : declaredFields) {Object value = f.get(null);System.out.println("    " + f.getName() + ": " + value);}}Method[] declaredMethods = clazz.getDeclaredMethods();if (declaredMethods.length > 0) {for (Method m : declaredMethods) {System.out.println("    " + m.getName());}}}
}
效果图如下:

ClassVisitor中visitXxx()的调用顺序

visit
[visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
(visitAnnotation |visitTypeAnnotation |visitAttribute
)*
(visitNestMember |visitInnerClass |visitRecordComponent |visitField |visitMethod
)* 
visitEnd
其中,涉及到一些符号,它们的含义如下:
[]: 表示最多调用一次,可以不调用,但最多调用一次。
()和|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
*: 表示方法可以调用0次或多次。

FieldVisitor中visitXxx()的调用顺序

(visitAnnotation |visitTypeAnnotation |visitAttribute
)*
visitEnd

MethodVisitor中visitXxx()的调用顺序

(visitParameter)*
[visitAnnotationDefault]
(visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation | visitTypeAnnotation | visitAttribute)*
[visitCode(visitFrame |visitXxxInsn |visitLabel |visitInsnAnnotation |visitTryCatchBlock |visitTryCatchAnnotation |visitLocalVariable |visitLocalVariableAnnotation |visitLineNumber)*visitMaxs
]
visitEnd
第一组,在visitCode()方法之前的方法。这一组的方法,主要负责parameter、annotation和attributes等内容
第二组,在visitCode()方法和visitMaxs()方法之间的方法。这一组的方法,主要负责当前方法的“方法体”内的opcode内容。其中,visitCode()方法,标志着方法体的开始,而visitMaxs()方法,标志着方法体的结束。
第三组,是visitEnd()方法。这个visitEnd()方法,是最后一个进行调用的方法。

不同的MethodVisitor对象,它们的visitXxx()方法是彼此独立的,只要各自遵循方法的调用顺序,就能够得到正确的结果。

三、狸猫换太子

1、修改类的版本

ClassVisitor子类实现

public class ClassChangeVersionVisitor extends ClassVisitor {public ClassChangeVersionVisitor(int api, ClassVisitor classVisitor) {super(api, classVisitor);}@Overridepublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {super.visit(Opcodes.V1_7, access, name, signature, superName, interfaces);}
}

使用ClassVisitor的子类ClassChangeVersionVisitor进行类的版本修改

public class ASMModifyClass {public static void main(String[] args) throws Exception {String relative_path = "sample/HelloWorld.class";String filepath = FileUtils.getFilePath(relative_path);byte[] bytes = FileUtils.readBytes(filepath);//(1)构建ClassReaderClassReader cr = new ClassReader(bytes);//(2)构建ClassWriterClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);//(3)串连ClassVisitorint api = Opcodes.ASM9;ClassVisitor cv = new ClassChangeVersionVisitor(api, cw);//(4)结合ClassReader和ClassVisitorint parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;cr.accept(cv, parsingOptions);//(5)生成byte[]byte[] bytes2 = cw.toByteArray();FileUtils.writeBytes(filepath, bytes2);}
}
验证&效果

通过javap -p -v HelloWorld命令可以看到版本号信息已从52调整位51

2.给每个方法添加计算调用时间

对目标类进行方法改造调换—为每个方法添加用时计算


public class HelloWorld {public int add(int a, int b) throws InterruptedException {int c = a + b;Random rand = new Random(System.currentTimeMillis());int num = rand.nextInt(300);Thread.sleep(100 + num);return c;}public int sub(int a, int b) throws InterruptedException {int c = a - b;Random rand = new Random(System.currentTimeMillis());int num = rand.nextInt(400);Thread.sleep(100 + num);return c;}
}
ASM编码实现

public class MethodTimerVisitor2 extends ClassVisitor {private String owner;private boolean isInterface;public MethodTimerVisitor2(int api, ClassVisitor classVisitor) {super(api, classVisitor);}@Overridepublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {super.visit(version, access, name, signature, superName, interfaces);owner = name;isInterface = (access & ACC_INTERFACE) != 0;}@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);if (!isInterface && mv != null && !"<init>".equals(name) && !"<clinit>".equals(name)) {boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;boolean isNativeMethod = (access & ACC_NATIVE) != 0;if (!isAbstractMethod && !isNativeMethod) {// 每遇到一个合适的方法,就添加一个相应的字段FieldVisitor fv = super.visitField(ACC_PUBLIC | ACC_STATIC, getFieldName(name), "J", null, null);if (fv != null) {fv.visitEnd();}mv = new MethodTimerAdapter2(api, mv, owner, name);}}return mv;}private String getFieldName(String methodName) {return "timer_" + methodName;}private class MethodTimerAdapter2 extends MethodVisitor {private final String owner;private final String methodName;public MethodTimerAdapter2(int api, MethodVisitor mv, String owner, String methodName) {super(api, mv);this.owner = owner;this.methodName = methodName;}@Overridepublic void visitCode() {// 首先,处理自己的代码逻辑super.visitFieldInsn(GETSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);super.visitInsn(LSUB);super.visitFieldInsn(PUTSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应// 其次,调用父类的方法实现super.visitCode();}@Overridepublic void visitInsn(int opcode) {// 首先,处理自己的代码逻辑if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {super.visitFieldInsn(GETSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);super.visitInsn(LADD);super.visitFieldInsn(PUTSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应}// 其次,调用父类的方法实现super.visitInsn(opcode);}}
}

对方法进行转换


public class HelloWorldTransformCore {private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;public static void main(String[] args) {String relative_path = "sample/HelloWorld.class";String dir = HelloWorldTransformCore.class.getResource("/").getPath();String filepath = dir + relative_path;File file = new File(filepath);try {InputStream in = new FileInputStream(file);in = new BufferedInputStream(in);ByteArrayOutputStream bao = new ByteArrayOutputStream();copyLarge(in, bao, new byte[DEFAULT_BUFFER_SIZE]);byte[] bytes1 = bao.toByteArray();//(1)构建ClassReaderClassReader cr = new ClassReader(bytes1);//(2)构建ClassWriterClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);//(3)串连ClassVisitorint api = Opcodes.ASM9;ClassVisitor cv = new MethodTimerVisitor2(api, cw);//(4)结合ClassReader和ClassVisitorint parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;cr.accept(cv, parsingOptions);//(5)生成byte[]byte[] bytes2 = cw.toByteArray();OutputStream out = new FileOutputStream(filepath);BufferedOutputStream buff = new BufferedOutputStream(out);buff.write(bytes2);buff.flush();buff.close();System.out.println("file://" + filepath);} catch (IOException e) {e.printStackTrace();}}public static long copyLarge(final InputStream input, final OutputStream output, final byte[] buffer)throws IOException {long count = 0;int n;while (-1 != (n = input.read(buffer))) {output.write(buffer, 0, n);count += n;}return count;}
}
验证结果
public class HelloWorldRun {public static void main(String[] args) throws Exception {// 第一部分,先让“子弹飞一会儿”,让程序运行一段时间HelloWorld instance = new HelloWorld();Random rand = new Random(System.currentTimeMillis());for (int i = 0; i < 10; i++) {boolean flag = rand.nextBoolean();int a = rand.nextInt(50);int b = rand.nextInt(50);if (flag) {int c = instance.add(a, b);String line = String.format("%d + %d = %d", a, b, c);System.out.println(line);}else {int c = instance.sub(a, b);String line = String.format("%d - %d = %d", a, b, c);System.out.println(line);}}// 第二部分,来查看方法运行的时间Class<?> clazz = HelloWorld.class;Field[] declaredFields = clazz.getDeclaredFields();for (Field f : declaredFields) {String fieldName = f.getName();f.setAccessible(true);if (fieldName.startsWith("timer")) {Object FieldValue = f.get(null);System.out.println(fieldName + " = " + FieldValue);}}}
}

3、打印方法参数和返回值

对目标类进行方法改造—为每个方法添加打印入参和出参

public class HelloWorld {public int test(String name, int age, long idCard, Object obj) {int hashCode = 0;hashCode += name.hashCode();hashCode += age;hashCode += (int) (idCard % Integer.MAX_VALUE);hashCode += obj.hashCode();return hashCode;}
}

我们想实现的预期目标:打印出“方法接收的参数值”和“方法的返回值”。

public class HelloWorld {public int test(String name, int age, long idCard, Object obj) {int hashCode = 0;hashCode += name.hashCode();hashCode += age;hashCode += (int) (idCard % Integer.MAX_VALUE);hashCode += obj.hashCode();System.out.println(hashCode);return hashCode;}
}

实现这个功能的思路:在“方法进入”的时候,打印出“方法接收的参数值”;在“方法退出”的时候,打印出“方法的返回值”。

首先,我们添加一个ParameterUtils类,在这个类定义了许多print方法,这些print方法可以打印不同类型的数据。

public class ParameterUtils {private static final DateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void printValueOnStack(boolean value) {System.out.println("    " + value);}public static void printValueOnStack(byte value) {System.out.println("    " + value);}public static void printValueOnStack(char value) {System.out.println("    " + value);}public static void printValueOnStack(short value) {System.out.println("    " + value);}public static void printValueOnStack(int value) {System.out.println("    " + value);}public static void printValueOnStack(float value) {System.out.println("    " + value);}public static void printValueOnStack(long value) {System.out.println("    " + value);}public static void printValueOnStack(double value) {System.out.println("    " + value);}public static void printValueOnStack(Object value) {if (value == null) {System.out.println("    " + value);}else if (value instanceof String) {System.out.println("    " + value);}else if (value instanceof Date) {System.out.println("    " + fm.format(value));}else if (value instanceof char[]) {System.out.println("    " + Arrays.toString((char[])value));}else {System.out.println("    " + value.getClass() + ": " + value.toString());}}public static void printText(String str) {System.out.println(str);}
}

在下面的MethodParameterVisitor2类当中,我们将使用ParameterUtils类帮助我们打印信息。

ASM编码实现

public class MethodParameterVisitor2 extends ClassVisitor {public MethodParameterVisitor2(int api, ClassVisitor classVisitor) {super(api, classVisitor);}@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);if (mv != null && !name.equals("<init>")) {boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;boolean isNativeMethod = (access & ACC_NATIVE) != 0;if (!isAbstractMethod && !isNativeMethod) {mv = new MethodParameterAdapter2(api, mv, access, name, descriptor);}}return mv;}private static class MethodParameterAdapter2 extends MethodVisitor {private final int methodAccess;private final String methodName;private final String methodDesc;public MethodParameterAdapter2(int api, MethodVisitor mv, int methodAccess, String methodName, String methodDesc) {super(api, mv);this.methodAccess = methodAccess;this.methodName = methodName;this.methodDesc = methodDesc;}@Overridepublic void visitCode() {// 首先,处理自己的代码逻辑boolean isStatic = ((methodAccess & ACC_STATIC) != 0);int slotIndex = isStatic ? 0 : 1;printMessage("Method Enter: " + methodName + methodDesc);Type methodType = Type.getMethodType(methodDesc);Type[] argumentTypes = methodType.getArgumentTypes();for (Type t : argumentTypes) {int sort = t.getSort();int size = t.getSize();String descriptor = t.getDescriptor();int opcode = t.getOpcode(ILOAD);super.visitVarInsn(opcode, slotIndex);if (sort >= Type.BOOLEAN && sort <= Type.DOUBLE) {String methodDesc = String.format("(%s)V", descriptor);printValueOnStack(methodDesc);}else {printValueOnStack("(Ljava/lang/Object;)V");}slotIndex += size;}// 其次,调用父类的方法实现super.visitCode();}@Overridepublic void visitInsn(int opcode) {// 首先,处理自己的代码逻辑if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {printMessage("Method Exit: " + methodName + methodDesc);if (opcode >= IRETURN && opcode <= DRETURN) {Type methodType = Type.getMethodType(methodDesc);Type returnType = methodType.getReturnType();int size = returnType.getSize();String descriptor = returnType.getDescriptor();if (size == 1) {super.visitInsn(DUP);}else {super.visitInsn(DUP2);}String methodDesc = String.format("(%s)V", descriptor);printValueOnStack(methodDesc);}else if (opcode == ARETURN) {super.visitInsn(DUP);printValueOnStack("(Ljava/lang/Object;)V");}else if (opcode == RETURN) {printMessage("    return void");}else {printMessage("    abnormal return");}}// 其次,调用父类的方法实现super.visitInsn(opcode);}private void printMessage(String str) {super.visitLdcInsn(str);super.visitMethodInsn(INVOKESTATIC, "sample/ParameterUtils", "printText", "(Ljava/lang/String;)V", false);}private void printValueOnStack(String descriptor) {super.visitMethodInsn(INVOKESTATIC, "sample/ParameterUtils", "printValueOnStack", descriptor, false);}}
}

进行转换


public class HelloWorldTransformCore {public static void main(String[] args) {String relative_path = "sample/HelloWorld.class";String filepath = FileUtils.getFilePath(relative_path);byte[] bytes1 = FileUtils.readBytes(filepath);//(1)构建ClassReaderClassReader cr = new ClassReader(bytes1);//(2)构建ClassWriterClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);//(3)串连ClassVisitorint api = Opcodes.ASM9;ClassVisitor cv = new MethodParameterVisitor2(api, cw);//(4)结合ClassReader和ClassVisitorint parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;cr.accept(cv, parsingOptions);//(5)生成byte[]byte[] bytes2 = cw.toByteArray();FileUtils.writeBytes(filepath, bytes2);}
}
验证结果
public class HelloWorldRun {public static void main(String[] args) throws Exception {HelloWorld instance = new HelloWorld();int hashCode = instance.test("Tomcat", 10, System.currentTimeMillis(), new Object());int remainder = hashCode % 2;if (remainder == 0) {System.out.println("hashCode is even number.");}else {System.out.println("hashCode is odd number.");}}
}

四、非顺序结构

1、if语句

public class HelloWorld {public void test(int value) {if (value == 0) {System.out.println("value is 0");}else {System.out.println("value is not 0");}}
}
ASM编码实现

public class HelloWorldGenerateCore {public static void main(String[] args) throws Exception {String relative_path = "sample/HelloWorld.class";String filepath = FileUtils.getFilePath(relative_path);// (1) 生成byte[]内容byte[] bytes = dump();// (2) 保存byte[]到文件FileUtils.writeBytes(filepath, bytes);}public static byte[] dump() throws Exception {// (1) 创建ClassWriter对象ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);// (2) 调用visitXxx()方法cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",null, "java/lang/Object", null);{MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);mv1.visitCode();mv1.visitVarInsn(ALOAD, 0);mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);mv1.visitInsn(RETURN);mv1.visitMaxs(0, 0);mv1.visitEnd();}{MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(I)V", null, null);Label elseLabel = new Label();Label returnLabel = new Label();// 第1段mv2.visitCode();mv2.visitVarInsn(ILOAD, 1);mv2.visitJumpInsn(IFNE, elseLabel);mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitLdcInsn("value is 0");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);mv2.visitJumpInsn(GOTO, returnLabel);// 第2段mv2.visitLabel(elseLabel);mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitLdcInsn("value is not 0");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);// 第3段mv2.visitLabel(returnLabel);mv2.visitInsn(RETURN);mv2.visitMaxs(0, 0);mv2.visitEnd();}cw.visitEnd();// (3) 调用toByteArray()方法return cw.toByteArray();}
}
验证结果

public class HelloWorldRun {public static void main(String[] args) throws Exception {Class<?> clazz = Class.forName("sample.HelloWorld");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("test", int.class);method.invoke(obj, 0);method.invoke(obj, 1);}
}

2、switch语句

public class HelloWorld {public void test(int val) {switch (val) {case 1:System.out.println("val = 1");break;case 2:System.out.println("val = 2");break;case 3:System.out.println("val = 3");break;case 4:System.out.println("val = 4");break;default:System.out.println("val is unknown");}}
}

ASM编码实现


public class HelloWorldGenerateCore {public static void main(String[] args) throws Exception {String relative_path = "sample/HelloWorld.class";String filepath = FileUtils.getFilePath(relative_path);// (1) 生成byte[]内容byte[] bytes = dump();// (2) 保存byte[]到文件FileUtils.writeBytes(filepath, bytes);}public static byte[] dump() throws Exception {// (1) 创建ClassWriter对象ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);// (2) 调用visitXxx()方法cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",null, "java/lang/Object", null);{MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);mv1.visitCode();mv1.visitVarInsn(ALOAD, 0);mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);mv1.visitInsn(RETURN);mv1.visitMaxs(0, 0);mv1.visitEnd();}{MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(I)V", null, null);Label caseLabel1 = new Label();Label caseLabel2 = new Label();Label caseLabel3 = new Label();Label caseLabel4 = new Label();Label defaultLabel = new Label();Label returnLabel = new Label();// 第1段mv2.visitCode();mv2.visitVarInsn(ILOAD, 1);mv2.visitTableSwitchInsn(1, 4, defaultLabel, new Label[]{caseLabel1, caseLabel2, caseLabel3, caseLabel4});// 第2段mv2.visitLabel(caseLabel1);mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitLdcInsn("val = 1");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);mv2.visitJumpInsn(GOTO, returnLabel);// 第3段mv2.visitLabel(caseLabel2);mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitLdcInsn("val = 2");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);mv2.visitJumpInsn(GOTO, returnLabel);// 第4段mv2.visitLabel(caseLabel3);mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitLdcInsn("val = 3");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);mv2.visitJumpInsn(GOTO, returnLabel);// 第5段mv2.visitLabel(caseLabel4);mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitLdcInsn("val = 4");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);mv2.visitJumpInsn(GOTO, returnLabel);// 第6段mv2.visitLabel(defaultLabel);mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitLdcInsn("val is unknown");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);// 第7段mv2.visitLabel(returnLabel);mv2.visitInsn(RETURN);mv2.visitMaxs(0, 0);mv2.visitEnd();}cw.visitEnd();// (3) 调用toByteArray()方法return cw.toByteArray();}
}

验证结果

import java.lang.reflect.Method;public class HelloWorldRun {public static void main(String[] args) throws Exception {Class<?> clazz = Class.forName("sample.HelloWorld");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("test", int.class);for (int i = 1; i < 6; i++) {method.invoke(obj, i);}}
}

3、for语句

public class HelloWorld {public void test() {for (int i = 0; i < 10; i++) {System.out.println(i);}}
}

ASM编码实现

import org.objectweb.asm.*;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore {public static void main(String[] args) throws Exception {String relative_path = "sample/HelloWorld.class";String filepath = FileUtils.getFilePath(relative_path);// (1) 生成byte[]内容byte[] bytes = dump();// (2) 保存byte[]到文件FileUtils.writeBytes(filepath, bytes);}public static byte[] dump() throws Exception {// (1) 创建ClassWriter对象ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);// (2) 调用visitXxx()方法cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",null, "java/lang/Object", null);{MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);mv1.visitCode();mv1.visitVarInsn(ALOAD, 0);mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);mv1.visitInsn(RETURN);mv1.visitMaxs(0, 0);mv1.visitEnd();}{MethodVisitor methodVisitor = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);Label conditionLabel = new Label();Label returnLabel = new Label();// 第1段methodVisitor.visitCode();methodVisitor.visitInsn(ICONST_0);methodVisitor.visitVarInsn(ISTORE, 1);// 第2段methodVisitor.visitLabel(conditionLabel);methodVisitor.visitVarInsn(ILOAD, 1);methodVisitor.visitIntInsn(BIPUSH, 10);methodVisitor.visitJumpInsn(IF_ICMPGE, returnLabel);methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");methodVisitor.visitVarInsn(ILOAD, 1);methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);methodVisitor.visitIincInsn(1, 1);methodVisitor.visitJumpInsn(GOTO, conditionLabel);// 第3段methodVisitor.visitLabel(returnLabel);methodVisitor.visitInsn(RETURN);methodVisitor.visitMaxs(0, 0);methodVisitor.visitEnd();}cw.visitEnd();// (3) 调用toByteArray()方法return cw.toByteArray();}
}

验证结果

public class HelloWorldRun {public static void main(String[] args) throws Exception {Class<?> clazz = Class.forName("sample.HelloWorld");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("test");method.invoke(obj);}
}

4、try-catch语句

public class HelloWorld {public void test() {try {System.out.println("Before Sleep");Thread.sleep(1000);System.out.println("After Sleep");} catch (InterruptedException e) {e.printStackTrace();}}
}

ASM编码实现

public class HelloWorldGenerateCore {public static void main(String[] args) throws Exception {String relative_path = "sample/HelloWorld.class";String filepath = FileUtils.getFilePath(relative_path);// (1) 生成byte[]内容byte[] bytes = dump();// (2) 保存byte[]到文件FileUtils.writeBytes(filepath, bytes);}public static byte[] dump() throws Exception {// (1) 创建ClassWriter对象ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);// (2) 调用visitXxx()方法cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",null, "java/lang/Object", null);{MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);mv1.visitCode();mv1.visitVarInsn(ALOAD, 0);mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);mv1.visitInsn(RETURN);mv1.visitMaxs(0, 0);mv1.visitEnd();}{MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);Label startLabel = new Label();Label endLabel = new Label();Label exceptionHandlerLabel = new Label();Label returnLabel = new Label();// 第1段mv2.visitCode();// visitTryCatchBlock可以在这里访问mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandlerLabel, "java/lang/InterruptedException");// 第2段mv2.visitLabel(startLabel);mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitLdcInsn("Before Sleep");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);mv2.visitLdcInsn(new Long(1000L));mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv2.visitLdcInsn("After Sleep");mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);// 第3段mv2.visitLabel(endLabel);mv2.visitJumpInsn(GOTO, returnLabel);// 第4段mv2.visitLabel(exceptionHandlerLabel);mv2.visitVarInsn(ASTORE, 1);mv2.visitVarInsn(ALOAD, 1);mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/InterruptedException", "printStackTrace", "()V", false);// 第5段mv2.visitLabel(returnLabel);mv2.visitInsn(RETURN);// 第6段// visitTryCatchBlock也可以在这里访问// mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandlerLabel, "java/lang/InterruptedException");mv2.visitMaxs(0, 0);mv2.visitEnd();}cw.visitEnd();// (3) 调用toByteArray()方法return cw.toByteArray();}
}

验证结果

public class HelloWorldRun {public static void main(String[] args) throws Exception {Class<?> clazz = Class.forName("sample.HelloWorld");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("test");method.invoke(obj);}
}

五、查看class文件的ASM代码

1.打印

当我们想对某一类进行ASM学习或者对想要实现的功能不知道如何实现时可以使用如下类进行ASM代码输出并查看

public class ASMPrint {public static void main(String[] args) throws IOException {// (1) 设置参数String className = "sample.HelloWorld";int parsingOptions = ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG;boolean asmCode = true;// (2) 打印结果Printer printer = asmCode ? new ASMifier() : new Textifier();PrintWriter printWriter = new PrintWriter(System.out, true);TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, printer, printWriter);new ClassReader(className).accept(traceClassVisitor, parsingOptions);}
}

className值设置为类的全限定名,可以是我们自己写的类,例如sample.HelloWorld,也可以是JDK自带的类,例如java.lang.Comparable。

asmCode值设置为true或false。如果是true,可以打印出对应的ASM代码;如果是false,可以打印出方法对应的Instruction。

parsingOptions值设置为ClassReader.SKIP_CODE、ClassReader.SKIP_DEBUG、ClassReader.SKIP_FRAMES、ClassReader.EXPAND_FRAMES的组合值,也可以设置为0,可以打印出详细程度不同的信息。

2.插件

如果你是IDEA可以安装ASM ByteCode Viewer,然后选择要查看得java文件右键选择后可以在右侧看到ASPPlugin上看到对应得ByteCode、ASMified、Groovified。或者是插件ASM Bytecode Outline(本人用得社区版IDEA未达到效果)

六、TreeApi

Core API和Tree API的区别

  • Tree API的优势:易用性:如果一个人在之前并没有接触过Core API和Tree API,那么Tree API更容易入手。功能性:在实现比较复杂的功能时,Tree API比Core API更容易实现。
  • Core API的优势:执行效率:在实现相同功能的前提下,Core API要比Tree API执行效率高,花费时间少。内存使用:Core API比Tree API占用的内存空间少。
  • 第一点,在ASM当中,不管是Core API,还是Tree API,都能够进行Class Generation、Class Transformation和Class Analysis操作。
  • 第二点,Core API和Tree API是两者有各自的优势。Tree API易于使用、更容易实现复杂的操作;Core API执行速度更快、占用内存空间更少。

这里由于篇幅限制做了删减,如果有兴趣可以联系作者进行交流

七、文中用到的工具类

1、FileUtils

public class FileUtils {public static String getFilePath(String relativePath) {String dir = FileUtils.class.getResource("/").getPath();return dir + relativePath;}
}

八、思考对于ASM我们以后能用于做些什么?

1、生成类----根据模版生成类(结合脚手架快速搭建项目以及项目初期模块快速搭建)

2、修改类----根据模版修改类(结合特定结构在java源代码编译生成.class字节码文件时对类进行修改以达到提升系统效率和释放开发人力的目的,可以结合IDEA插件开发开发类似于lombok或更为强大的插件)

作者:京东健康 马仁喜

来源:京东云开发者社区 转载请注明来源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/158472.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

每日一练:X加上100为完全平方数,再加上168仍然为完全平方数

题目 一个整数&#xff0c;它加上100后是一个完全平方数&#xff0c;再加上168又是一个完全平方数&#xff0c;请问该数是多少&#xff1f; 实现方式1 解题思路 设整数为x&#xff0c;根据题意建立方程&#xff1a;   (1) x 100 是一个完全平方数&#xff0c;即存在整数a满…

技术分享| gcc版本升级到5.2

一、介绍 GCC&#xff08;GNU Compiler Collection&#xff09;是一套广泛使用的开源编译器集合&#xff0c;用于编译多种编程语言&#xff0c;包括C、C、Objective-C、Fortran等。GCC 的不同版本提供了许多新功能、改进和修复&#xff0c;其中包括从 GCC 4.8.5 升级到 GCC 5.…

泛型边界的问题

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 我们花了两篇文章讲述了…

常见树种(贵州省):007青冈

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、青冈 …

AI一点通:卷积神经网络的输出节点大小如何计算?全连接层必要输入大小如何设置

在使用卷积网络&#xff08;CNN&#xff09;时&#xff0c;一个步骤是计算经过卷积和池化步骤后的输出大小&#xff0c;以便我们可以将输出连接到一个完全收集的线性层。 以Pytorch中的一维CNN为例&#xff0c; self.conv1 nn.Conv1d(in_channels1, out_channels64, kernel_s…

CPSC发布关于亚马逊含有纽扣电池或硬币电池产品的相关规则标准!UL4200A

2023年9月21日&#xff0c;美国消费品安全委员会&#xff08;CPSC&#xff09;在《联邦公报》上发布了纽扣及硬币电池及相关产品的最终规则&#xff08;DFR&#xff09;16 CFR 1263&#xff0c;以保护6岁以下儿童免受电池摄入危害。DFR将于2023年10月23日生效&#xff0c;除非消…

Git 教程

目录 Git 与 SVN 区别 Git 快速入门 学习目录 git简明指南 Git 安装配置 Git 工作流程、工作区、暂存区和版本库 Git 创建仓库 Git 基本操作 Git 分支管理 Git 查看提交历史 Git 标签 Git 远程仓库(Github) Git 服务器搭建 Git 是一个开源的分布式版本控…

如何写老客户开发信?维护客户邮件怎么写?

老客户开发信的写作技巧&#xff1f;针对老客户的营销邮件模板&#xff1f; 老客户开发信是维护和发展客户关系的关键工具之一。通过巧妙的信函&#xff0c;您可以巩固与老客户之间的联系&#xff0c;促使他们继续购买您的产品或服务。在本文中&#xff0c;蜂邮将分享一些建议…

小程序Tab栏与页面滚动联动

小程序tab栏切换与页面滚动联动 tab栏与页面滚动联动点击tab栏页面跳到指定位置滚动页面时切换tab栏 tab栏与页面滚动联动 在进行小程序开发时&#xff0c;需要实现点击tab栏页面滚动到某一指定位置&#xff0c;并且滚动页面时&#xff0c;小程序的tab栏进行切换。 在一开始&a…

黑苹果新手指导:名词解释常用软件常见问题说明

黑苹果新手指导&#xff1a;名词解释&常用软件&常见问题说明 写在前面名词解释系统篇引导篇工具篇 常见问题安装篇如何安装黑苹果&#xff1f;安装过程中卡在一排号怎么办&#xff1f;AMD处理器可以安装黑苹果 macOS吗&#xff1f;我的笔记本电脑为什么不能驱动独立显卡…

uni-app - 日期 · 时间选择器

目录 1.基本介绍 2.案例介绍 ①注意事项&#xff1a; ②效果展示 3.代码展示 ①view部分 ②js部分 ③css样式 1.基本介绍 从底部弹起的滚动选择器。支持五种选择器&#xff0c;通过mode来区分&#xff0c;分别是普通选择器&#xff0c;多列选择器&#xff0c;时间选择器&a…

API之 要求接口上传pdf 以 合同PDF的二进制数据,multpart方式上传

实现 //时间戳13位毫秒private function getMillisecond() {list($s1,$s2) explode( ,microtime());return (float)sprintf(%.0f,(floatval($s1) floatval($s2)) * 1000);}// 组装参数private function gysscPost1($url,$data){// $data[timestamp] 1694402111964;$data[tim…

怎么批量提取文件名字到Excel中?

怎么批量提取文件名字到Excel中&#xff1f;Excel是由微软公司开发的一种电子表格软件&#xff0c;它是Microsoft Office办公套件的一部分。Excel提供了强大的数据处理和分析功能&#xff0c;用户可以使用Excel创建、编辑和管理电子表格&#xff0c;进行各种计算、数据分析、图…

【C++ 学习 ㊴】- 详解 C++ 的 I/O 流

目录 一、C 的 I/O 流 二、C 的标准 I/O 流 三、C 的文件 I/O 流 一、C 的 I/O 流 C 语言有一套完成数据读写&#xff08;I/O&#xff09;的解决方案&#xff1a; 使用 scanf()、gets() 等函数从键盘读取数据&#xff0c;使用 printf()、puts() 等函数向屏幕输出数据&#…

909-2014-T2

文章目录 1.原题2.算法思想3.关键代码4.完整代码5.运行结果 1.原题 二叉树采用二叉链表存储结构&#xff0c;设计算法&#xff0c;判断二叉树是否为满二叉树。叙述算法思想并给出算法实现。 2.算法思想 通过一次遍历&#xff0c;得到结点个数和树的高度。用结点个数和树的高…

全国市政公用事业和邮政、电信业发展数据,shp/excel格式

随着城市化进程的加速和人们对城市生活品质要求的提高&#xff0c;市政公用事业和邮政、电信业发展越来越受到关注。 今天我们来分享全国市政公用事业和邮政、电信业发展数据&#xff0c;为读者呈现一个更加全面的行业发展图景。 首先了解下数据的基本信息&#xff0c;格式为s…

2023亿发数字化智能工单,专业管理工单处理全流程,助力企业转型腾飞

伴随着智能化和信息化的不断深入&#xff0c;企业数字化转型势如腾飞。在这个过程中&#xff0c;工单管理成为生产、家电、后勤等多个管理场景下频繁应用的关键环节。如何满足管理方对设备、服务等智能化管理的需求&#xff0c;提升工单管理效率、规范管理流程&#xff0c;并实…

Fiddler模拟弱网环境

1.设置弱网&#xff1a;Rules-》Customize Rules 上传速度&#xff1a;1KB/300ms1KB/0.3s3.33KB/s 下载速度&#xff1a;1KB/150ms1KB/0.15s6.67KB/s 2.启动弱网&#xff1a;Rules-》Performance-》Simulate Modem Speeds 开启后&#xff0c;此项为勾选状态 3.验证弱网生效…

光量子计算再创融资高峰!法国 Quandela获投5000万欧元

​&#xff08;图片来源&#xff1a;网络&#xff09; 法国光量子计算公司Quandela致力于开发首台光量子计算机&#xff0c;目前已获得超过5,000万欧元的巨额融资。投资者包括通过“法国2030计划”获得的法国政府支持以及银行合作伙伴、个人。新的投资者包括法国投资公司Seren…