Java 在运行时添加类路径
Java add Classpaths at runtime
这个问题在Whosebug里有很多答案吗?
但大多数人将 ClassLoader.getSystemClassLoader()
转换为 URLClassLoader 并且这不再起作用了。
系统类加载器必须找到 类。
还有其他解决方案吗?
- 无需重新启动 jar
- 无需创建自己的类加载器(在这种情况下,我必须用自己的类加载器替换系统类加载器)
现在必须只在启动时添加缺少的 classes/jars,我不想在 "Classpath".
的清单中添加这些
我用 premain-Method 找到了 Java Agent。这也可以很好地工作,但在这种情况下,我想在不调用 "java -javaagent:... -jar ..."
的情况下启动 premain 方法
目前我在开始时用缺少的类路径重新启动我的程序:
public class LibLoader {
protected static List<File> files = new LinkedList<>();
public static void add(File file) {
files.add(file);
}
public static boolean containsLibraries() {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
String[] classpaths = runtimeMxBean.getClassPath().split(System.getProperty("path.separator"));
List<File> classpathfiles = new LinkedList<>();
for(String string : classpaths) classpathfiles.add(new File(string));
for(File file : files) {
if(!classpathfiles.contains(file)) return false;
}
return true;
}
public static String getNewClassPaths() {
StringBuilder builder = new StringBuilder();
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
builder.append(runtimeMxBean.getClassPath());
for(File file : files) {
if(builder.length() > 0) builder.append(System.getProperty("path.separator"));
builder.append(file.getAbsolutePath());
}
return builder.toString();
}
public static boolean restartWithLibrary(Class<?> main, String[] args) throws IOException {
if(containsLibraries()) return false;
List<String> runc = new LinkedList<>();
runc.add(System.getProperty("java.home") + "\bin\javaw.exe");
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List<String> arguments = runtimeMxBean.getInputArguments();
runc.addAll(arguments);
File me = new File(LibLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath());
String classpaths = getNewClassPaths();
if(!classpaths.isEmpty()) {
runc.add("-cp");
runc.add(classpaths);
}
if(me.isFile()) {
runc.add("-jar");
runc.add(me.getAbsolutePath().replace("%20", " "));
} else {
runc.add(main.getName());
}
for(String arg : args) runc.add(arg);
ProcessBuilder processBuilder = new ProcessBuilder(runc);
processBuilder.directory(new File("."));
processBuilder.redirectOutput(Redirect.INHERIT);
processBuilder.redirectError(Redirect.INHERIT);
processBuilder.redirectInput(Redirect.INHERIT);
Process process = processBuilder.start();
try {
process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
希望有人有更好的解决方案。
Problem is, the classes must be found my the system ClassLoader
not by a new ClassLoader
.
听起来您当前的重新启动 JVM 的解决方案是唯一干净的方法。
无法更改系统 ClassLoader
,并且您无法在运行时向其添加额外的 JAR。
(如果您尝试使用反射来扰乱系统 classloader 的数据结构,最好的情况是它不可移植并且依赖于版本。最坏的情况是它要么容易出错......要么被 JVM 的运行时安全机制阻止。)
Johannes Kuhn 在评论中建议的解决方案无效。 java.system.class.loader
属性 在 JVM bootstrap 期间被查询。到您的应用程序 运行 时,对其进行更改将不会生效。我不相信他 Answer 中的方法也能奏效。
这是处理此问题的一种可能的替代方法...如果您能及早找出丢失的 JAR。
为自己编写一个启动器 class,执行以下操作:
- 保存命令行参数
- 找到应用程序 JAR 文件
- 从 MANIFEST.MF.
中提取 Main-Class 和 Class-Path 属性
- 根据上述...和其他特定于应用程序的逻辑找出真正的class路径。
- 使用正确的 class 路径创建一个新的
URLClassLoader
,并将系统 classloader 作为其父级。
- 用它加载主class。
- 使用反射找到主要的classes
main
方法。
- 通过保存命令行参数调用它。
这本质上是 Spring Bootstrap 和 OneJar(以及其他东西)用来处理 "jars in a jar" 问题等的方法。它避免启动 2 个虚拟机。
这个问题在Whosebug里有很多答案吗?
但大多数人将 ClassLoader.getSystemClassLoader()
转换为 URLClassLoader 并且这不再起作用了。
系统类加载器必须找到 类。
还有其他解决方案吗?
- 无需重新启动 jar
- 无需创建自己的类加载器(在这种情况下,我必须用自己的类加载器替换系统类加载器)
现在必须只在启动时添加缺少的 classes/jars,我不想在 "Classpath".
的清单中添加这些
我用 premain-Method 找到了 Java Agent。这也可以很好地工作,但在这种情况下,我想在不调用 "java -javaagent:... -jar ..."
目前我在开始时用缺少的类路径重新启动我的程序:
public class LibLoader {
protected static List<File> files = new LinkedList<>();
public static void add(File file) {
files.add(file);
}
public static boolean containsLibraries() {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
String[] classpaths = runtimeMxBean.getClassPath().split(System.getProperty("path.separator"));
List<File> classpathfiles = new LinkedList<>();
for(String string : classpaths) classpathfiles.add(new File(string));
for(File file : files) {
if(!classpathfiles.contains(file)) return false;
}
return true;
}
public static String getNewClassPaths() {
StringBuilder builder = new StringBuilder();
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
builder.append(runtimeMxBean.getClassPath());
for(File file : files) {
if(builder.length() > 0) builder.append(System.getProperty("path.separator"));
builder.append(file.getAbsolutePath());
}
return builder.toString();
}
public static boolean restartWithLibrary(Class<?> main, String[] args) throws IOException {
if(containsLibraries()) return false;
List<String> runc = new LinkedList<>();
runc.add(System.getProperty("java.home") + "\bin\javaw.exe");
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List<String> arguments = runtimeMxBean.getInputArguments();
runc.addAll(arguments);
File me = new File(LibLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath());
String classpaths = getNewClassPaths();
if(!classpaths.isEmpty()) {
runc.add("-cp");
runc.add(classpaths);
}
if(me.isFile()) {
runc.add("-jar");
runc.add(me.getAbsolutePath().replace("%20", " "));
} else {
runc.add(main.getName());
}
for(String arg : args) runc.add(arg);
ProcessBuilder processBuilder = new ProcessBuilder(runc);
processBuilder.directory(new File("."));
processBuilder.redirectOutput(Redirect.INHERIT);
processBuilder.redirectError(Redirect.INHERIT);
processBuilder.redirectInput(Redirect.INHERIT);
Process process = processBuilder.start();
try {
process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
希望有人有更好的解决方案。
Problem is, the classes must be found my the system
ClassLoader
not by a newClassLoader
.
听起来您当前的重新启动 JVM 的解决方案是唯一干净的方法。
无法更改系统 ClassLoader
,并且您无法在运行时向其添加额外的 JAR。
(如果您尝试使用反射来扰乱系统 classloader 的数据结构,最好的情况是它不可移植并且依赖于版本。最坏的情况是它要么容易出错......要么被 JVM 的运行时安全机制阻止。)
Johannes Kuhn 在评论中建议的解决方案无效。 java.system.class.loader
属性 在 JVM bootstrap 期间被查询。到您的应用程序 运行 时,对其进行更改将不会生效。我不相信他 Answer 中的方法也能奏效。
这是处理此问题的一种可能的替代方法...如果您能及早找出丢失的 JAR。
为自己编写一个启动器 class,执行以下操作:
- 保存命令行参数
- 找到应用程序 JAR 文件
- 从 MANIFEST.MF. 中提取 Main-Class 和 Class-Path 属性
- 根据上述...和其他特定于应用程序的逻辑找出真正的class路径。
- 使用正确的 class 路径创建一个新的
URLClassLoader
,并将系统 classloader 作为其父级。 - 用它加载主class。
- 使用反射找到主要的classes
main
方法。 - 通过保存命令行参数调用它。
这本质上是 Spring Bootstrap 和 OneJar(以及其他东西)用来处理 "jars in a jar" 问题等的方法。它避免启动 2 个虚拟机。