在内部编译 class 时类路径不起作用?

Classpath not working when compiling class internally?

我正在开发一个项目,用户可以在其中以原始 Java 代码存储插件。然后,我的应用程序将采用这些插件、编译它们并导入它们。 classes 基于存储在我的 jar 中的接口。但是,当我尝试使用 JavaCompiler.CompilationTask 运行 它时,它拒绝让我将当前 jar 添加到编译器的 class 路径。在这种情况下,当它尝试对其进行编译时,它的行为就好像该接口不可用于实现一样。

这是我的文件结构:

主 .jar 文件:

CommandProcessor.java
----------------------------------------------
package plugins;
public interface CommandProcessor {
    public String onCommand(String command);
}

然后我就有了加载插件的功能。

http://hastebin.com/jabacopeye.coffee(HasteBin 避免问题过于混乱)

这是其中一个用户插件的示例:

public class MyCommand implements plugins.CommandProcessor {
    @Override
    public String onCommand(String command){
            return "this is a test";
    }
}

每当应用程序尝试编译这个外部存储的 .java 文件时,它会提示 class "plugins.CommandProcessor" 不存在。

正如您所说,

Whenever the application tries to compile this externally stored .java file, it says that the class "plugins.CommandProcessor" does not exist.

所以实际问题是

  1. 您的 jar 文件结构和 java 文件位置不匹配,因为您在外部存储了 .java 文件。在您的代码中,当调用 task.call() 时,URLClassLoader 无法从 "./plugins/WebC/plugins/" 位置加载文件。

因此您的代码无法继续执行

 if (task.call()) {
     URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./plugins/WebC/plugins/").toURI().toURL()});
     Class<?> loadedClass = classLoader.loadClass(className);
     Object obj = loadedClass.newInstance();
     if (obj instanceof CommandProcessor) {
         CommandProcessor cmd = (CommandProcessor)obj;
         classLoader.close();
         return cmd;
     }else{
        classLoader.close();
     }
 }

所以你需要在class路径上指定jar。

class路径上的 JAR

java 编译器和运行-time 不仅可以在单独的文件中搜索classes,而且可以在JAR' archives. A JAR file can maintain its own directory structure, and Java follows exactly the same rules as for searching in ordinary directories. Specifically,目录名=包名'中搜索。因为 JAR 本身就是一个目录,所以要在 class 搜索路径中包含 JAR 文件,该路径必须引用 JAR 本身,而不仅仅是包含 JAR 的目录。这是一个很常见的错误。假设我在目录 /myclasses 中有一个 JAR myclasses.jar。要让 Java 编译器在这个 jar 中查找 classes,我们需要指定:

javac -classpath /myclasses/myclasses.jar ...

不仅是目录 myclasses.

多个 class 搜索目录

在上面的例子中,我们告诉 javac 一次只搜索一个目录。实际上,您的 class 搜索路径将包含大量目录和 JAR 存档。 javac 和 java 的 -classpath 选项允许指定多个条目,但请注意 Unix 和 Windows 系统的语法略有不同。 在 Unix 上,我们会这样做:

javac -classpath dir1:dir2:dir3 ...

而在 Windows 我们有:

javac -classpath dir1;dir2;dir3 ...

不同的原因是Windows使用冒号(:)字符作为文件名的一部分,所以它不能用作文件名分隔符。当然,目录分隔符也不同:Unix 的正斜杠 (/) 和 Windows.

的反斜杠 ()

资源位置:

  1. Java ignores classpath
  2. JARs on the classpath and Multiple class search directories

使用 OpenHFT 库 https://github.com/OpenHFT/Java-Runtime-Compiler您可以轻松解决您的问题。

public static void main(String[] args) 
      throws ClassNotFoundException, InstantiationException, IllegalAccessException {

  String className = "plugins.MyCommand";
  String javaCode = 
      "package plugins;\n" +
      "public class MyCommand implements plugins.CommandProcessor {"+
      "  @Override"+
      "  public String onCommand(String command){"+
      "          return \"this is a test\";"+
      "  }"+
      "}";

  Class aClass = CompilerUtils.CACHED_COMPILER
      .loadFromJava(className, javaCode);
  CommandProcessor processor = (CommandProcessor) aClass.newInstance();
System.out.println(processor.onCommand(""));
}

我 运行 这段代码和(正如预期的那样)输出"this is a test"。您可以从文件中动态加载 class。

观察:

如果选择此选项,您需要添加项目的依赖项 ${env.JAVA_HOME}/lib/tools.jarOpenHFT

您应该使用可以处理您期望的分辨率的自定义 ClassLoader 创建所有 类。您可以在任何外部 jar 文件内容以及所需接口的加载中进行编码。 JVM 会跟踪您定义的任何 类。

主程序将创建您即将编写的 ScriptingClassLoader,其中将包含解析脚本源代码:

 public class ScriptingClassLoader extends ClassLoader {

   public Class findClass(String name) {
     // you can fixup the desired api interface here
     // as you already know where they are

     byte[] b = loadClassData(name);
     return defineClass(name, b, 0, b.length);
   }

   private byte[] loadClassData(String name) {
     // go find your script source and compile
     // can be local disk, etc. You are only returning
     // the byte code here
   }
}

http://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html