Java 动态生成二进制字节码

Java动态代理过程中,会动态生成二进制字节码(只在内存中),该生成过程究竟是怎样的,我来一探究竟。

class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的。

1、类加载器


a.定义一个 Programmer类:
    public class Programmer { 
       
        public void code()  
        {  
            System.out.println("I'm a Programmer,Just Coding.....");  
        }  
    }  

b.自定义一个类加载器:
    public class MyClassLoader extends ClassLoader {  
      
        public Class defineMyClass( byte[] b, int off, int len)   
        {  
            return super.defineClass(b, off, len);  
        }  
          
    }  

c.然后编译成Programmer.class文件,在程序中读取字节码,然后转换成相应的class对象,再实例化: 
    public class MyTest {  
      
        public static void main(String[] args) throws IOException {  
            //读取本地的class文件内的字节码,转换成字节码数组  
            File file = new File(".");  
            InputStream  input = new FileInputStream(file.getCanonicalPath()+"\\bin\\samples\\Programmer.class");  
            byte[] result = new byte[1024];  
              
            int count = input.read(result);  
            // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象  
            MyClassLoader loader = new MyClassLoader();  
            Class clazz = loader.defineMyClass( result, 0, count);  
            //测试加载是否成功,打印class 对象的名称  
            System.out.println(clazz.getCanonicalName());  
                      
                   //实例化一个Programmer对象  
                   Object o= clazz.newInstance();  //new Programmer();
                   try {  
                       //调用Programmer的code方法  
                        clazz.getMethod("code", null).invoke(o, null);  
                       } catch (IllegalArgumentException | InvocationTargetException  
                            | NoSuchMethodException | SecurityException e) {  
                         e.printStackTrace();  
                      }  
     }  
    }  
以上代码演示了,通过字节码加载成class对象的能力。
 

上面只是使用类加载器来手动加载class文件到内存中(class文件是存在的),那么class文件如何动态生成呢?

2、在运行期生成二进制字节码

由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。
在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。

1)Java字节码生成开源框架介绍:ASM
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解

使用ASM框架提供了ClassWriter 接口,通过访问者模式进行动态创建Programmer.class字节码:


public class MyGenerator {  
  
    public static void main(String[] args) throws IOException {  
  
        System.out.println();  
        ClassWriter classWriter = new ClassWriter(0); 
        // 通过visit方法确定类的头部信息  
        classWriter.visit(Opcodes.V1_7,// java版本  
                Opcodes.ACC_PUBLIC,// 类修饰符  
                "Programmer", // 类的全限定名  
                null, "java/lang/Object", null);  
          
        //创建构造函数  
        MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "",
                                                                 "()V", null, null);  
        mv.visitCode();  
        mv.visitVarInsn(Opcodes.ALOAD, 0);  
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "","()V");  
        mv.visitInsn(Opcodes.RETURN);  
        mv.visitMaxs(1, 1);  
        mv.visitEnd();  
          
        // 定义code方法  
        MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC,
                                                          "code", "()V", null, null);  
        methodVisitor.visitCode();  
        methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",  
                "Ljava/io/PrintStream;");  
        methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");  
        methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",  
                "(Ljava/lang/String;)V");  
        methodVisitor.visitInsn(Opcodes.RETURN);  
        methodVisitor.visitMaxs(2, 2);  
        methodVisitor.visitEnd();  
        classWriter.visitEnd();   
        // 使classWriter类已经完成  
        // 将classWriter转换成字节数组写到文件里面去  
        byte[] data = classWriter.toByteArray();  
        File file = new File("D://Programmer.class"); //生成字节码文件 
        FileOutputStream fout = new FileOutputStream(file);  
        fout.write(data);  
        fout.close();  
    }  
} 
 
2)Java字节码生成开源框架介绍Javassist
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶滋)所创建的。它已加入了开放源代码JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类

生成Programmer.class字节码:


public class MyGenerator {  
  
    public static void main(String[] args) throws Exception {  
        ClassPool pool = ClassPool.getDefault();  
        //创建Programmer类       
        CtClass cc= pool.makeClass("com.samples.Programmer");  
        //定义code方法  
        CtMethod method = CtNewMethod.make("public void code(){}", cc);  
        //插入方法代码  
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");  
        cc.addMethod(method);  
        //保存生成的字节码  
        cc.writeFile("d://temp");  
    }  
}
 

参考:

Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM) (清晰,浅显)

你可能感兴趣的:(Java)