What does a module.a binds module.b mean during the resolution?
Asked Answered
K

1

5

While performing a proof of concept for a problem - JPMS ServiceLoader does not work for me as expected.

I reached a state to understand the difference in how the two modules were resolved when providing a jar versus target classes to the module path. The console had a one-line difference that of:

base.service binds user.service file://.../user-service/target/classes/

What does this effectively mean? Couldn't really find such reference of this term in the spec or the conceptual draft.

Additionally, when could such behavior differ during automatic module resolution? (refer to this answer)

Keverne answered 7/8, 2021 at 14:16 Comment(0)
D
7

This most likely refers to the bind operation of resolveAndBind processing usesprovides relationships.

This method works exactly as specified by resolve except that the graph of resolved modules is augmented with modules induced by the service-use dependence relation.

More specifically, the root modules are resolved as if by calling resolve. The resolved modules, and all modules in the parent configurations, with service dependences are then examined. All modules found by the given module finders that provide an implementation of one or more of the service types are added to the module graph and then resolved as if by calling the resolve method. Adding modules to the module graph may introduce new service-use dependences and so the process works iteratively until no more modules are added.

So, binding takes place after resolving, but still before any code of the involved modules has been invoked. Namely, it doesn’t matter whether the ServiceLoader is actually used within the module(s) or not. But when it is used, it will utilize the already available information. So a lot of potential problems have been precluded at this point already. This is also the reason why we could build optimized module images pre-linking such services.

This, however, doesn’t work with automatic modules, as they don’t have uses directives. Without that information, service lookups can only be done when an actual use of ServiceLoader happens, just like with classes loaded through the old classpath.


Note that the issue in the linked Q&A is a bit different. According to the OP’s information, a module declaration has been used when compiling. But then, the OP ran the application using the -jar option which puts the specified jar file on the classpath and loads it from there, creating an unnamed module, rather than an automatic module. This ignores the compiled module-info and in absence of old-fashioned META-INF/services/… resources, no service implementation was found at all. The OP’s code was designed to fall back to the default service implementation if none had been found, which is indistinguishable from the scenario of finding the default service through the ServiceLoader.

The crucial differences of your answer are the start method -m base.service/base.service.ServiceUser, which will locate the base.service through the module path and launch its base.service.ServiceUser, as well as --add-modules …user.service, to ensure that the module providing the service will be added to the runtime environment. The latter is needed since there is no direct dependency from the launched module base.service to the user.service, so it wouldn’t be loaded automatically.

So in your answer’s setup, both modules are loaded as such. When they have a module-info, its provides directive will be processed to find the service implementation, otherwise, it’s an automatic module and must have a META-INF/services/… file. As said above, there is no equivalent to the uses directive for automatic modules, so you will never see a “binds” log entry for them. But they may still use the service lookup.

Dikmen answered 10/8, 2021 at 15:58 Comment(6)
Great answer. Thank you for reminding me about the unnamed module resolution via the code and command in OP's question. Follow-up question, in the last command of my answer .. java --show-module-resolution -p base-service/target/base-service-1.0-SNAPSHOT.jar:user-service/target/user-service-1.0-SNAPSHOT.jar --add-modules base.service,user.service -m base.service/base.service.ServiceUser user1, without the META-INF/services/…, the user service is loaded and not the default one, why would that be the case?Keverne
@Keverne I can only guess about the details of your setup. According to the output, only base.service was an automatic module, the user.service was not. So for the latter, the module-info has been used and if it contains a provides declaration, that’s the reason why its service implementation has been loaded.Dikmen
Not to guess further, the reproducible example is here on githubin case you would want to review. The module-info is part of both the modules(jars) and I was using Java-17 to build and execute.Keverne
Well, the base-service-1.0-SNAPSHOT.jar does not have a module-info, which is what makes it an automatic module. So it’s precisely what I supposed, one is an automatic module (without a META-INF/services/… resource), the other is not (having a module-info with provides). So one module’s service implementation is available in this setup.Dikmen
Yeah, spot on. The expectation difference and that I noticed it now, with maven-shade-plugin(at least with the present configuration) the module-info.class doesn't get packaged in the jar.Keverne
@Holger: Thank you for the explanation why -jar ignores the -p for ServiceLoader. To support learning - as I did - and analysis I uploaded my results (so far) to gitlabEmbolic

© 2022 - 2024 — McMap. All rights reserved.