zoukankan      html  css  js  c++  java
  • 【进阶之路】动态代理与字节码生成

    这段时间换了新工作,因为去了外企,所以对英语的要求突然猛增,现在每天靠着谷歌翻译过日子。在开会的时候,经常遇到不懂的单词,很多时候都需要记下读音,事后再慢慢根据语境去找对应的单词,日子过得可谓是有滋有味。于是乎,自我充电的时间大部分用来学习英语了,所以这段时间更新的节奏会很慢~

    对于大多数Java程序员而言,我们会经常用到字节码生成与动态代理技术,比如编译时织入的AOP框架中,在Spring的Bean组织管理中,亦或是Web服务器的JSP编译器里。总之,我们在不知不觉中已经大量的用到了这些技术了。

    动态代理中所说的动态,是基于Java代码实际编写代理类的静态代理而言的。相比较而言,它的优势在于可以在不知道原始类与接口的时候,就先确定了代理行为,可以很方便的在不同的应用场景中灵活运用。同时,还可以减少代码的行数,让你的代码更加美观和简洁。

    一、动态代理

    这边简单的实现一个动态代理的方法,如果想看基于AOP与注解形式的,可以去看我之前的文章,也讲的很详细【进阶之路】自定义注解介绍与实战

    public class DynamicTest {
        public static void main(String[] args) {
            IPayment pay = (IPayment) new BankDynamicProxy().bind(new Alipay());
            pay.payment();
        }
    
        interface IPayment {
            void payment();
        }
    
        static class Alipay implements IPayment {
            @Override
            public void payment() {
                System.out.println("Use Alipay to payment");
            }
        }
    
        static class BankDynamicProxy implements InvocationHandler {
            Object dynamicProxy;
    
            Object bind(Object dynamicProxy) {
                this.dynamicProxy = dynamicProxy;
                return Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader()
                        , dynamicProxy.getClass().getInterfaces(), this);
            }
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("access Bank Api");
                return method.invoke(dynamicProxy, args);
            }
        }
    }
    
    

    这个动态代理方法逻辑很简单,就是在使用支付宝支付的时候去请求了一次银行的接口。通过这个方法,我们可以使用debug的方法看到程序的验证、优化、缓存、字节码生成、类加载等一些列操作

    image.png

    但是我们这一次不用去探究全部的流程,只需要去了解字节码生成的操作。

    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces, accessFlags);
    

    这个方法会在运行时生产一个描述代理类的字节码byte[]数组。

    image.png

    在debug中看实在是太麻烦,我们可以生成一个字节码文件,通过反编译来查看具体的内容。

    二、字节码生成

    只需要在代码的main方法中加入下图这个方法,就可以在指定的位置生成一个名为Proxy0.classProxy0.class的代理类文件。当然,换成nanju.class也没问题。

    public static void main(String[] args) {
    
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", DynamicTest.class.getInterfaces());
        String path = "D:\temp\$Proxy0.class";
        try (FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("Agent class file written successfully");
        } catch (Exception e) {
            System.out.println("file written fail");
        }
    }
    

    然后我们通过最简单的方法,直接把class文件,拖拽到IntelliJ IDEA工具中,IntelliJ自动反编译为Java文件。

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy {
        private static Method m1;
        private static Method m2;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    从这个动态代理类反编译的代码中可以看出,它为从Object中继承来的equals(),toString()和hashCode()等方法都生成了对应的实现,并且统一调用了java.lang.reflect.InvocationHandler对象中的invoke()方法,只是传入的参数和Method方法有所不同。所以无论动态代理什么方法,其实执行的依旧是InvocationHandler中的额逻辑。

    generateProxyClass()方法通过Class文件的规范去拼接字节码,但是对于程序代码来说,这样的拼接很消耗资源且只能产生高度模板化的代码。比起这样的生成,现成的字节码库更适合于生产上的实践。

    有需要的同学可以加我的公众号,以后的最新的文章第一时间都在里面,也可以找我要思维导图

  • 相关阅读:
    GridView多表头固定+分组+总计
    ajaxpro.2.dll使用【转帖】
    表达式计算易错题
    uclibc下使用libcurl的段错误(缺少hosts文件)
    《java.util.concurrent 包源码阅读》01 源码包的结构
    Linux学习笔记【2】Install Software under RedHat enterprise 5.4
    Windows语言包的那些事
    Let outlook work background when it is minimal
    DB2 Error Message
    db2 系统表信息
  • 原文地址:https://www.cnblogs.com/wl-blog/p/15093095.html
Copyright © 2011-2022 走看看