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