在内部编译类时,类路径无法正常工作?

时间:2016-04-12 21:39:46

标签: java compilation classpath

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

以下是我的文件结构:

主.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文件时,它都会说“plugins.CommandProcessor”类不存在。

3 个答案:

答案 0 :(得分:3)

如你所述,

  

每当应用程序尝试编译此外部存储的.java时   文件,它说类" plugins.CommandProcessor"才不是   存在。

所以实际问题是

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

     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();
         }
     }
    

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

    类路径上的JAR

    java编译器和运行时不仅可以在单独的文件中搜索类,还可以在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,目录名=包名&#39;中搜索类。由于JAR本身就是一个目录,要在类搜索路径中包含JAR文件,该路径必须引用JAR本身,而不仅仅是包含JAR的目录。这是一个非常常见的错误。假设我在目录/ myclasses中有一个JAR myclasses.jar。要让Java编译器在这个jar中查找类,我们需要指定:

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

    而不仅仅是目录myclasses。

    多个类搜索目录

    在上面的例子中,我们告诉javac一次只搜索一个目录。实际上,您的类搜索路径将包含许多目录和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

答案 1 :(得分:1)

使用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"。您可以从文件中动态加载类。

观察:

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

答案 2 :(得分:1)

您应该使用可以处理您期望的分辨率的自定义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