如何使动态加载的插件能够感知 Web 应用程序
我决定在 glassfish Web 应用程序中实现动态类加载,作为尝试的一种方式,并支持可以由 Web 应用程序在运行时加载和执行的小插件。
我添加了以下类:
public class PluginManager {
private static final String dropBoxDir = "file:///path/to/dropbox/";
private static final URLClassLoader dropBoxClassLoader;
static {
try {
URL dropBoxURL = new URL(dropBoxDir);
dropBoxClassLoader = URLClassLoader.newInstance(new URL[]{dropBoxURL});
}
catch (MalformedURLException mue) {
throw new RuntimeException("MalformedURLException thrown during PluginManager initialization - the hardcoded URL " + dropBoxDir + " must be invalid.", mue);
}
}
//this method is called by a web service
public static void runPluginFromDropBox(String fullClassName) {
try {
//load the plugin class
Class<?> pluginClass = dropBoxClassLoader.loadClass(fullClassName);
//instantiate it
Runnable plugin = (Runnable)pluginClass.newInstance();
//call its run() method
plugin.run();
}
catch (ClassNotFoundException cnfe) {
throw new RuntimeException("The class file for " + fullClassName + " could not be located at the designated directory (" + dropBoxDir + "). Check that the specified class name is correct, and that its file is in the right location.", cnfe);
}
catch (InstantiationException ie) {
throw new RuntimeException("InstantiationException thrown when attempting to instantiate the plugin class " + fullClassName + " - make sure it is an instantiable class with a no-arg constructor.", ie);
}
catch (IllegalAccessException iae) {
throw new RuntimeException("IllegalAccessException thrown when attempting to instantiate the plugin class " + fullClassName + " - make sure the class and its no-arg constructor have public access.", iae);
}
catch (ClassCastException cce) {
throw new RuntimeException("Plugin instance could not be cast to Runnable - plugin classes must implement this interface.", cce);
}
}
}
然后在一个单独的项目中,我创建了一个测试插件:
public class TestPlugin implements Runnable {
@Override
public void run() {
System.out.println("plugin code executed");
}
}
我部署了 Web 应用程序,然后将 TestPlugin
编译为 .class
文件并将其放入指定的文件夹。我调用了一个 Web 服务,该服务使用类名命中 runPluginFromDropBox()
并获得了预期的输出。
这一切都作为概念证明,但我的插件实际上毫无用处,除非它能够了解我的 Web 应用程序的类。我从那时起就读到 .war
仅作为一个独立的应用程序,而不是在其他库的类路径上,这对于这个小副项目来说不是一个好兆头。
我查看了这个讨论: 使用插件扩展 Java Web 应用程序 并得到我感觉自己无缘无故地陷入了设计挑战的泥沼,应该扭转局面。然而,这篇文章有点旧,而且是 Tomcat 特定的,所以我只是想问是否有任何简单的方法可以让我在没有一些复杂的第三方框架的情况下解决这个问题。
I decided to implement dynamic class loading in my glassfish web application, as a way of trying it out and to support small plugins that could be loaded and executed by the web app at runtime.
I added the following class:
public class PluginManager {
private static final String dropBoxDir = "file:///path/to/dropbox/";
private static final URLClassLoader dropBoxClassLoader;
static {
try {
URL dropBoxURL = new URL(dropBoxDir);
dropBoxClassLoader = URLClassLoader.newInstance(new URL[]{dropBoxURL});
}
catch (MalformedURLException mue) {
throw new RuntimeException("MalformedURLException thrown during PluginManager initialization - the hardcoded URL " + dropBoxDir + " must be invalid.", mue);
}
}
//this method is called by a web service
public static void runPluginFromDropBox(String fullClassName) {
try {
//load the plugin class
Class<?> pluginClass = dropBoxClassLoader.loadClass(fullClassName);
//instantiate it
Runnable plugin = (Runnable)pluginClass.newInstance();
//call its run() method
plugin.run();
}
catch (ClassNotFoundException cnfe) {
throw new RuntimeException("The class file for " + fullClassName + " could not be located at the designated directory (" + dropBoxDir + "). Check that the specified class name is correct, and that its file is in the right location.", cnfe);
}
catch (InstantiationException ie) {
throw new RuntimeException("InstantiationException thrown when attempting to instantiate the plugin class " + fullClassName + " - make sure it is an instantiable class with a no-arg constructor.", ie);
}
catch (IllegalAccessException iae) {
throw new RuntimeException("IllegalAccessException thrown when attempting to instantiate the plugin class " + fullClassName + " - make sure the class and its no-arg constructor have public access.", iae);
}
catch (ClassCastException cce) {
throw new RuntimeException("Plugin instance could not be cast to Runnable - plugin classes must implement this interface.", cce);
}
}
}
Then in a separate project, I created a test plugin:
public class TestPlugin implements Runnable {
@Override
public void run() {
System.out.println("plugin code executed");
}
}
I deployed the web application, then compiled TestPlugin
into a .class
file and dropped it into the designated folder. I called a web service that hits runPluginFromDropBox()
with the class name and got the expected output.
This all worked as a proof of concept, but my plugin is effectively useless unless it can be made aware of my web application's classes. I've since read that .war
is intended only as a standalone application, and not meant to be on other libraries' classpaths, which doesn't bode well for this little side-project.
I had a look at this discussion: Extending Java Web Applications with plugins and get the feeling I'm wading into a swamp of design challenges for no huge reason and should turn around. However that post is kind of old and is Tomcat-specific, so I just thought I'd ask if there's any straightforward way for me to approach this without some elaborate third party framework.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
war 文件的类由特定的类加载器加载,以将 war 与部署在同一服务器上的其他 Web 应用程序隔离,并能够取消部署 war。
要了解 webapp 类,您的插件类加载器应该将 webapp 类加载器作为其父级。我不知道使用额外的类加载器可能会遇到的所有问题,但我怀疑当容器取消部署并重新部署 web 应用程序时,您可能会遇到内存泄漏和其他令人讨厌的问题(内存中保存的静态值等)。容器之间也可能存在差异。
The classes of a war file are loaded by a specific classloader, to isolate the war from other webapps deployed on the same server, and to be able to undeploy the war.
To be aware of the webapp classes, your plugin classloader should have the webapp classloader as its parent. I'm not aware of all the problems you might have by using an additional classloader, but I suspect you might have memory leaks and other nasty problems (static values kept in memory, etc.) when the container will undeploy and redeploy the webapp. And you might have differences between containers as well.