zoukankan      html  css  js  c++  java
  • 字节码技术及动态代理

    简述class文件加载过程

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

    Java字节码生成框架

    • ASM
      ASM 是一个够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
      eg:
    public class Test {
    public void testAsm() {
    System.out.println("testAsm");
    }
    }
    public class TestAsmMain {
    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,// 类修饰符
    "com.liud.classtec.Test", // 类的全限定名
    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();
    // 定义testAsm方法
    MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "testAsm", "()V",
    null, null);
    methodVisitor.visitCode();
    methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
    "Ljava/io/PrintStream;");
    methodVisitor.visitLdcInsn("testAsm");
    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("C:\test\Test.class");
    FileOutputStream fout = new FileOutputStream(file);
    fout.write(data);
    fout.close();
    }
    }
    • Javassist
      Javassist是一个开源的分析、编辑和创建Java字节码的类库。
      eg:
     class Test {
    public void testAsm() {
    System.out.println("testAsm");
    }
    }
    public class TestJavassist {
    public static void main(String[] args) throws Exception {
    ClassPool pool = ClassPool.getDefault();
    //创建类
    CtClass cc= pool.makeClass("com.liud.classtec.Test");
    //定义方法
    CtMethod method = CtNewMethod.make("public void testAsm(){}", cc);
    //插入方法代码
    method.insertBefore("System.out.println("test");");
    cc.addMethod(method);
    //保存生成的字节码
    cc.writeFile("C:\test\javassit");
    }
    }

    字节码技术与动态代理

      先上个旧图,之前写代理模式时候的UML类图。

           所谓动态代理是区别于静态代理的,除去了静态代理的类,在运行中动态创建代理类。运行中动态创建用到的就是字节码技术,现在实现的方式主流是两种 JDK原生的模式和CGLIB的模式。

    • JDK原生的模式
      步骤如下:
    1. 获取 RealSubject上的所有接口列表
    2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX
    3. 根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码
      将对应的字节码转换为对应的class 对象
    4. 创建InvocationHandler 实例handler,用来处理Proxy所有方法调用
    5. Proxy 的class对象 以创建的handler对象为参数,实例化一个proxy对象
      eg:
    public class DynamicProxy {
    public static void main(String args[]) {
    RealSubject real = new RealSubject();
    Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
    new Class[]{Subject.class},
    new ProxyHandler(real));
    proxySubject.doSomething();
    createProxyClassFile();
    }
    public static void createProxyClassFile() {
    String name = "ProxySubject";
    byte[] data = ProxyGenerator.generateProxyClass( name, new Class[] { Subject.class } );
    try
    {
    FileOutputStream out = new FileOutputStream( name + ".class" );
    out.write(data);
    out.close();
    } catch(Exception e) {
    e.printStackTrace();
    }
    }
    }
    interface Subject
    {
    public void doSomething();
    }
    class RealSubject implements Subject
    {
    public void doSomething()
    {
    System.out.println( "call doSomething()" );
    }
    }
    public class ProxyHandler implements InvocationHandler
    {
    private Object proxied;
    public ProxyHandler( Object proxied )
    {
    this.proxied = proxied;
    }
    public Object invoke(Object proxy, Method method, Object[] args ) throws Throwable
    {
    //在转调具体目标对象之前,可以执行一些功能处理
    //转调具体目标对象的方法
    return method.invoke(proxied, args);
    //在转调具体目标对象之后,可以执行一些功能处理
    }
    }

    不足:
    Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏。

    • CGLIB的模式
      cglib其被广泛应用于AOP框架(Spring等)中,用以提供方法拦截操作。
      cglib代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。
      这里上一张cglib的图从这里可以看出cglib底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。

    eg:当时再写注解的时候就拿cglib自定义过注解,这里在另外举一个例子

    public class CglibProxyClass {
    public void test(){
    System.out.println("run method");
    }
    public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(CglibProxyClass.class);
    enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable
    {
    System.out.println("before method run");
    Object result = proxy.invokeSuper(obj, args);
    System.out.println("after method run");
    return result;
    }
    });
    CglibProxyClass proxyClass = (CglibProxyClass) enhancer.create();
    proxyClass.test();
    }
    }

    总结

    这篇主要还是说道我们日常常接触并应用的动态代理技术,及其背后的字节码技术,在此做下备忘,网路上还有一个旧文,《动态代理的前世今生》,
    也是很经典的东西啦。

    参考博文:

    http://blog.csdn.net/luanlouis/article/details/24589193
    http://blog.csdn.net/danchu/article/details/70238002
    https://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html

  • 相关阅读:
    Content Hugging Priority 和 Content Compression Resistance Priority
    fiddler 拦截小结
    好的 iOS 代码习惯
    查看约束优先级的方法
    定义接口常用约定
    让系统照片选择器的导航栏变蓝的代码
    ZT C语言实现字符串倒序
    ZT 蓝牙的AVCTP协议笔记
    ZT 蓝牙的AVDTP协议笔记
    ZT A2DP协议笔记
  • 原文地址:https://www.cnblogs.com/daily-note/p/8258623.html
Copyright © 2011-2022 走看看