On 11/12/2024 19:31, David Lloyd wrote:
I'm once again experimenting with modularizing some of our runtime projects (Quarkus, WildFly) using JDK modules. Among the various problems I've encountered, this one is one I haven't cracked yet and seems like an oversight.

I am currently experimenting with a one-module-per-layer design which allows us certain capabilities (like lazy loading of modules, late binding of dependencies, and circularity in dependencies), and also allows us to create module graphs that can mix in "unnamed" modules for libraries which don't yet work properly as named modules, as well as automatic modules for libraries which can work as modules but don't yet define a descriptor.

However I have discovered an important difference between service loaders which load services by class loader (for example, those found in Microprofile and Jakarta frameworks) and those which load by module layer (none?) which is preventing things from working properly.

When I load a service by layer, after searching the given layer, the parent layers of that layer are then searched in order, recursing up the graph. (Sometimes the same service could be returned multiple times when there are diamonds in the layer graph, but that's a different problem.)

When I load a service by class loader, if the service is not found in the class loader's defined layers, then the search terminates and the service is considered not found, even if the service provider exists in a parent layer of one of those layers. This essentially means that all frameworks which are loading services by class loader (and using the class loader of the framework itself is typical here) will also need all of their implementations to coexist in one of the layers defined by that class loader - or else they must be defined in the unnamed module (in which case it will always be found).

Ironically, it *will* search the parent class loading delegation chain for layers which contain service implementations. The point of using module layers though is that they can have multiple parents, departing from this single-delegation model. In our class-loader-per-module setup, we do not use the parent class loader delegation as it would be meaningless in that context.

A good solution would be to modify service loading by class loader to also search parent layers of each layer defined to that class loader. This seems like it would be fairly easy to implement, but might possibly have subtle compatibility implications for very specific environments which have a module-per-class-loader situation along with multiple layers.

If ServiceLoader.load is invoked with a ClassLoader then it lazily iterates through the class loader delegation chain. If ServiceLoader.load is invoked with a ModuleLayer then it iterates through the module layers.  The former has deliberately limited support for cases where a class loader is used by a module layer but doing what you propose is adding more complexity for what seems like a really niche usage. I would worry that changing it along the lines you propose would result in something that only a few people could understand.

Have you looked at changing these frameworks to work with modules and specify a module layer to ServiceLoader.load?

-Alan

Reply via email to