Proposal: #ServiceLoaderEnhancements

Stephen Colebourne scolebourne at joda.org
Mon Sep 12 22:08:41 UTC 2016


My preference of these three options is option 2. However, I would
prefer to see the method name be defined by the module definition as
previously suggested [1]:

module {
 provides java.sql.Driver with com.mysql.jdbc.Driver::instance;
}

This would allow ::staticMethod or ::staticConstant, and avoid
hard-coding "provider" as a special method name.

(For my motivating use case, the Chronology classes of ThreeTen-Extra
[2] it would be distracting to have a method named provider(). While a
separate class in a non-exported package would be possible to hold the
provider() method, as a solution that seems like overkill relative to
the syntax above.)

Stephen

[1] http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2016-July/000535.html
[2] https://github.com/ThreeTen/threeten-extra/tree/master/src/main/java/org/threeten/extra/chrono


On 12 September 2016 at 16:13, Mark Reinhold <mark.reinhold at oracle.com> wrote:
> 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 jigsaw-dev mailing list