简体   繁体   English

加载并编译外部 .java 文件

[英]Load and compile an external .java file

What is the best way to load a .java file call the main method and execute the code.加载.java文件的最佳方法是调用 main 方法并执行代码。 So for instance if I call C:/Test.java it should execute the code.因此,例如,如果我调用C:/Test.java它应该执行代码。

My code right now is the following:我现在的代码如下:

final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
final Iterable<<? extends JavaFileObject>compilationUnits = fileManager.getJavaFileObjectsFromStrings(Arrays.asList("C:/test.java"));
final JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);

final boolean success = task.call();

try {
    System.out.println("Compiled: " + success);
    fileManager.close();
} catch (final IOException e) {
    e.printStackTrace();
}

But it also generate a .class file in the folder which I don't want.但它也会在我不想要的文件夹中生成一个.class文件。 Is this code good for java 14 or there is a better way?这段代码对 java 14 有用还是有更好的方法?

First you can simplify the code a bit.首先,您可以稍微简化一下代码。 You use the StandardJavaFileManager and it imports the interface Auto/Closeable .您使用StandardJavaFileManager并导入接口Auto/Closeable So you can use the try-with-resource :所以你可以使用try-with-resource

final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();

try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) {
    final Iterable<<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromStrings(Arrays.asList("C:/test.java"));
    final JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);

    final boolean success = task.call();
} catch (IOException e) {
    e.printStackTrace();
}

Second you need the .class file to execute your java program.其次,您需要.class文件来执行您的 java 程序。 There exists a nice post .有一个不错的帖子 If you want to execute your test file and you use the command line, you should use the following commands:如果要执行测试文件并使用命令行,则应使用以下命令:

javac Test.java
java Test

// or a little bit shorter
java Test.java

If you want to know more about compilation, I can recommend this post or this post .如果你想了解更多关于编译的知识,我可以推荐这篇文章或这篇文章

By implementing JavaFileObject yourself and "patching" JavaFileManager you can keep the compiler in-memory, and then via exposing defineClass() of ClassLoader you can load the resulting byte array and launch main() via reflection.通过自己实现JavaFileObject并“修补” JavaFileManager您可以将编译器保留在内存中,然后通过公开ClassLoader defineClass() ,您可以加载生成的字节数组并通过反射启动main()

Here is a condensed variant, it actually does what you asked for, loads source code from file (that's one line with Files.readString() anyway).这是一个精简的变体,它实际上按照您的要求执行,从文件加载源代码Files.readString()无论如何,这是与Files.readString()一行)。 However that's something what you can probably leave entirely to the patched JavaFileManager , just I focused on keeping even the source code in memory, that's why there is the commented String src = "...";然而,这可能完全留给打过补丁的JavaFileManager ,只是我专注于将源代码保存在内存中,这就是为什么有注释String src = "..."; block.堵塞。

import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;

import javax.lang.model.element.*;
import javax.tools.*;

public class SOCompileTestDense {
  public static void main(String[] args) throws Exception {
    String name="Test";
//    String src="public class Test {\r\n" + 
//        "  public static void main(String[] args) {\r\n" + 
//        "    System.out.println(\"Hello \"+args[0]+\"!\");\r\n" + 
//        "  }\r\n" + 
//        "}";
    String src=Files.readString(Paths.get(name+".java"));
    System.out.println(src);
    List<JFO> sources=Arrays.asList(new JFO(name,src));
    Map<String,JFO> files=new HashMap<>();
    JavaCompiler jc=ToolProvider.getSystemJavaCompiler();
    JavaCompiler.CompilationTask ct=jc.getTask(null, new JFM(jc.getStandardFileManager(null, null, null),files), null, null, null, sources);
    System.out.println(ct.call());
    Class<?> clazz=new CL().defineClass(name, files.get(name).baos.toByteArray());
    clazz.getDeclaredMethod("main", String[].class).invoke(null, (Object)new String[] {"StackOverflow"});
  }
  
  static class JFO implements JavaFileObject {
    final String name;
    final String src;
    JFO(String name,String src){
      this.name=name;
      this.src=src;
    }

    public URI toUri() {
      try {
        return new URI(name);
      } catch(URISyntaxException use) {use.printStackTrace();};
      return null;
    }

    public String getName() {
      return name;
    }

    ByteArrayOutputStream baos;
    public OutputStream openOutputStream() throws IOException {
      baos=new ByteArrayOutputStream();
      return baos;
    }

    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
      return src;
    }

    public Kind getKind() {
      return Kind.SOURCE;
    }

    public boolean isNameCompatible(String simpleName, Kind kind) {
      return name.equals(simpleName);
    }

    public InputStream openInputStream() throws IOException {return null;}
    public Reader openReader(boolean ignoreEncodingErrors) throws IOException {return null;}
    public Writer openWriter() throws IOException {return null;}
    public long getLastModified() {return 0;}
    public boolean delete() {return false;}
    public NestingKind getNestingKind() {return null;}
    public Modifier getAccessLevel() {return null;}
  }
  
  static class JFM implements JavaFileManager {
    final JavaFileManager jfm;
    final Map<String, JFO> files;
    JFM(JavaFileManager jfm, Map<String, JFO> files){
      this.jfm=jfm;
      this.files=files;
    }

    public int isSupportedOption(String option) {
      return jfm.isSupportedOption(option);
    }

    public ClassLoader getClassLoader(Location location) {
      return jfm.getClassLoader(location);
    }

    public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
      return jfm.list(location, packageName, kinds, recurse);
    }

    public String inferBinaryName(Location location, JavaFileObject file) {
      return jfm.inferBinaryName(location, file);
    }

    public boolean handleOption(String current, Iterator<String> remaining) {
      return jfm.handleOption(current, remaining);
    }

    public boolean hasLocation(Location location) {
      return jfm.hasLocation(location);
    }

    public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
      return jfm.getJavaFileForInput(location, className, kind);
    }

    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
      JFO jfo=new JFO(className,null);
      files.put(className, jfo);
      return jfo;
    }
    
      public String inferModuleName(Location location) throws IOException {
        return jfm.inferModuleName(location);
      }

      public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
        return jfm.listLocationsForModules(location);
      }
      
    public boolean isSameFile(FileObject a, FileObject b) {return false;}
    public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {return null;}
    public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {return null;}
    public void flush() throws IOException {}
    public void close() throws IOException {}
  }
  
  static class CL extends ClassLoader {
    public Class<?> defineClass(String name,byte array[]) {
      return defineClass(name, array, 0, array.length);
    }
  }
}

And this is a tidy, short version, its output looks as follows:这是一个整洁、简短的版本,其输出如下所示:

 public class Test { public static void main(String[] args) { System.out.println("Hello "+args[0]+"!"); } } true Hello StackOverflow!

A much longer variant with verbose output is available on Ideone, https://ideone.com/DAXIlH , and if you download that one and remove the // in front of various System.out.println(); Ideone 上提供了一个更长的带有详细输出的变体, https: //ideone.com/DAXIlH ,如果您下载该变体并删除各种System.out.println();前面的// System.out.println(); calls, it will display a lot more stuff, which were simply too much for Ideone.调用,它会显示更多的东西,这对 Ideone 来说太多了。
Practically what I did was implementing all methods as throw new Error();实际上我所做的是将所有方法实现为throw new Error(); (see code on Ideone), and gradually filled them out with something meaningful whenever they got actually called. (参见 Ideone 上的代码),并在它们被实际调用时逐渐用一些有意义的东西填充它们。 Actually it was pretty straigtforward, the only gotcha I ran into was a couple default methods of the JavaFileManager interface.实际上它非常简单,我遇到的唯一问题是JavaFileManager接口的几个default方法。 As eclipse did not provide a stub implementation of those, it was a bit of surprise when code died without touching my throw new Error();由于 eclipse 没有提供这些的存根实现,当代码没有触及我的throw new Error();死了时,这有点令人惊讶throw new Error(); lines.线。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM