问题
(Yes, this is hacky and probably not best practice, but it is the least bulky solution)
I have a project that involves several jars - a runnable launcher, a server, a wrapper for the server, and plugins for the server.
The launcher runs the wrapper by starting a new unconnected process, a child process, or just by instantiating it, depending on config. This should be unimportant with regards to the issue.
The wrapper uses URLClassLoader to load the server jar and start it, which works fine.
Before starting the server, the wrapper looks for plugins that contain certain classpaths/files normally used in the server, and loads them, patching the normal version of the class.
The problem is that the classloader wants to automatically resolve every class and import in the plugin patch classfiles, and the server has not been loaded yet.
I need to either prevent the classloader from resolving imports, or load the classes after the server and replace the already-loaded classes with them. As far as i know the second option is impossible without instability and bytecode manipulation?
回答1:
First of all, answering your title question:
Is it possible to load a class without loading referenced classes/imports?
Well you could choose not to resolve the class by passing false to loadClass. Details about what resolving a class entails can be found here. You could then do the resolution in a separate step by calling resolveClass explicitly.
However, if that doesn't get you what you want, I don't know if this is the only possible solution, and it's certainly awful (and there were definitely better ways to approach this from the start), but what if you do something like this:
I'm going to call your classes that serve as patches and must be loaded before the server "pre-load" classes / code. I'm going to call the components of your patches that have server dependencies but whose loading must be delayed until after the server loads "post-load" classes / code.
For each of your plugins:
Strip all the post-load stuff out of the patch class and move it to a different class, e.g.
PluginImplementation
or something. Then make an instance of that implementation class be a member of your plugin class, delegating any necessary member functions to it, but do not instantiate aPluginImplementation
right off the bat, and make the member field be of typeObject
(this is often described as an opaque pointer / the pimpl pattern). Essentially you're refactoring to use the pimpl idiom, where your pre-load stuff is coded directly, and your post-load stuff is actually delegated to a different class hidden behind anObject
and not initialized immediately. Your goal is to remove all dependencies on server classes from the plugin class itself, changing it to be the bare minimum necessary for the patch to just be loaded, but moving all the meat to an implementation class ultimately hidden behind an opaque pointer.Now load all of your plugins as normal. They should load now, and since all the post-load stuff that has server dependencies has been removed from them, the server classes won't be loaded.
Now have your plugins expose some sort of
serverLoaded()
, orinitializePlugin()
method or something like that. After the server class loads, go through and call these on each loaded plugin.Finally, in the initialization method from the previous step, have your plugin instantiate the post-load classes using
Class.forName().newInstance()
.
So, basically, you hide all your post-load stuff behind an opaque pointer, thus keeping it hidden from the class loader, then when it's time, you dynamically instantiate the various PluginImplementation
classes, thus making your plugins "fully complete" but allowing the server-dependent parts to be delay loaded.
The downside is this adds a few limitations and requires a lot of care. You need to make sure that none of your PluginImplementation
-delegated methods are called until after the server loads and the initialization functions are called, because the implementation classes won't have been instantiated yet.
I am sure there may be better options but this is the one I could think of that probably involves the least amount of work given what you already have. You'll have to move a lot of code around; an IDE like Eclipse or whatever can make spitting out delegates easy at least (just temporarily make the member field be a PluginImplementation
to help the IDE along then you can change it back to Object
after you generate all the code), but it should minimize *fundamental * changes to your existing architecture.
Here's another idea, although I'm not sure how well it fits in with your current code, or if it's even possible in your situation:
Basically, the goal here is to just make the plugins no longer be dependent on the server itself, by doing the following:
- Declare your server as an interface.
- Make your concrete server implementation implement that interface.
- Change all plugins to operate on that interface instead of on an instance of the server class itself.
- Make sure your server class doesn't declare any inner classes that the plugins may use: Break any inner classes out into the top level (IDE's like Eclipse can do this for you).
Now you'll have to figure out a way to tell each plugin the instance of the server it's working with, but the plugin will store this in a member with the type of the base interface.
This way, loading the plugins won't load the server itself, only the base interface.
I think this idea is a hell of a lot simpler and less hackier than the above, I just don't know if it's more or less feasible than the above.
Note that both of these options don't necessarily guarantee success, but in practice they'll probably work.
来源:https://stackoverflow.com/questions/39563803/is-it-possible-to-load-a-class-without-loading-referenced-classes-imports