在内部编译 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.
所以实际问题是
- 您的 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.
的反斜杠 ()
资源位置:
- Java ignores classpath
- 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.jar
和 OpenHFT
您应该使用可以处理您期望的分辨率的自定义 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
我正在开发一个项目,用户可以在其中以原始 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.
所以实际问题是
- 您的 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.
的反斜杠 ()资源位置:
- Java ignores classpath
- 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.jar
和 OpenHFT
您应该使用可以处理您期望的分辨率的自定义 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