Proposal: #ServiceLoaderEnhancements
Mark Reinhold
mark.reinhold at oracle.com
Mon Sep 12 15:13:01 UTC 2016
Issue summary
-------------
#ServiceLoaderEnhancements --- The module system encourages the use of
services for loose coupling, but the `ServiceLoader` class is not very
flexible. Consider enhancing it so that (1) neither a provider class
nor its no-args constructor need be declared `public`, (2) a provider
can be a singleton, or perhaps a collection of singletons, and (3) the
classes of the available providers can be inspected and selected prior
to instantiation. [13]
Proposal
--------
(1) No change: Continue to require service-provider classes, and their
no-args constructors, to be public.
Providers on the class path, and their no-args constructors, must
always be public. Allowing a class-path provider or its no-args
constructor to be non-public introduces a security risk, since an
adversary could place a `META-INF/services` entry elsewhere on the
class path in order to force that otherwise-inaccessible
constructor to be invoked.
For providers in named modules, allowing non-public provider
classes and non-public no-args constructors isn't really necessary
and is, in some ways, counterproductive. In a named module a
provider class, and its constructor, can be encapsulated by placing
the provider in an unexported package. Having to declare the
provider class and its no-args constructor `public` is a useful
declaration of intent, since they will be accessed by the service
loader itself, and hence useful documentation.
(2) Revise the `ServiceLoader` class so that if a candidate provider
class in a named module has a no-args public static method named
`provider` then that method is invoked and its result is taken as
the provider object. An exception is thrown if the method does
not have an appropriate return type. A static `provider` method
can either return a singleton or act as a factory method. If the
candidate provider class does not have such a method then its
public no-args constructor, if any, is invoked, per (1) above.
(An alternative is to use an annotation, say `@Provider`, to
identify provider-containing fields or provider-returning methods.
The cost of loading the annotation-reading code into the JVM is,
however, nontrivial, and since services are used widely within the
JDK itself we'd prefer not to impose that overhead on all
applications.)
(3) Decouple the loading of provider classes from the instantiation of
such classes: Introduce a new `ServiceLoader.Provider` interface
that pairs a provider class with a method to instantiate that
provider, and add a `stream()` method that returns a stream of
objects implementing that interface. A client can then filter
providers by inspecting the elements of the stream, examining each
provider class and perhaps the annotations thereon, and then
instantiating the class if appropriate. (Draft Javadoc source
attached below.)
[1] http://openjdk.java.net/projects/jigsaw/spec/issues/#ServiceLoaderEnhancements
--
/**
* Represents a service provider located by {@code ServiceLoader}.
*
* <p> When using a loader's {@link ServiceLoader#stream() stream()} method
* then the elements are of type {@code Provider}. This allows processing
* to select or filter on the provider class without instantiating the
* provider. </p>
*
* @param <S> The service type
* @since 9
*/
public static interface Provider<S> extends Supplier<S> {
/**
* Returns the provider class. There is no guarantee that this type is
* accessible and so attempting to instantiate it, by means of its
* {@link Class#newInstance() newInstance()} method for example, will
* fail when it is not accessible. The {@link #get() get()} method
* should instead be used to obtain the provider.
*
* @return The provider class
*/
Class<S> type();
/**
* Returns an instance of the provider.
*
* @return An instance of the provider.
*
* @throws ServiceConfigurationError
* If the service provider cannot be instantiated. The error
* cause will carry an appropriate cause.
*/
@Override S get();
}
/**
* Returns a stream that lazily loads the available providers of this
* loader's service. The stream elements are of type {@link Provider
* Provider}, the {@code Provider}'s {@link Provider#get() get} method
* must be invoked to get or instantiate the provider.
*
* <p> When processing the stream then providers that were previously
* loaded by stream operations are processed first, in load order. It then
* lazily loads any remaining providers. If a provider class cannot be
* loaded, can't be assigned to the service type, or some other error is
* thrown when locating the provider then it is wrapped with a {@code
* ServiceConfigurationError} and thrown by whatever method caused the
* provider to be loaded. </p>
*
* <p> If this loader's provider caches are cleared by invoking the {@link
* #reload() reload} method then existing streams for this service
* loader should be discarded. </p>
*
* <p> The following examples demonstrate usage. The first example
* creates a stream of providers, the second example is the same except
* that it sorts the providers by provider class name (and so locate all
* providers).
* <pre>{@code
* Stream<CodecSet> providers = ServiceLoader.load(CodecSet.class)
* .stream()
* .map(Provider::get);
*
* Stream<CodecSet> providers = ServiceLoader.load(CodecSet.class)
* .stream()
* .sorted(Comparator.comparing(p -> p.type().getName()))
* .map(Provider::get);
* }</pre>
*
* @return A stream that lazily loads providers for this loader's service
*
* @since 9
*/
public Stream<Provider<S>> stream() { ... }
More information about the jpms-spec-observers
mailing list