热加载 .jar 文件:随机 FileNotFoundException
Hot loading .jar files: random FileNotFoundException
我有一个 PluginManager
class 监视 ./plugins
目录以使用 WatchService
创建文件,然后使用静态方法 loadPlugin(File)
来自PluginLoader
class 在运行时加载新添加的 jar。文件夹中的所有 jar 也会在应用程序启动时加载并启动,在这种情况下一切顺利,即使有一堆插件。
但是当我将插件一个一个地放到文件夹中时,我得到了一个奇怪的行为:
- 第一个 jar 在 98% 的时间内都可以正常加载
- 第二只在5%左右
- 第三个只是在极少数情况下,但它确实发生了
我得到的是:
java.io.FileNotFoundException: .\plugins\test2.jar (Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird)
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.<init>(ZipFile.java:220)
at java.util.zip.ZipFile.<init>(ZipFile.java:150)
at java.util.jar.JarFile.<init>(JarFile.java:166)
at java.util.jar.JarFile.<init>(JarFile.java:130)
at PluginLoader.loadPlugin(PluginLoader.java:34)
at PluginManager$WatchQueueReader.run(PluginManager.java:118)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "FileWatcher" java.lang.NullPointerException
at PluginLoader.loadPlugin(PluginLoader.java:41)
at PluginManager$WatchQueueReader.run(PluginManager.java:118)
at java.lang.Thread.run(Thread.java:745)
说这个文件被另一个进程使用了。
PluginLoader:
public static BasePlugin loadPlugin(File pluginJar) {
Attributes attrib = null;
JarFile file = null;
try {
file = new JarFile(pluginJar);
Manifest manifest = file.getManifest();
attrib = manifest.getMainAttributes();
} catch (IOException e) {
e.printStackTrace();
}
String main = attrib.getValue(Attributes.Name.MAIN_CLASS);
String name = attrib.getValue("Plugin-Name");
if (main == null || name == null) {
System.out.println("Not a valid manifest: " + pluginJar.getName());
return null;
}
URLClassLoader loader = null;
try {
loader = URLClassLoader.newInstance(new URL[] { pluginJar.toURI().toURL() }, PluginLoader.class.getClassLoader());
} catch (MalformedURLException e2) {
e2.printStackTrace();
}
Class<?> cl = null;
try {
cl = Class.forName(main, true, loader);
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} finally {
try {
loader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Class<? extends BasePlugin> c = cl.asSubclass(BasePlugin.class);
Constructor<? extends BasePlugin> ctr = c.getConstructor(String.class, SystemManager.class);
return ctr.newInstance(name, this.sm);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
PluginManager 内的 WatchService:
private static class WatchQueueReader implements Runnable {
private WatchService watcher;
private PluginManager pm;
public WatchQueueReader(PluginManager pm, WatchService watcher) {
this.pm = pm;
this.watcher = watcher;
}
@Override
public void run() {
try {
WatchKey key = watcher.take();
while (key != null) {
for (WatchEvent<?> event : key.pollEvents()) {
switch (event.kind().toString()) {
case "ENTRY_CREATE":
Path dir = (Path) key.watchable();
Path fullPath = dir.resolve(event.context().toString());
BasePlugin plugin = PluginLoader.loadPlugin(fullPath.toFile());
if (plugin != null) {
this.pm.startPlugin(plugin);
}
break;
case "ENTRY_MODIFY":
break;
case "ENTRY_DELETE":
this.pm.stopPlugin(event.context().toString()); // TODO wrong name (.jar)
break;
default:
break;
}
}
key.reset();
key = watcher.take();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
BasePlugin 是插件扩展的抽象class。
文件仍在复制中,只是 WatchService 很快就找到了它。
事实上,它会在文件[的 inode] 创建后立即报告该文件存在,但尚未填充信息(字节数,你知道)。
我的第一个提示是,如果您遇到此异常,请等待几秒钟,然后重试。在 10-30 秒的徒劳尝试之后,您可以放弃,因为它可能已被删除。但是这部分需要微调,因为复制操作可能很慢。
我有一个 PluginManager
class 监视 ./plugins
目录以使用 WatchService
创建文件,然后使用静态方法 loadPlugin(File)
来自PluginLoader
class 在运行时加载新添加的 jar。文件夹中的所有 jar 也会在应用程序启动时加载并启动,在这种情况下一切顺利,即使有一堆插件。
但是当我将插件一个一个地放到文件夹中时,我得到了一个奇怪的行为:
- 第一个 jar 在 98% 的时间内都可以正常加载
- 第二只在5%左右
- 第三个只是在极少数情况下,但它确实发生了
我得到的是:
java.io.FileNotFoundException: .\plugins\test2.jar (Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird)
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.<init>(ZipFile.java:220)
at java.util.zip.ZipFile.<init>(ZipFile.java:150)
at java.util.jar.JarFile.<init>(JarFile.java:166)
at java.util.jar.JarFile.<init>(JarFile.java:130)
at PluginLoader.loadPlugin(PluginLoader.java:34)
at PluginManager$WatchQueueReader.run(PluginManager.java:118)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "FileWatcher" java.lang.NullPointerException
at PluginLoader.loadPlugin(PluginLoader.java:41)
at PluginManager$WatchQueueReader.run(PluginManager.java:118)
at java.lang.Thread.run(Thread.java:745)
说这个文件被另一个进程使用了。
PluginLoader:
public static BasePlugin loadPlugin(File pluginJar) {
Attributes attrib = null;
JarFile file = null;
try {
file = new JarFile(pluginJar);
Manifest manifest = file.getManifest();
attrib = manifest.getMainAttributes();
} catch (IOException e) {
e.printStackTrace();
}
String main = attrib.getValue(Attributes.Name.MAIN_CLASS);
String name = attrib.getValue("Plugin-Name");
if (main == null || name == null) {
System.out.println("Not a valid manifest: " + pluginJar.getName());
return null;
}
URLClassLoader loader = null;
try {
loader = URLClassLoader.newInstance(new URL[] { pluginJar.toURI().toURL() }, PluginLoader.class.getClassLoader());
} catch (MalformedURLException e2) {
e2.printStackTrace();
}
Class<?> cl = null;
try {
cl = Class.forName(main, true, loader);
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} finally {
try {
loader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Class<? extends BasePlugin> c = cl.asSubclass(BasePlugin.class);
Constructor<? extends BasePlugin> ctr = c.getConstructor(String.class, SystemManager.class);
return ctr.newInstance(name, this.sm);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
PluginManager 内的 WatchService:
private static class WatchQueueReader implements Runnable {
private WatchService watcher;
private PluginManager pm;
public WatchQueueReader(PluginManager pm, WatchService watcher) {
this.pm = pm;
this.watcher = watcher;
}
@Override
public void run() {
try {
WatchKey key = watcher.take();
while (key != null) {
for (WatchEvent<?> event : key.pollEvents()) {
switch (event.kind().toString()) {
case "ENTRY_CREATE":
Path dir = (Path) key.watchable();
Path fullPath = dir.resolve(event.context().toString());
BasePlugin plugin = PluginLoader.loadPlugin(fullPath.toFile());
if (plugin != null) {
this.pm.startPlugin(plugin);
}
break;
case "ENTRY_MODIFY":
break;
case "ENTRY_DELETE":
this.pm.stopPlugin(event.context().toString()); // TODO wrong name (.jar)
break;
default:
break;
}
}
key.reset();
key = watcher.take();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
BasePlugin 是插件扩展的抽象class。
文件仍在复制中,只是 WatchService 很快就找到了它。
事实上,它会在文件[的 inode] 创建后立即报告该文件存在,但尚未填充信息(字节数,你知道)。
我的第一个提示是,如果您遇到此异常,请等待几秒钟,然后重试。在 10-30 秒的徒劳尝试之后,您可以放弃,因为它可能已被删除。但是这部分需要微调,因为复制操作可能很慢。