ASM系列之一:初探ASM

一、什么是ASM

ASM是一个JAVA字节码分析、创建和修改的开源应用框架。在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和灵活的操作字节码的方式。目前ASM已被广泛的开源应用架构所使用,例如:Spring、Hibernate等。

二、ASM能干什么

分析一个类、从字节码角度创建一个类、修改一个已经被编译过的类文件

三、ASM初探例子

这里我们使用ASM的CoreAPI(ASM提供了两组API:Core和Tree,Core是基于访问者模式来操作类的,而Tree是基于树节点来操作类的)创建一个MyClass类,目标类如下:

public class MyClass {  private String name;  public MyClass(){  this.name = "zhangzhuo";  }  public String getName() {  return name;  }  public void setName(String name) {  this.name = name;  }  
} 

这个类在构造方法中初始化了属性name,并提供了两个public方法来修改和访问name属性。

接下来就要书写创建这个类的代码了,现将代码给出,然后逐步解释,代码如下:

代码1:

public class GenerateClass {  public void generateClass() {  //方法的栈长度和本地变量表长度用户自己计算  ClassWriter classWriter = new ClassWriter(0);     //Opcodes.V1_6指定类的版本  //Opcodes.ACC_PUBLIC表示这个类是public,  //“org/victorzhzh/core/classes/MyClass”类的全限定名称  //第一个null位置变量定义的是泛型签名,  //“java/lang/Object”这个类的父类  //第二个null位子的变量定义的是这个类实现的接口  
classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC,  "org/victorzhzh/core/classes/MyClass", null,  "java/lang/Object", null);  ClassAdapter classAdapter = new MyClassAdapter(classWriter);  classAdapter.visitField(Opcodes.ACC_PRIVATE, "name",  Type.getDescriptor(String.class), null, null);//定义name属性  classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null,  null).visitCode();//定义构造方法  String setMethodDesc = "(" + Type.getDescriptor(String.class) + ")V";  classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "setName", setMethodDesc,  null, null).visitCode();//定义setName方法  String getMethodDesc = "()" + Type.getDescriptor(String.class);  classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "getName", getMethodDesc,  null, null).visitCode();//定义getName方法  byte[] classFile = classWriter.toByteArray();//生成字节码  MyClassLoader classLoader = new MyClassLoader();//定义一个类加载器  Class clazz = classLoader.defineClassFromClassFile(  "org.victorzhzh.core.classes.MyClass", classFile);  try {//利用反射方式,访问getName  Object obj = clazz.newInstance();  Method method = clazz.getMethod("getName");  System.out.println(obj.toString());  System.out.println(method.invoke(obj, null));  } catch (Exception e) {  e.printStackTrace();  }  }  class MyClassLoader extends ClassLoader {  public Class defineClassFromClassFile(String className, byte[] classFile)  throws ClassFormatError {  return defineClass(className, classFile, 0, classFile.length);  }  }  public static void main(String[] args) {  GenerateClass generateClass = new GenerateClass();  generateClass.generateClass();  }  
} 

   代码2:

public class MyClassAdapter extends ClassAdapter {  public MyClassAdapter(ClassVisitor cv) {  super(cv);  }  @Override  public MethodVisitor visitMethod(int access, String name, String desc,  String signature, String[] exceptions) {  MethodVisitor methodVisitor = cv.visitMethod(access, name, desc,  signature, exceptions);  if (name.equals("<init>")) {  return new InitMethodAdapter(methodVisitor);  } else if (name.equals("setName")) {  return new SetMethodAdapter(methodVisitor);  } else if (name.equals("getName")) {  return new GetMethodAdapter(methodVisitor);  } else {  return super.visitMethod(access, name, desc, signature, exceptions);  }  }  //这个类生成具体的构造方法字节码  class InitMethodAdapter extends MethodAdapter {  public InitMethodAdapter(MethodVisitor mv) {  super(mv);  }  @Override  public void visitCode() {  mv.visitVarInsn(Opcodes.ALOAD, 0);  mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",  "<init>", "()V");//调用父类的构造方法  mv.visitVarInsn(Opcodes.ALOAD, 0);  mv.visitLdcInsn("zhangzhuo");//将常量池中的字符串常量加载刀栈顶  mv.visitFieldInsn(Opcodes.PUTFIELD,  "org/victorzhzh/core/classes/MyClass", "name",  Type.getDescriptor(String.class));//对name属性赋值  mv.visitInsn(Opcodes.RETURN);//设置返回值  mv.visitMaxs(2, 1);//设置方法的栈和本地变量表的大小  }  };  //这个类生成具体的setName方法字节码    class SetMethodAdapter extends MethodAdapter {  public SetMethodAdapter(MethodVisitor mv) {  super(mv);  }  @Override  public void visitCode() {  mv.visitVarInsn(Opcodes.ALOAD, 0);  mv.visitVarInsn(Opcodes.ALOAD, 1);  mv.visitFieldInsn(Opcodes.PUTFIELD,  "org/victorzhzh/core/classes/MyClass", "name",  Type.getDescriptor(String.class));  mv.visitInsn(Opcodes.RETURN);  mv.visitMaxs(2, 2);  }  }  //这个类生成具体的getName方法字节  class GetMethodAdapter extends MethodAdapter {  public GetMethodAdapter(MethodVisitor mv) {  super(mv);  }  @Override  public void visitCode() {  mv.visitVarInsn(Opcodes.ALOAD, 0);  mv.visitFieldInsn(Opcodes.GETFIELD,  "org/victorzhzh/core/classes/MyClass", "name",  Type.getDescriptor(String.class));//获取name属性的值  mv.visitInsn(Opcodes.ARETURN);//返回一个引用,这里是String的引用即name  mv.visitMaxs(1, 1);  }  }  
} 

   运行结果:

org.victorzhzh.core.classes.MyClass@1270b73  
zhangzhuo 

   这个例子只是简单地介绍了一下ASM如何创建一个类,接下来的几个章节,将详细介绍ASM的CoreAPI和TreeAPI中如何操作类。


ASM系列之二:Java类的基本表述

上一篇文章中我们看到了如何使用ASM生成一个简单的JAVA类,里面使用到了很多的基本概念,比如:方法描述、引用描述等,下面将一一介绍。

一、类版本:

一个Java二进制的类文件,都有一个版本,因此ASM中提供了几个常量来指定一个类的版,这些常量定义在org.objectweb.asm.Opcodes接口中,如下:

int V1_1 = 3 << 16 | 45;  
int V1_2 = 0 << 16 | 46;  
int V1_3 = 0 << 16 | 47;  
int V1_4 = 0 << 16 | 48;  
int V1_5 = 0 << 16 | 49;  
int V1_6 = 0 << 16 | 50;  
int V1_7 = 0 << 16 | 51; 

二、内部名字:

在Java二进制文件中使用的是JVM的内部名字,而不是我们所熟悉的以“.”分割的全限定名,内部名字是以“/”替代“.”的全名,例如:java.lang.String在JVM中的内部名字是java/lang/String。在ASM中可以使用org.objectweb.asm.Type类中的静态方法getInternalName(final Class c) 来获得,如下:

public class InternalNameTransform {  public static void main(String[] args) {  System.out.println(Type.getInternalName(String.class));  System.out.println(Type.getInternalName(Integer.class));  System.out.println(Type.getInternalName(InternalNameTransform.class));  }  
} 

    运行结果:

java/lang/String  
java/lang/Integer  
org/victorzhzh/core/structure/InternalNameTransform 

三、类型描述:

我们知道JAVA类型分为基本类型和引用类型,在JVM中对每一种类型都有与之相对应的类型描述,如下表:

Java类型 JVM中的描述
boolean Z
char C
byte B
short S
int I
float F
long J
double D
Object Ljava/lang/Object;
int [I
Object [[Ljava/lang/Object;

在ASM中要获得一个类的JVM内部描述,可以使用org.objectweb.asm.Type类中的getDescriptor(final Class c)方法,如下:

public class TypeDescriptors {  public static void main(String[] args) {  System.out.println(Type.getDescriptor(TypeDescriptors.class));  System.out.println(Type.getDescriptor(String.class));  }  }  

    运行结果:

Lorg/victorzhzh/core/structure/TypeDescriptors;  
Ljava/lang/String;  

四、方法描述:

在Java的二进制文件中,方法的方法名和方法的描述都是存储在Constant pool中的,且在两个不同的单元里。因此,方法描述中不含有方法名,只含有参数类型和返回类型,如下:

方法描述,在类中的 方法描述,在二进制文件中的
void a(int i,float f) (IF)V
void a(Object o) (Ljava/lang/Object;)V
int a(int i,String s) (ILjava/lang/String;)I
int[] a(int[] i) ([I)[I
String a() ()Ljava/lang/String;

获取一个方法的描述可以使用org.objectweb.asm.Type.getMethodDescriptor方法,如下:

public class MethodDescriptors {  public static void main(String[] args) throws Exception {  Method m = String.class.getMethod("substring", int.class);  System.out.println(Type.getMethodDescriptor(m));  }  } 

    运行结果:

(I)Ljava/lang/String;  

 其实在org.objectweb.asm.Type类中提供了很多方法让我们去了解一个类,有兴趣的可以看一下它的源码,这对我们了解一个类和操作一个类还是有大帮助的。


ASM系列之三:ASM中的访问者模式

在ASM的Core API中使用的是访问者模式来实现对类的操作,主要包含如下类:

一、ClassVisitor接口:

在这个接口中主要提供了和类结构同名的一些方法,这些方法可以对相应的类结构进行操作。如下:

public interface ClassVisitor {  void visit(int version,int access,String name,String signature,String superName,String[] interfaces);  void visitSource(String source, String debug);  void visitOuterClass(String owner, String name, String desc);  AnnotationVisitor visitAnnotation(String desc, boolean visible);  void visitAttribute(Attribute attr);  void visitInnerClass(String name,String outerName,String innerName,int access);  FieldVisitor visitField(int access,String name,String desc,String signature,Object value);  MethodVisitor visitMethod(int access,String name,String desc,String signature,String[] exceptions);  void visitEnd();  
} 

这里定义的方法调用是有顺序的,在ClassVisitor中定义了调用的顺序和每个方法在可以出现的次数,如下:

visit [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* (visitInnerClass | visitField | visitMethod )* visitEnd。

二、ClassReader类:

这个类会提供你要转变的类的字节数组,它的accept方法,接受一个具体的ClassVisitor,并调用实现中具体的visit,

visitSource, visitOuterClass, visitAnnotation, visitAttribute, visitInnerClass,visitField, visitMethod和 visitEnd方法。

三、ClassWriter类:

这个类是ClassVisitor的一个实现类,这个类中的toByteArray方法会将最终修改的字节码以byte数组形式返回,在这个类的构造时可以指定让系统自动为我们计算栈和本地变量的大小(COMPUTE_MAXS),也可以指定系统自动为我们计算栈帧的大小(COMPUTE_FRAMES)。

四、ClassAdapter类:

这个类也是ClassVisitor的一个实现类,这个类可以看成是一个事件过滤器,在这个类里,它对ClassVisitor的实现都是委派给一个具体的ClassVisitor实现类,即调用那个实现类实现的方法。

五、AnnotationVisitor接口:

这个接口中定义了和Annotation结构想对应的方法,这些方法可以操作Annotation中的定义,如下:

public interface AnnotationVisitor {  void visit(String name, Object value);  void visitEnum(String name, String desc, String value);  AnnotationVisitor visitAnnotation(String name, String desc);  AnnotationVisitor visitArray(String name);  void visitEnd();  
} 

调用顺序如下:

(visit | visitEnum | visitAnnotation | visitArray)* visitEnd

六、FieldVisitor接口:

这个接口定义了和属性结构相对应的方法,这些方法可以操作属性,如下:

public interface FieldVisitor {  AnnotationVisitor visitAnnotation(String desc, boolean visible);  void visitAttribute(Attribute attr);  void visitEnd();  
}  

调用顺序:

visitAnnotation | visitAttribute )* visitEnd .

七、MethodVisitor接口:

这个接口定义了和方法结构相对应的方法,这些方法可以去操作源方法,具体的可以查看一下源码。

八、操作流程:

一般情况下,我们需要操作一个类时,首先是获得其二进制的字节码,即用ClassReader来读取一个类,然后需要一个能将二进制字节码写回的类,即用ClassWriter类,最后就是一个事件过滤器,即ClassAdapter。事件过滤器中的某些方法可以产生一个新的XXXVisitor对象,当我们需要修改对应的内容时只要实现自己的XXXVisitor并返回就可以了。

九、例子:

在这个例子中,我们将对Person类的sayName方法做出一些修改,源类:

public class Person {  private String name;  public void sayName() {  System.out.println(name);  }  
}

  如果我们定义一个Person类然后调用其sayName()方法将会得到的是一个null,行成的二进制字节码如下:

public void sayName();  Code:  Stack=2, Locals=1, Args_size=1  0: getstatic   #17; //Field java/lang/System.out:Ljava/io/PrintStream;  3:   aload_0  4:   getfield    #23; //Field name:Ljava/lang/String;  7:   invokevirtual   #25; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  10:  return 
}

 我们修改一下这个方法,让它输出"zhangzhuo",代码如下:

public class GenerateNewPerson {  public static void main(String[] args) throws Exception {  // 使用全限定名,创建一个ClassReader对象  ClassReader classReader = new ClassReader(  "org.victorzhzh.core.ic.Person");  // 构建一个ClassWriter对象,并设置让系统自动计算栈和本地变量大小  ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);  ClassAdapter classAdapter = new GeneralClassAdapter(classWriter);  classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);  byte[] classFile = classWriter.toByteArray();  // 将这个类输出到原先的类文件目录下,这是原先的类文件已经被修改  File file = new File(  "target/classes/org/victorzhzh/core/ic/Person.class");  FileOutputStream stream = new FileOutputStream(file);  stream.write(classFile);  stream.close();  }  
}  public class GeneralClassAdapter extends ClassAdapter {  public GeneralClassAdapter(ClassVisitor cv) {  super(cv);  }  @Override  public MethodVisitor visitMethod(int access, String name, String desc,  String signature, String[] exceptions) {  MethodVisitor mv = cv.visitMethod(access, name, desc, signature,  exceptions);  // 当是sayName方法是做对应的修改  if (name.equals("sayName")) {  MethodVisitor newMv = new SayNameMethodAdapter(mv);  return newMv;  } else {  return mv;  }  }  // 定义一个自己的方法访问类  class SayNameMethodAdapter extends MethodAdapter {  public SayNameMethodAdapter(MethodVisitor mv) {  super(mv);  }  // 在源方法前去修改方法内容,这部分的修改将加载源方法的字节码之前  @Override  public void visitCode() {  // 记载隐含的this对象,这是每个JAVA方法都有的  mv.visitVarInsn(Opcodes.ALOAD, 0);  // 从常量池中加载“zhangzhuo”字符到栈顶  mv.visitLdcInsn("zhangzhuo");  // 将栈顶的"zhangzhuo"赋值给name属性  mv.visitFieldInsn(Opcodes.PUTFIELD,  Type.getInternalName(Person.class), "name",  Type.getDescriptor(String.class));  }  }  } 

 这时,我们在查看一下Person的字节码:

public void sayName();  Code:  Stack=2, Locals=1, Args_size=1  0:   aload_0  1:   ldc #13; //String zhangzhuo  3:   putfield    #15; //Field name:Ljava/lang/String;  
=============以上是我们新增加的内容================================  6:   getstatic   #21; //Field java/lang/System.out:Ljava/io/PrintStream;  9:   aload_0  10:  getfield    #15; //Field name:Ljava/lang/String;  13:  invokevirtual   #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  16:  return  } 

再次调用Person对象,输出结果为:zhangzhuo


ASM系列之四:操作类属性

在上一篇文章中,我们看到了ASM中的Core API中使用的是XXXVisitor操作类中的对应部分。本文将展示如何使用ASM中的Core API对类的属性的操作。

首先,我们定义一个原类Person,如下:

public class Person {  public String name = "zhangzhuo";  public String address = "xxxxx" ;  
} 

这里,我们将属性定义为public类型,目的是为了我们使用反射去调用这个属性,接下来我们要为这个类添加一个int类型的属性,名字叫age。

第一个问题,ASM的Core API允许我们在那些方法中来添加属性?

在ASM的Core API中你要为类添加属性就必须要自己去实现ClassVisitor这个接口,这个接口中的visitInnerClass、visitField、visitMethod和visitEnd方法允许我们进行添加一个类属性操作,其余的方法是不允许的。这里我们依然使用Core API中的ClassAdapter类,我们继承这个类,定义一个去添加属性的类,ClassAdapter实现了ClassVisitor。

第二个问题,我们要在这些方法中写什么样的代码才能添加一个属性?

在使用ASM的Core API添加一个属性时只需要调用一句语句就可以,如下:

classVisitor.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),  null, null); 

第一个参数指定的是这个属性的操作权限,第二个参数是属性名,第三个参数是类型描述,第四个参数是泛型类型,第五个参数是初始化的值,这里需要注意一下的是第五个参数,这个参数只有属性为static时才有效,也就是数只有为static时,这个值才真正会赋值给我们添加的属性上,对于非static属性,它将被忽略。

好了,让我们看一段代码,在visitEnd去添加一个名字为age的属性:

public class Transform extends ClassAdapter {  public Transform(ClassVisitor cv) {  super(cv);  }  @Override  public void visitEnd() {  cv.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),  null, null);  }  } 

 非常简单吧,只要一句话就可以添加一个属性到我们的类中,看一下我们的测试类:

public class TransformTest {  @Test  public void addAge() throws Exception {  ClassReader classReader = new ClassReader(  "org.victorzhzh.core.field.Person");  ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);  ClassAdapter classAdapter = new Transform(classWriter);  classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);  byte[] classFile = classWriter.toByteArray();  GeneratorClassLoader classLoader = new GeneratorClassLoader();  Class clazz = classLoader.defineClassFromClassFile(  "org.victorzhzh.core.field.Person", classFile);  Object obj = clazz.newInstance();  System.out.println(clazz.getDeclaredField("name").get(obj));//----(1)  System.out.println(clazz.getDeclaredField("age").get(obj));//----(2)  }  
}  

 在这里,如果我们的age没有被添加进去那么(2)这个地方将会报错,看一下结果:

zhangzhuo  
0 

int类型在没有被初始化时,默认值为0,而第二行输出0,说明我们添加了一个属性age

接下来,我们在visitField方法中在次添加age属性,如下:

public class Transform extends ClassAdapter {  public Transform(ClassVisitor cv) {  super(cv);  }  @Override  public FieldVisitor visitField(int access, String name, String desc,  String signature, Object value) {  cv.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),  null, null);  return super.visitField(access, name, desc, signature, value);  }  }  

 这时,我们再次运行测试类,结果如下:

java.lang.ClassFormatError: Duplicate field name&signature in class file org/victorzhzh/core/field/Person  at java.lang.ClassLoader.defineClass1(Native Method)  at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)  at java.lang.ClassLoader.defineClass(ClassLoader.java:616)  at java.lang.ClassLoader.defineClass(ClassLoader.java:466)  at org.victorzhzh.common.GeneratorClassLoader.defineClassFromClassFile(GeneratorClassLoader.java:14)  at org.victorzhzh.core.field.TransformTest.addAge(TransformTest.java:22)  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)  at java.lang.reflect.Method.invoke(Method.java:597)  at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)  at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)  at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)  at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)  at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)  at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)  at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)  at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)  at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)  at org.junit.runners.ParentRunner.run(ParentRunner.java:236)  at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)  at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)  

 很奇怪,怎么会属性名重复,我们看一下原类,

public String name = "zhangzhuo";  
public String address = "xxxxx" ; 

没有重复的名字,而我们添加的是age也不重复,为什么会报重复属性名错误呢?

原因是,在我们的Transform类中的visitField方法,这个方法会在每次属性被访问时调用,而ASM在对这个类操作时会遍历到每个属性,也就是说有一个属性就会调用一次visitField方法,有两个属性就会调用两次visitField方法,所以当我们原类中有两个属性时visitField方法被调用了两次,因此创建了两个同名的age属性。

从这个例子中我们可以将visitInnerClass、visitField、visitMethod和visitEnd这些方法分成两组,一组是visitInnerClass、visitField、visitMethod,这些方法有可能会被多次调用,因此在这些方法中创建属性时要注意会重复创建;另一组是visitEnd,这个方法只有在最后才会被调用且只调用一次,所以在这个方法中添加属性是唯一的,因此一般添加属性选择在这个方法里编码。

当然这里只给出了如何创建一个属性,其实修改,删除也都一样,根据上述知识大家可以参考ASM的源码即可掌握修改删除等操作。

附GeneratorClassLoader类代码

public class GeneratorClassLoader extends ClassLoader {  @SuppressWarnings("rawtypes")  public Class defineClassFromClassFile(String className, byte[] classFile)  throws ClassFormatError {  return defineClass(className, classFile, 0, classFile.length);  }  
} 


ASM系列之五:操作类方法

前面我们了解了如何使用ASM的CoreAPI来操作一个类的属性,现在我们来看一下如何修改一个类方法。

场景:假设我们有一个Person类,它当中有一个sleep方法,我们希望监控一下这个sleep方法的运行时间:

一般我们会在代码里这样写:

public void sleep() {  long timer = System.currentTimeMillis(); try {  System.out.println("我要睡一会...");  TimeUnit.SECONDS.sleep(2);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println(System.currentTimeMillis()-timer);}

 标红的两行代码是我们希望有的,但是一般不会将这样的代码和业务代码耦合在一起,所以借助asm来实现动态的植入这样两行代码,就可以使业务方法很清晰。因此我们需要能够修改方法的API,在ASM中提供了对应的API,即MethodAdapter,使用这个API我们就可以随心所欲的修改方法中的字节码,甚至可以完全重写方法,当然这样是没有必要的。下面我们来看一下如何使用这个API,代码如下:

public class ModifyMethod extends MethodAdapter {  public ModifyMethod(MethodVisitor mv, int access, String name, String desc) {  super(mv);  }  @Override  public void visitCode() {  mv.visitFieldInsn(Opcodes.GETSTATIC,  Type.getInternalName(Person.class), "timer", "J");  mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",  "currentTimeMillis", "()J");  mv.visitInsn(Opcodes.LSUB);  mv.visitFieldInsn(Opcodes.PUTSTATIC,  Type.getInternalName(Person.class), "timer", "J");  }  @Override  public void visitInsn(int opcode) {  if (opcode == Opcodes.RETURN) {  mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",  "Ljava/io/PrintStream;");  mv.visitFieldInsn(Opcodes.GETSTATIC,  Type.getInternalName(Person.class), "timer", "J");  mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",  "currentTimeMillis", "()J");  mv.visitInsn(Opcodes.LADD);  mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",  "println", "(J)V");  }  mv.visitInsn(opcode);  }  
} 

MethodAdapter类实现了MethodVisitor接口,在MethodVisitor接口中严格地规定了每个visitXXX的访问顺序,如下:

visitAnnotationDefault?( visitAnnotation | visitParameterAnnotation | visitAttribute )*( visitCode
( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |visitLocalVariable | visitLineNumber )*visitMaxs )?visitEnd

首先,统一一个概念,ASM访问,这里所说的ASM访问不是指ASM代码去调用某个类的具体方法,而是指去分析某个类的某个方法的二进制字节码。

在这里visitCode方法将会在ASM开始访问某一个方法时调用,因此这个方法一般可以用来在进入分析JVM字节码之前来新增一些字节码,visitXxxInsn是在ASM具体访问到每个指令时被调用,上面代码中我们使用的是visitInsn方法,它是ASM访问到无参数指令时调用的,这里我们判但了当前指令是否为无参数的return来在方法结束前添加一些指令。

通过重写visitCode和visitInsn两个方法,我们就实现了具体的业务逻辑被调用前和被调用后植入监控运行时间的代码。

ModifyMethod类只是对方法的修改类,那如何让外部类调用它,要通过我们上一篇中使用过的类,ClassAdapter的一个子类,在这里我们定义一个ModifyMethodClassAdapter类,代码如下:

public class ModifyMethodClassAdapter extends ClassAdapter {  public ModifyMethodClassAdapter(ClassVisitor cv) {  super(cv);  }  @Override  public MethodVisitor visitMethod(int access, String name, String desc,  String signature, String[] exceptions) {  if (name.equals("sleep")) {  return new ModifyMethod(super.visitMethod(access, name, desc,  signature, exceptions), access, name, desc);  }  return super.visitMethod(access, name, desc, signature, exceptions);  }  @Override  public void visitEnd() {  cv.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC, "timer", "J",  null, null);  }  } 

上述代码中我们使用visitEnd来添加了一个timer属性,用于记录时间,我们重写了visitMethod方法,当ASM访问的方法是sleep方法时,我们调用已经定义的ModifyMethod方法,让这个方法作为访问者,去访问对应的方法。

这样两个类就实现了我们要的添加执行时间的业务。

看一下测试类:

public class ModifyMethodTest {  @Test  public void modiySleepMethod() throws Exception {  ClassReader classReader = new ClassReader(  "org.victorzhzh.common.Person");  ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);  ClassAdapter classAdapter = new ModifyMethodClassAdapter(classWriter);  classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);  byte[] classFile = classWriter.toByteArray();  GeneratorClassLoader classLoader = new GeneratorClassLoader();  @SuppressWarnings("rawtypes")  Class clazz = classLoader.defineClassFromClassFile(  "org.victorzhzh.common.Person", classFile);  Object obj = clazz.newInstance();  System.out.println(clazz.getDeclaredField("name").get(obj));  clazz.getDeclaredMethod("sleep").invoke(obj);  }  
} 

 通过反射机制调用我们修改后的Person类,运行结果如下:

zhangzhuo  
我要睡一会...  
2023 

2023就是我们让sleep方法沉睡的时间,看一下我们的原始Person类:

public class Person {  public String name = "zhangzhuo";  public void sayHello() {  System.out.println("Hello World!");  }  public void sleep() {  try {  System.out.println("我要睡一会...");  TimeUnit.SECONDS.sleep(2);//沉睡两秒  } catch (InterruptedException e) {  e.printStackTrace();  }  }  
} 

以上几篇文章都是关于ASM的大体介绍,ASM的功能可以说是十分强大,要学好这个东西个人有几点体会:

第一、要熟悉Java字节码结构,及指令:因为我们在很多时候都是要写最原始的字节吗指令的,虽然ASM也为我们提供相应的简化API替我们来做这些事情,但是最基本的东西还是要了解和掌握的,这样才能使用的更好;

第二、充分理解访问者模式有助于我们理解ASM的CoreAPI;

第三、掌握基本的ClassVisitor、ClassAdapter、MethodVisitor、MethodAdapter、FieldVisitor、FieldWriter、ClassReader和ClassWriter这几个类对全面掌握CoreAPI可以有很大的帮助;

第四、在掌握了CoreAPI后再去研究TreeAPI,这样更快速;

最后,希望这几篇文章能对研究ASM的朋友有所帮助


转载自:http://victorzhzh.iteye.com/category/140253

参考:http://blog.csdn.net/zhangjg_blog/article/details/22976929

http://my.oschina.net/u/1166271/blog/162796

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

相关文章

  1. Github免费中文书《Go入门指南》,带你从零学Go | 极客头条

    点击上方↑↑↑蓝字关注我们~「2019 Python开发者日」全日程揭晓,请扫码咨询 ↑↑↑作者 | 无闻整理 | Jane出品 | AI科技大本营(ID:rgznai100)【导语】Go(也称 Golang)是 Google 开发的一种静态强类型、编译型、并发型的编程语言。但是国内学习 Go 语言的资源还并不是特…...

    2024/3/4 9:15:50
  2. 贪吃蛇开发文档说明

    #Linux环境下vim编写C语言贪吃蛇 https://gitee.com/qianzhen1997/c_language_week2.git 此为贪吃蛇源码在码云上的代码链接 #一.---------------贪吃蛇实际完成情况 #二.---------------贪吃蛇游戏运行环境 #三.---------------贪吃蛇游戏构成 #四.---------------心得体会 ##…...

    2024/3/4 9:15:48
  3. js/jquery 实时监听输入框值变化的完美方案:oninput & onpropertychange

    (1) 先说jquery, 使用 jQuery 库的话,只需要同时绑定oninput 和 onpropertychange 两个事件就可以了,示例代码:$(#username).bind(inputpropertychange, function() {$(#content).html($(this).val().length+ characters); });(2) 对于JS原生写法而言, oninput 是 HT…...

    2024/3/4 9:15:48
  4. MySQL数据库 ODBC驱动安装和配置数据源

    MySQL ODBC驱动安装和配置数据源 一、MySQL的ODBC驱动下载及安装 步骤一:下载ODBC驱动安装包 1、下载地址: https://dev.mysql.com/downloads/connector/odbc/ 2、选择适合自己电脑版本(我的操作系统是Win10,64位)3、进入下载界面后,我们不登录(Login)也不注册(Sign U…...

    2024/3/13 0:29:37
  5. C语言基于SDL2写的贪吃蛇项目

    copyright-------西南交大利兹 leo调用到的头文件有#include <stdio.h> #include <stdlib.h> #include <SDL2/SDL.h> #include <SDL2/SDL_mixer.h> #include <stdbool.h>#include <time.h>对于SDL一类的API,codebloakcs的用户可以去这里下…...

    2024/3/7 17:08:28
  6. Go语言从头学习笔记

    1.golang下载+idea安装go插件首先,下载go语言需要翻墙,安装教程看:http://www.runoob.com/go/go-environment.html http://blog.csdn.net/chenggong2dm/article/details/49423555网上大部分都是从pycharm,【 File】–>【settings】–>找到【Plugins】,在对应页面中,点…...

    2024/3/7 17:08:27
  7. Java中如何查看字节码?

    之前一直没怎么看过Java中的字节码,也没尝试怎么去看过,最近准备深入研究一下Java并发和锁相关的东西,发现字节码是绕不过了,所谓“工欲善其事,必先利其器”,所以我们得先知道怎么去看字节码。这里,给出两种方式去查看字节码,一种是控制台,一种是图形化界面工具。(1)…...

    2024/3/7 17:08:26
  8. 解决无法从windows配置的odbc数据源连接到linux下的TimesTen1122。

    问题可能出现在:linux下的  TimesTen没有添加操作用户所致,解决办法如下: create user username identified by password; grant create session ,create any table to username;windows下,加上User ID和Password,重新测试连接,成功!!!...

    2024/3/7 17:08:25
  9. 大一时用c语言写的贪吃蛇游戏,嘿嘿

    今天整理电脑, 发现自己去年就是大一上学期快结束时写的贪吃蛇, 那时还没有写blog, 今天拿出来晒晒啊,嘿嘿多多交流,多多交流...... #include<stdio.h>#include<conio.h>#include<math.h>#include<dos.h>#include<stdlib.h>#include<graphic…...

    2024/3/7 17:08:24
  10. java字节码混淆开源工具-ProGuard

    定义 ProGuard是一个压缩、优化和混淆Java字节码文件的免费的工具,它可以删除无用的类、字段、方法和属性。可以删除没用的注释,最大限度地优化字节码文件。它还可以使用简短的无意义的名称来重命名已经存在的类、字段、方法和属性。2压缩 JAVA源代码(.java文件)通常被编译…...

    2024/3/16 3:26:37
  11. windows10新建ODBC连接“Sql Server2008” 测试失败

    在sql server数据库里新建用户名并配置账户和系统属性:1.用户登陆密码2.用户映射用户名右击属性,点击用户属性,勾选要映射的数据库:3.Sql Server服务器登陆方式:之后,在windows10系统下,打开控制面板->小图标模式查看->找到管理工具->选择ODBC源->添加用户DS…...

    2024/3/7 17:08:22
  12. Golang教程:(十三)Map

    原文:https://golangbot.com/maps/这是本Golang系列教程的第十三篇。什么是 map?Map 是 Go 中的内置类型,它将键与值绑定到一起。可以通过键获取相应的值。如何创建 map?可以通过将键和值的类型传递给内置函数 make 来创建一个 map。语法为:make(map[KeyType]ValueType)。…...

    2024/3/7 17:08:21
  13. onchange 和 onpropertychange、onblur区别

    onchange 和 onpropertychange、onblur区别 IE下,当一个HTML元素的属性改变的时候,都能通过 onpropertychange来捕获。例如一个 <input name="text1" id="text1" /> [color=red]对象的value属性被页面的脚本修改的时候,onchange无法捕获到,而onp…...

    2024/3/11 7:20:11
  14. java字节码详解

    一、整体结构二、详解*首先要说下class文件的定义:8位字节为基础单位的二进制流。 有着严格的顺序,中间是没有空隙,即没有多余的数据。 如果超过8个字节,以高位在前的方式分割为若干个8位字节。 整体结构是由一个无符号数+一个表,表中有指向其他表的索引,直到找到最后的值…...

    2024/3/7 17:08:19
  15. Golang教程:(九)循环语句

    原文:https://golangbot.com/loops/这是本Golang系列教程的第九篇。循环语句用于重复执行一段代码。for 语句是 Go 中唯一的循环语句。Go 没有提供其他语言(如 C)中的 while 和 do while 语句。for 语句语法for 语句的语法如下:for initialisation; condition; post { }其…...

    2024/3/7 17:08:19
  16. ODBC和ODBC数据源的区别

    ODBC:ODBC(Open DataBase Conectivity)是微软公司制定连接访问数据库的接口,只要有相应的ODBC驱动程序,就可以通过ODBC连结操作各种不同的数据库。应用程序-----(ODBC提供的API)------》ODBC-----(不同的数据库驱动)-----》与驱动相应的数据库;ODBC数据源:数据的来源…...

    2024/3/7 17:08:17
  17. c语言练手项目

    C语言实现的猜拳游戏(剪子锤子布),让你与电脑对决 C语言贪吃蛇下载[带源码和解析] C语言文件加密解密软件 C语言写的简单的定时关机程序 C语言动态数组的实现:数组长度随数组元素改变,不会溢出,不会浪费资源 一道题目,玩转C语言指针数组和指向指针的指针 C语言数组灵活多变…...

    2024/3/12 22:43:10
  18. Eclipse安装查看java字节码插件Bytecode Outline

    一、下载地址 download选择适合你自己的eclipse的版本 二、安装根据提示把下载的jar包放进对应的目录里面,然后重启就行了。 我自己的是放在这个路径下:E:\eclipse-jee-kepler-SR2-win32\eclipse\dropins然后重启eclipse 三、使用输入by,同时选中这两个选项,点击确定按钮接…...

    2024/3/7 17:08:15
  19. oninput和onpropertychange实时监听输入框值的变化

    oninput和onpropertychange实时监听输入框值的变化传统监听输入框的做法就是使用keyup、keydown、keypress,或者change事件来实现,但keyup、keydown、keypress事件是只要完成击键事件后就触发,不考虑输入框的值是否变化,也监听不了使用鼠标右键[剪贴]和[粘贴]这些操作,更监…...

    2024/3/7 17:08:14
  20. 关于在windows 7 64位机器上配置32位的odbc数据源解决办法

    装完oracle客户端之后,在控制面板-》管理工具-》数据源 是找不到32位的oracle odbc驱动的(至少我机器是这样的)。 如果是装的是64位客户端,则可以看到64位的oracle odbc驱动,但是配置完成后该数据源不能在32位程序中使用,会提示说驱动和应用程序结构体系不对,如果是装的…...

    2024/3/4 9:15:55

最新文章

  1. VMware vSAN OSA存储策略 - 基于虚拟机的分布式对象存储

    简介 博客&#xff1a;https://songxwn.com/ 存储策略 (Storage Policy) 是管理员定义的一组规则&#xff0c;这组规则定义了数据对象在 vSAN 存储上是如何保存的&#xff0c;存储策略定义了数据存储的可靠性、访问性能等特性。vSAN 提供了基于存储策略的存储管理 SPBM (Stor…...

    2024/3/28 19:35:12
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 批量删除 rabbitmq中随机队列

    批量删除 amq.gen–* 随机队列 操作错误产生了无效随机队列&#xff0c;需要批量删除 过滤列出指定amq.gen–队列 # 列出 指定 vhost/qq 以amq.gen开头的所有队列 rabbitmqctl list_queues --vhost / | grep ^amq.gen-# 批量删除队列 #由于list_queues会列出队列名称以及对应…...

    2024/3/28 7:59:13
  4. 路径优化算法 | 基于白鹭群算法ESOA实现复杂城市地形下无人机避障三维航迹规划附Matlab代码

    白鹭群算法(Egret Swarm Optimization Algorithm, ESOA)是一种模拟白鹭群体行为的优化算法,适用于解决复杂优化问题。在无人机避障三维航迹规划问题中,考虑复杂城市地形、障碍物、威胁区域以及飞行约束条件(如高度限制、转角限制等)是非常重要的。下面是一个简化的步骤概…...

    2024/3/25 20:19:28
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/3/27 10:21:24
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/3/24 20:11:25
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/3/18 12:12:47
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/3/24 20:11:23
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/3/26 20:58:42
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/3/28 17:01:12
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/3/24 5:55:47
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/3/27 10:28:22
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/3/26 23:04:51
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/3/26 11:20:25
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/3/24 20:11:18
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/3/28 9:10:53
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/3/24 20:11:16
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/3/24 20:11:15
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/3/27 7:12:50
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/3/24 20:11:13
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/3/26 11:21:23
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/3/28 18:26:34
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/3/28 12:42:28
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/3/26 9:58:17
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57