java生成动态类的几种方法

典型回答

我们可以从常见的 Java 类来源分析,通常的开发过程是,开发者编写 Java 代码,调用 javac 编译成 class 文件,然后通过类加载机制载入 JVM,就成为应用运行时可以使用的 Java 类了。

从上面过程得到启发,其中一个直接的方式是从源码入手,可以利用 Java 程序生成一段源码,然后保存到文件等,下面就只需要解决编译问题了。

有一种笨办法,直接用 ProcessBuilder 之类启动 javac 进程,并指定上面生成的文件作为输入,进行编译。最后,再利用类加载器,在运行时加载即可。

前面的方法,本质上还是在当前程序进程之外编译的,那么还有没有不这么 low 的办法呢?

你可以考虑使用 Java Compiler API,这是 JDK 提供的标准 API,里面提供了与 javac 对等的编译器功能,具体请参考java.compiler相关文档。

进一步思考,我们一直围绕 Java 源码编译成为 JVM 可以理解的字节码,换句话说,只要是符合 JVM 规范的字节码,不管它是如何生成的,是不是都可以被 JVM 加载呢?我们能不能直接生成相应的字节码,然后交给类加载器去加载呢?

当然也可以,不过直接去写字节码难度太大,通常我们可以利用 Java 字节码操纵工具和类库来实现,比如在专栏第 6 讲中提到的ASM、Javassist、cglib 等。

import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Method;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
/**
 * 动态生成类以及编译,JDK版本必须要在1.6,或者1.6以上
 */
public class CompilerTest {
 
    //回车加换行符
    static String rt = "\r\n";
    //生成类的源文件,写成字符串的形式
    static String src =
        "package com.test;"+
        "public class HelloWorld  {" + rt +
        "    public static void main(String[] args) {" + rt +
        "         System.out.println(\"Hello world!\");" + rt +
        "    }" + rt +
        "}";
   
    public static void main (String[] args) throws Exception {
 
        //写文件,目录可以自己定义
        String filename = System.getProperty ("user.dir") + "/src/HelloWorld.java";
        //System.out.println (filename);
        File file = new File (filename);
        FileWriter fw = new FileWriter (file);
        fw.write(src);
        fw.flush();
        fw.close();
 
        //编译文件,调用jdk本身的工具
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        System.out.println(compiler.getClass().getName());
 
        StandardJavaFileManager sjfm = compiler.getStandardFileManager (null, null, null);
        Iterable units = sjfm.getJavaFileObjects (filename);
        CompilationTask ct = compiler.getTask (null, sjfm, null, null, null, units);
        // 动态编译可执行的代码
        ct.call();
        Class<?> clazz = Class.forName("com.test.HelloWorld");
        try {
            // 生成对象
            Object obj = clazz.newInstance();
            Class<? extends Object> cls = obj.getClass();
            // 调用main方法
            Method m = clazz.getMethod("main",String[].class);
            Object invoke = m.invoke(obj, new Object[] { new String[] {} });
        } catch (Exception e) {
            e.printStackTrace();
        }
        sjfm.close();
    }
}

字节码如何转换成Class 对象

类加载过程,这一步是通过 defineClass 方法实现的。将字节码转换成 Class 对象。defineClass 最终都是本地代码实现的。

protected fnal Class<?> defneClass(String name, byte[] b, int of, int len,ProtectionDomain protectionDomain)
protected fnal Class<?> defneClass(String name, java.nio.ByteBufer b,ProtectionDomain protectionDomain)

JDK Proxy的实现

JDK dynamic Proxy,JDK动态代理的实现逻辑在 ProxyBuilder 这个静态内部类中 ,ProxyGenerator 生成字节码,并 byte 数组的形式保存,然后通过调用 Unsafe 提供的 defineClass 入库。

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
try {
    Class<?> pc = UNSAFE.defneClass(proxyName, proxyClassFile,
0, proxyClassFile.length,loader, null);
    reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
    return pc;
} catch (ClassFormatError e) {
    // 如果出现ClassFormatError,很可能是输入参数有问题,比如, ProxyGenerator有bug

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值