(是的,这是hacky,可能不是最好的做法,但它是最不起眼的解决方案)
我有一个涉及多个jar的项目 - 一个可运行的启动器,一个服务器,一个服务器的包装器,以及服务器的插件。
启动程序通过启动新的未连接进程,子进程或仅通过实例化它来运行包装器,具体取决于配置。对于这个问题,这应该是不重要的。
包装器使用URLClassLoader加载服务器jar并启动它,工作正常。
在启动服务器之前,包装器会查找包含服务器中通常使用的某些类路径/文件的插件,并加载它们,修补该类的正常版本。
问题是类加载器想要自动解析每个类并导入插件补丁类文件,而服务器尚未加载。
我需要阻止类加载器解析导入,或者在服务器之后加载类并用它们替换已经加载的类。据我所知,没有不稳定性和字节码操作,第二种选择是不可能的?
答案 0 :(得分:1)
首先,回答你的标题问题:
是否可以在不加载引用类/导入的情况下加载类?
您可以通过将false传递给loadClass
来选择解决该类。可以找到有关解决类所需内容的详细信息here。然后,您可以通过明确调用resolveClass
在单独的步骤中执行解决方案。
然而,如果那不能得到你想要的东西,我不知道这是否是唯一可行的解决方案,而且它肯定很糟糕(而且肯定有更好的方法可以接近这从一开始),但如果你这样做会怎么样:
我要打电话给你作为补丁的类,必须在服务器之前加载"预加载"课程/代码。我将调用具有服务器依赖性的补丁组件,但其加载必须延迟,直到服务器加载"后加载"课程/代码。
对于每个插件:
从补丁类中删除所有后加载的东西并将其移动到另一个类,例如PluginImplementation
或其他什么。然后使该实现类的实例成为您的插件类的成员,将任何必要的成员函数委托给它,但不要立即实例化PluginImplementation
,并使成员字段成为类型Object
(通常将其描述为an opaque pointer / the pimpl pattern)。基本上你是在重构使用pimpl习语,你的预加载内容是直接编码的,你的后载内容实际上被委托给隐藏在Object
后面的另一个类,而不是立即初始化。 您的目标是从插件类本身中删除服务器类的所有依赖项,将其更改为最小,该修补程序只需要加载,但是将所有的肉移动到最终隐藏在不透明指针后面的实现类。
现在正常加载所有插件。它们应该立即加载,并且由于已经从它们中删除了所有具有服务器依赖性的后加载内容,因此无法加载服务器类。
现在让你的插件暴露某种serverLoaded()
或initializePlugin()
方法或类似的东西。 在加载服务器类之后,通过并在每个加载的插件上调用它们。
最后,在上一步的初始化方法中,让您的插件使用Class.forName().newInstance()
实例化后加载类。
所以,基本上,你将所有后加载的东西隐藏在一个不透明的指针后面,从而将它隐藏在类加载器中,然后在它的时间,你动态地实例化各种PluginImplementation
类,从而使你的插件完全完整"但允许延迟加载与服务器相关的部分。
缺点是这增加了一些限制,需要很多照顾。在服务器加载并调用初始化函数之前,您需要确保没有调用任何PluginImplementation
- 委托方法,因为实现类还没有被实例化。
我确信可能有更好的选择,但这是我能想到的,可能涉及最少的工作量给定你已经拥有的。你必须移动很多代码;像Eclipse这样的IDE或其他什么方法可以使代表更容易吐出代表(只是暂时使成员字段成为PluginImplementation
以帮助IDE,然后在生成所有内容之后将其更改回Object
代码),但它应该最小化对现有架构的*基础*更改。
这是另一个想法,虽然我不确定它与您当前的代码有多好,或者在您的情况下甚至是可能的:
基本上,这里的目标是使插件不再依赖于服务器本身,执行以下操作:
现在,您必须找出一种方法来告诉每个插件它正在使用的服务器的实例,但插件会将其存储在具有基本接口类型的成员中。 / p>
这样,加载插件不会加载服务器本身,只加载基本接口。
我认为这个想法比上述更简单,更简单,我只是不知道它是否比上述更可行。
请注意,这两个选项都不一定guarantee success,但实际上它们可能会有效。