Selecting the right service was: Eliminate silent dependencies

Jaroslav Tulach jaroslav.tulach at oracle.com
Wed Feb 22 09:14:23 PST 2012


Hello David.

>## 20. 2. 2012 15:06:19 ##<
> 1) There is only one implementation which is used system-wide (this is
> your "services as singletons" scenario).

Given your following comment ...

> If a service is indeed a true singleton
> in every sense (which corresponds to case #1), then the simplest
> solution is to have the API import its implementation (or at least
> import its services resources in order to be able to locate it) and use
> its own class loader to locate it.

... I don't think this is injectable singleton example. Of course if you have 
a singleton and it is supposed to have just a single implementation, then let 
the API instantiate the implementation using "new". You can even put the 
singleton API and its implementation into the same module. No reason to have 
anything modular, imho.

> 2) There are multiple implementations, however normally only one
> implementation is selected for the entire application.

This is probably what I call injectable singleton:
http://wiki.apidesign.org/wiki/Injectable_Singleton#Initialize

In contrast to case #1 it makes sense to use modularity here and separate the 
singleton API into one module and put the implementation into other(s).

> 3) There are multiple implementations, but each module may need to
> choose a different implementation.

> If a service is typically per-module (case #3) then each module would
> import (at run time) its preferred implementation and use its own class
> loader to search for it.

It will be better if the classloading tricks are eliminated as Jesse and Alan 
wrote. Imho it may be sufficient to get all instances in the system and 
somehow select the right one by querying its capabilities (e.g. 
getSupportedXYZ(), etc.).

> 4) There are multiple implementations, however implementations carry
> semantic meaning and generally must therefore be selected by some
> identifier.

> Note that this case is unique in
> that the conceptual service dependency is not just a base type to be
> implemented, but it also has a "flavor" component to specify behavior
> characteristics.

Actually NetBeans has an example of this style as well. The editor guards API
http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-
guards/overview-summary.html
benefits from the fact that requires/provides are just string tokens (not a 
class names). One registers the service capable to work for Java and then uses

provides token org.netbeans.api.editor.guards.Java;

Users of the editor guards API wanting to deal with Java then use:

/* guarantees editor guards API is on classpath */
requires org.netbeans.api.editor.guards; 
/* guarantees the Java implementation will be found using ServiceLoader */
requires token org.netbeans.api.editor.guards.Java; 

> If a service's behavior varies by implementation (case #4), as is the
> case for cryptographic, digest, and other security APIs (where the
> algorithm is significant to determining the behavior of the
> implementation) and several third-party APIs, then the application must
> import the implementations which it uses

A cryptography module providing SHA implementation would use something like

provides token java.security.MessageDigest.SHA

> All of these solutions have in common that the API to locate the
> implementation requires some notion of what class loader to search.

I don't think so. You can enumerate all implementations. Sure, it would be 
more efficient to prevent instantiating unneeded classes and select just the 
right one, but that is something ServiceLoader does not handle[1] in general, 
as far as I can tell.

-jt

[1] One of the reasons NetBeans use Lookup which can handle declarative 
services as demonstrated at http://wiki.apidesign.org/wiki/CompileTimeCache




More information about the jigsaw-dev mailing list