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)
This most likely refers to the bind operation of resolveAndBind
processing uses
—provides
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, withservice dependences
are then examined. All modules found by the given module finders thatprovide
an implementation of one or more of the service types are added to the module graph and then resolved as if by calling theresolve
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.