首页 > 编程语言 > 详细

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

时间:2020-03-19 11:33:01      阅读:63      评论:0      收藏:0      [点我收藏+]

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

class文件简介及加载

java文件到class对象大体流程

Java编译器编译好Java文件之后,产生.class 文件在磁盘中。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的Class对象。

技术分享图片

 

 

 class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的、具体class文件是怎样组织类信息的,可以参考 此博文:深入理解Java Class文件格式系列或者是Java虚拟机规范

代码演示class文件字节码 -> class对象 -> 实例化

a. 定义一个 Programmer类:

package com.bijian.study;

/** 
 * 程序猿类 
 */
public class Programmer {

    public void code() {
        System.out.println("I‘m a Programmer,Just Coding.....");
    }
}

b. 自定义一个类加载器:

package com.bijian.study;

/** 
 * 自定义一个类加载器,用于将字节码转换为class对象 
 */
public class MyClassLoader extends ClassLoader {

    public Class<?> defineMyClass(byte[] b, int off, int len) {
        return super.defineClass(b, off, len);
    }
}

c. 然后编译成Programmer.class文件,在程序中读取字节码,然后转换成相应的class对象,再实例化:

package com.bijian.study;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;

public class MyTest {

    public static void main(String[] args) throws IOException {
        //读取本地的class文件内的字节码,转换成字节码数组  
        File file = new File(".");
        InputStream input = new FileInputStream(file.getCanonicalPath() + "\\bin\\com\\bijian\\study\\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());
        
        try {
            //实例化一个Programmer对象  
            Object o = clazz.newInstance();
            //调用Programmer的code方法  
            clazz.getMethod("code", null).invoke(o, null);
        } catch (IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

com.bijian.study.Programmer
I‘m a Programmer,Just Coding.....

以上代码演示了,通过字节码加载成class 对象的能力,下面看一下在代码中如何生成class文件的字节码。

在运行期的代码中生成二进制字节码

由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。

技术分享图片

 

 

 在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。

Java字节码生成开源框架介绍--ASM

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

下面通过ASM 生成下面类Programmer的class字节码:

package com.bijian.demo;

public class Programmer {

    public void code() {
        System.out.println("I‘m a Programmer,Just Coding.....");
    }
}

使用ASM框架提供了ClassWriter 接口,通过访问者模式进行动态创建class字节码,看下面的例子:

package com.bijian.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

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, "<init>", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()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();
    }
}

上述的代码执行过后,用Java反编译工具(如JD_GUI)打开D盘下生成的Programmer.class,可以看到以下信息:

技术分享图片

 

 再用上面我们定义的类加载器将这个class文件加载到内存中,然后 创建class对象,并且实例化一个对象,调用code方法,会看到下面的结果:

Programmer
I‘m a Programmer,Just Coding.....

以上表明:在代码里生成字节码,并动态地加载成class对象、创建实例是完全可以实现的。

Java字节码生成开源框架介绍--Javassist

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

原文:https://www.cnblogs.com/fanguangdexiaoyuer/p/12522765.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!