有哪些方法可以在运行时动态生成一个Java类?

之前的文章中,提到了动态代理,先举一个比较常见的应用场景,在JPA中仅仅是实现了Jps两个接口,就自动生成了类,帮助我们完成Sql基本操作,并且层层封装,从配置、驱动、到SQL语句的写入都帮我们完成,换句话说,动态的生成了一个方法完成了操作,这也就是动态代理操作,但是文章中并没有很仔细的进行说明,因此在本文中将进行深入的分析。

概述

我们从原始的java类来进行源分析,通常是我们编写完程序,调用javac编译成class文件,通过类加载机制载入jvm,然后就可以运行了。

但是我们也可以不使用这个办法,也可以用官方的java.compiler去编译,因为只要是符合 jvm字节码标准的都可以拿来编译,而java.compiler库下的方法拥有和javac对等的编译方式,进一步思考,我们已经知道了jvm编译可以识别的字节码,那么,自然也可以用之前提到的cglib asm去转化实现。

面试角度来说,主要有以下几点:

  • 字节码和类加载到底是怎么实现无缝转换的?发生在整个类加载过程的哪一步?
  • 如何利用字节码操作技术、实现基本动态代理
  • 除了动态代理,字节码操作还有哪些应用场景

扩展

首先了解一下类从字节码到Class对象的转换:

@Deprecated
    protected final Class<?> defineClass(byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(null, b, off, len, null);
    }


    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(name, b, off, len, null);
    }	

可以看出,只要能生出规范的字节码,不管是作为byte数组形式还是放到ByteBuffer里,都可以平滑的完成字节码到Java对象的转换过程

JDK提出的defineClass方法,最终都是本地代码实现的

    private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

    private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
                                         int off, int len, ProtectionDomain pd,
                                         String source);

更进一步,我们来看看JDK dynamic proxy的实现代码。你会发现,对应逻辑是实现在ProxyBuilder这个静态内部类中,ProxyGenerator生成字节码,并以byte数组的形式保 存,然后通过调用Unsafe提供的defneClass入口。

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
    try {
        return defineClass0(loader, proxyName,
         proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
                
         throw new IllegalArgumentException(e.toString());
    }

从以上的代码我们知道了字节码->Class对象的转换过程,但是还不知道如何生成自己需要的字节码,因此接下来看看字节码的操作逻辑:

JDK内部动态代理的逻辑,可以参考java.lang.refect.ProxyGenerator的内部实现。可以认为这是种另类的字节码操纵技术,其利用了DataOutputStrem提供的能力,配合hard-coded的各种JVM指令实现方法,生成所需的字节码数组,可以参考下面代码:

private void codeLocalLoadStore(int var1, int var2, int var3, DataOutputStream var4) throws IOException {
        assert var1 >= 0 && var1 <= 65535;

        if (var1 <= 3) {
            var4.writeByte(var3 + var1);
        } else if (var1 <= 255) {
            var4.writeByte(var2);
            var4.writeByte(var1 & 255);
        } else {
            var4.writeByte(196);
            var4.writeByte(var2);
            var4.writeShort(var1 & '\uffff');
        }

    }

我们可以看出,这种代码实现起来没有什么依赖关系,简单实用但是又需要知道一些JVM命令,知道如何处理偏移地址。因此不适合普通的开发场景

知道了字节码相关操作逻辑,可以简单思考一下,如果需要实现一个简单的动态代理,如何使用字节码技术来实现走通这个流程

对于一个普通的Java动态代理,实现过程可以简化为:

  • 提供一个基础的接口(可以参照动态代理那篇专栏举的例子),根据接口对应的实现类,使用getClassLoader,利用Proxy的newProxyInstance方法实现调用接口即能直接实现最终的java实体类方法(JPA就是此原理)

可以看下面方法签名具体来了解:

public satic Object newProxyInsance(
ClassLoader loader,  
Class<?>[] interfaces,  
InvocationHandler h
)

字节码的操作技术,除了使用在动态代理,还可以使用在以下:

  • Mock
  • ORM
  • IOC
  • Profiler工具
  • 生成形式化代码

甚至还可以认为,字节码操纵技术是工具和基础框架必不可少的部分,大大减少了开发者的负担。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值