当前位置: 代码网 > it编程>编程语言>Java > java代码实现编译源文件

java代码实现编译源文件

2025年01月21日 Java 我要评论
原理从java 6开始,引入了java代码重写过的编译器接口,使得我们可以在运行时编译java源代码,然后再通过类加载器将编译好的类加载进jvm,这种在运行时编译代码的操作就叫做动态编译。主要类库ja

原理

从java 6开始,引入了java代码重写过的编译器接口,使得我们可以在运行时编译java源代码,然后再通过类加载器将编译好的类加载进jvm,这种在运行时编译代码的操作就叫做动态编译。

主要类库

javacompiler -表示java编译器, run方法执行编译操作. 还有一种编译方式是先生成编译任务(compilationtask), 让后调用compilationtask的call方法执行编译任务

javafileobject -表示一个java源文件对象

javafilemanager - java源文件管理类, 管理一系列javafileobject

diagnostic -表示一个诊断信息

diagnosticlistener -诊断信息监听器, 编译过程触发. 生成编译task(javacompiler#gettask())或获取filemanager(javacompiler#getstandardfilemanager())时需要传递diagnosticlistener以便收集诊断信息

流程图

源码文件 -> 字节码文件

    public static void fromjavafile() {
        // 获取javac编译器对象
        javacompiler compiler = toolprovider.getsystemjavacompiler();
        // 获取文件管理器:负责管理类文件的输入输出
        standardjavafilemanager filemanager = compiler.getstandardfilemanager(null, null, null);
        // 获取要被编译的java源文件
        file file = new file("/users//github/perfect/perfect-javassist/src/main/java/com/jc/javassist/compiler/testhello.java");
        // 通过源文件获取到要编译的java类源码迭代器,包括所有内部类,其中每个类都是一个javafileobject
        iterable<? extends javafileobject> compilationunits = filemanager.getjavafileobjects(file);
        // 生成编译任务
        javacompiler.compilationtask task = compiler.gettask(null, filemanager, null, null, null, compilationunits);
        // 执行编译任务
        task.call();
    }

我们这里准备了testhello.java

public class testhello {
    public static void main(string[] args) {
        system.out.println("this is a test");
    }
}

我们试着手动加载该class文件,使用类加载器的defineclass方法,可以直接加载字节码文件。

public static class<?> loadclassfromdisk(string path) throws nosuchmethodexception, invocationtargetexception, illegalaccessexception {
   // defineclass 为 classloader 类的一个方法,用于加载类
   // 但是这个方法是 protected 的,所以需要通过反射的方式获取这个方法的权限
   class<classloader> classloaderclass = classloader.class;
    method defineclass = classloaderclass.getdeclaredmethod("defineclass", byte[].class, int.class, int.class);
    defineclass.setaccessible(true);
   // 读取文件系统的 file 为 byte 数组
   file file = new file(path);
   byte[] bytes = new byte[(int) file.length()];
   try (fileinputstream fileinputstream = new fileinputstream(file)) {
        fileinputstream.read(bytes);
    } catch (ioexception e) {
        e.printstacktrace();
    }
   // 执行 defineclass 方法 返回 class 对象
   return (class<?>) defineclass.invoke(thread.currentthread().getcontextclassloader(), bytes, 0, bytes.length);
}

由于testhello中的方法为静态方法,使用class反射机制执行方法

// 执行编译任务
boolean call = task.call();
if (call) {
    class<?> o = loadclassfromdisk("/users/oneyoung/oneyoung/project/my/code/src/main/java/top/oneyoung/dynamic/testhello.class");
    method main = o.getmethod("main", string[].class);
    main.invoke(null, new object[]{new string[]{}});
}

执行结果

| this is a test

源码字符串 -> 字节码文件

在流程图中,gettask().call()会通过调用作为参数传入的javafileobject对象的getcharcontent()方法获得字符串序列,即源码的读取是通过 javafileobject的 getcharcontent()方法,那我们只需要重写getcharcontent()方法,即可将我们的字符串源码装进javafileobject了。构造sourcejavafileobject实现定制的javafileobject对象,用于存储字符串源码

public class sourcejavafileobject extends simplejavafileobject {
   /**
     * the source code of this "file".
     */
   private final string code;
    sourcejavafileobject(string name, string code) {
       super(uri.create("string:///" + name.replace('.', '/') + kind.source.extension), kind.source);
       this.code = code;
    }
   @override
   public charsequence getcharcontent(boolean ignoreencodingerrors) {
       return code;
    }
}

则创建javafileobject对象时,变为了:

// 通过源代码字符串获取到要编译的java类源码迭代器,包括所有内部类,其中每个类都是一个javafileobject
sourcejavafileobject javafileobject = new sourcejavafileobject("testhello", "public class testhello { public static void main(string[] args) { system.out.println("hello world"); } }");
// 生成编译任务
javacompiler.compilationtask task = compiler.gettask(null, filemanager, null, null, null, collections.singleton(javafileobject));

执行后,同样编译出了class文件,不过由于没有指定编译的class输出路径,他会默认放在源文件的根目录下

源码字符串 -> 字节码数组

如果我们进行动态编译时,想要直接输入源码字符串并且输出的是字节码数组,而不是输出字节码文件,又该如何实现?实际上,这是从内存中得到源码,再输出到内存的方式。

在gettask().call()源代码执行流程图中,我们可以发现javafileobject 的 openoutputstream()方法控制了编译后字节码的输出行为,编译完成后会调用openoutputstream获取输出流,并写数据(字节码)。所以我们需要重写javafileobject 的 openoutputstream()方法。

同时在执行流程图中,我们还发现用于输出的javafileobject 对象是javafilemanager的getjavafileforoutput()方法提供的,所以为了让编译器编译完成后,将编译得到的字节码输出到我们自己构造的javafileobject 对象,我们还需要自定义javafilemanager。

这里我使用类委托的方式,把大部分功能委托给了传入的standardjavafilemanager,主要是重写了getjavafileforoutput,使输出编译完成的字节码文件为字节数组。

然后增加了方法getbytesbyclassname获取编译完成的字节码字节数组

public class bytearrayjavafilemanager implements javafilemanager {
   private static final logger log = loggerfactory.getlogger(bytearrayjavafilemanager.class);
   private final standardjavafilemanager filemanager;
   /**
     * synchronizing due to concurrentmodificationexception
     */
   private final map<string, bytearrayoutputstream> buffers = collections.synchronizedmap(new linkedhashmap<>());
   public bytearrayjavafilemanager(standardjavafilemanager filemanager) {
       this.filemanager = filemanager;
    }
   @override
   public classloader getclassloader(location location) {
       return filemanager.getclassloader(location);
    }
   @override
   public synchronized iterable<javafileobject> list(location location, string packagename, set<kind> kinds, boolean recurse) throws ioexception {
       return filemanager.list(location, packagename, kinds, recurse);
    }
   @override
   public string inferbinaryname(location location, javafileobject file) {
       return filemanager.inferbinaryname(location, file);
    }
   @override
   public boolean issamefile(fileobject a, fileobject b) {
       return filemanager.issamefile(a, b);
    }
   @override
   public synchronized boolean handleoption(string current, iterator<string> remaining) {
       return filemanager.handleoption(current, remaining);
    }
   @override
   public boolean haslocation(location location) {
       return filemanager.haslocation(location);
    }
   @override
   public javafileobject getjavafileforinput(location location, string classname, kind kind) throws ioexception {
       if (location == standardlocation.class_output) {
           boolean success;
           final byte[] bytes;
           synchronized (buffers) {
                success = buffers.containskey(classname) && kind == kind.class;
                bytes = buffers.get(classname).tobytearray();
            }
           if (success) {
               return new simplejavafileobject(uri.create(classname), kind) {
                   @override
                   public inputstream openinputstream() {
                       return new bytearrayinputstream(bytes);
                    }
                };
            }
        }
       return filemanager.getjavafileforinput(location, classname, kind);
    }
   @override
   public javafileobject getjavafileforoutput(location location, final string classname, kind kind, fileobject sibling) {
       return new simplejavafileobject(uri.create(classname), kind) {
           @override
           public outputstream openoutputstream() {
               // 字节输出流用与filemanager输出编译完成的字节码文件为字节数组
               bytearrayoutputstream bos = new bytearrayoutputstream();
               // 将每个需要加载的类的输出流进行缓存
               buffers.putifabsent(classname, bos);
               return bos;
            }
        };
    }
   @override
   public fileobject getfileforinput(location location, string packagename, string relativename) throws ioexception {
       return filemanager.getfileforinput(location, packagename, relativename);
    }
   @override
   public fileobject getfileforoutput(location location, string packagename, string relativename, fileobject sibling) throws ioexception {
       return filemanager.getfileforoutput(location, packagename, relativename, sibling);
    }
   @override
   public void flush() {
       // do nothing
   }
   @override
   public void close() throws ioexception {
       filemanager.close();
    }
   @override
   public int issupportedoption(string option) {
       return filemanager.issupportedoption(option);
    }
   public void clearbuffers() {
       buffers.clear();
    }
   public map<string, byte[]> getallbuffers() {
        map<string, byte[]> ret = new linkedhashmap<>(buffers.size() * 2);
        map<string, bytearrayoutputstream> compiledclasses = new linkedhashmap<>(ret.size());
       synchronized (buffers) {
            compiledclasses.putall(buffers);
        }
        compiledclasses.foreach((k, v) -> ret.put(k, v.tobytearray()));
       return ret;
    }
   public byte[] getbytesbyclassname(string classname) {
       return buffers.get(classname).tobytearray();
    }
}

然后我们修改下之前的执行流程

public static void fromjavasourcetobytearray1() throws exception {
   // 获取javac编译器对象
   javacompiler compiler = toolprovider.getsystemjavacompiler();
   // 获取文件管理器:负责管理类文件的输入输出
   standardjavafilemanager filemanager = compiler.getstandardfilemanager(null, null, null);
   // 创建自定义的filemanager
   bytearrayjavafilemanager bytearrayjavafilemanager = new bytearrayjavafilemanager(filemanager);
   // 通过源代码字符串获取到要编译的java类源码迭代器,包括所有内部类,其中每个类都是一个javafileobject
   javafileobject javafileobject = new sourcejavafileobject("testhello", "public class testhello { public static void say(string args) { system.out.println(args); } }");
    javacompiler.compilationtask task = compiler.gettask(null, bytearrayjavafilemanager, null, null, null, collections.singletonlist(javafileobject));
   // 执行编译任务
   boolean call = task.call();
   if (boolean.true.equals(call)) {
       byte[] testhellos = bytearrayjavafilemanager.getbytesbyclassname("testhello");
        class<classloader> classloaderclass = classloader.class;
        method defineclass = classloaderclass.getdeclaredmethod("defineclass", byte[].class, int.class, int.class);
        defineclass.setaccessible(true);
        object invoke = defineclass.invoke(testhello.class.getclassloader(), testhellos, 0, testhellos.length);
        class clazz = (class) invoke;
        clazz.getmethod("say", string.class).invoke(null, "你好");
    }
}

以上就是java代码实现编译源文件的详细内容,更多关于java编译源文件的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com