Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java ServiceLoader explanation

I'm trying to understand the Java ServiceLoader concepts, working mechanism and concrete use cases, but find the official documentation too abstract and confusing.

First of all, documentation outlines services and service providers. Service is a set of interfaces and abstract classes packaged in a jar archive (API library). Service provider is a set of classes that implements or extends the API, packaged in a distinct jar file (Provider library).


So far so good, but then the documentation gets confusing.

For the purpose of loading, a service is represented by a single type, that is, a single interface or abstract class. (A concrete class can be used, but this is not recommended.) A provider of a given service contains one or more concrete classes that extend this service type with data and code specific to the provider. The provider class is typically not the entire provider itself but rather a proxy which contains enough information to decide whether the provider is able to satisfy a particular request together with code that can create the actual provider on demand. The details of provider classes tend to be highly service-specific; no single class or interface could possibly unify them, so no such type is defined here. The only requirement enforced by this facility is that provider classes must have a zero-argument constructor so that they can be instantiated during loading.

So what are actually Service type and Provider class? I get the impression, that service type is is a facade in the API library, and provider class is the implementation of this facade interface in the provider library, the class which ServiceLoader actually loads. Is this correct? But it still doesn't make much sense to me, how all the components ties together.

And what is meant by provider class being a proxy to decide whether the provider is able to satisfy a particular request together with code that can create the actual provider on demand? No unifying type could be defined where? Basically all of this paragraph is confusing, and I would like to hear more comprehensible explanation by a concrete example.


Then about provider configuration file...

A service provider is identified by placing a provider-configuration file in the resource directory META-INF/services. The file's name is the fully-qualified binary name of the service's type. The file contains a list of fully-qualified binary names of concrete provider classes, one per line ...

The configuration file naming a particular provider need not be in the same jar file or other distribution unit as the provider itself. The provider must be accessible from the same class loader that was initially queried to locate the configuration file; note that this is not necessarily the class loader from which the file was actually loaded.

Does this mean for an API with service type of org.foo.BarServiceType, in the classpath there must exists the provider jar with a class implementing this type and META-INF/services/org.foo.BarServiceType named provider configuration file listing this provider class, all accessible by the same Classloader which loaded ServiceLoader to find and bind the provider on the API?

From the classloader perspective, accessible means the provider configuration file and provider library may be provided outside of the package, upper from the hierarchy, i.e. from a container or other middleware.

The provider configuration file lists provider classes, and may be bundled in the provider package (why would it list multiple classes anyway if bundled?) or come from outside. But which approach is more common: to provide the configuration file among the provider, or provide the file listing a set of supported providers from within the API library itself? Or is the latter a misconception?


Finally about ServiceLoader

Where the ServiceLoader is actually instantiated and called to load the service provider? Does this happens in a factory method provided by the API library? For example, does LoggingFactory.getLogger(clazz) of SLF4J internally delegate to ServiceLoader, which uses reflection to read the provider configuration files and load the services?.

How does the service loading mechanism concerns situations, wheres there are either multiple providers with their configuration files present, or there is provider configuration file entry but not the class itself?

And what are some other concrete use cases of ServiceLoader outside of logging frameworks? How much it is utilized under the hood on popular frameworks like Java EE, Spring and Hibernate? What are some alternatives to service loading mechanism with loosely coupled API--provider binding, or is there?

like image 350
Tuomas Toivonen Avatar asked Jul 29 '17 09:07

Tuomas Toivonen


People also ask

How does Java ServiceLoader work?

Java's ServiceLoader is a facility to load service providers that implement a given service interface. Java's service loading mechanism can be extended through a library to reduce the boilerplate code and provide useful features like injecting service references and activating a given service provider.

What is service provider class in Java?

A service provider is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself.


1 Answers

The service type is the interface or abstract class which is passed to a ServiceLoader.load or ServiceLoader.loadInstalled. A provider is a concrete implementation of that interface or abstract class.

Since a service often consists of a large amount of functionality, it is useful if such large classes are not all loaded immediately when a ServiceLoader scans for them. Instead, a better design is a tiny class that provides access to the major functionality. For instance, ServiceLoader.load(FileSystemProvider.class) doesn’t load an entire library capable of handling a particular set of file systems; rather, it loads a FileSystemProvider object, which is capable of initializing that library, if and only if the application chooses to use it. This allows the provider itself to remain lightweight.

Does this mean for an API with service type of org.foo.BarServiceType, in the classpath there must exists the provider jar with a class implementing this type and META-INF/services/org.foo.BarServiceType named provider configuration file listing this provider class, all accessible by the same Classloader which loaded ServiceLoader to find and bind the provider on the API?

Yes. Normally this is pretty straightfoward; for example, the .jar file that contains the org.foo.BarServiceType implementation class also contains a META-INF/services/org.foo.BarServiceType entry whose contents consist of one line of text.

why would it list multiple classes anyway if bundled?

Some service providers can only handle some situations. An example would be the IIORegistry class (which doesn’t mention ServiceLoader, and in fact was present long before ServiceLoader was added to Java SE, but functions identically to ServiceLoader). There might be one implementation of ImageReaderSpi which provides ImageReaders for PNG, another ImageReaderSpi which provides ImageReaders for JPEG, and so on. Each such service provider class (that is, each concrete implementation of ImageReaderSpi) would have different logic in its canDecodeInput method, so the heavyweight ImageReader instances are not created unless the application actually needs them.

But which approach is more common: to provide the configuration file among the provider, or provide the file listing a set of supported providers from within the API library itself?

If I understand your question correctly, the answer is that in practice, the SPI descriptor is always in the same .jar file as the provider classes it names.

As for the last part of your question: I don’t think logger frameworks use ServiceLoader. For examples of usage of ServiceLoader, look at all the packages of Java SE which end with .spi (java.awt.im.spi, java.nio.channels.spi, java.nio.charset.spi, etc.). Most of them don’t say they rely on ServiceLoader, but they all describe their lookup behavior, and you’ll find it’s almost always identical to that of ServiceLoader.

How does the service loading mechanism concerns situations, wheres there are either multiple providers with their configuration files present, or there is provider configuration file entry but not the class itself?

In the case of multiple providers present in the classpath, ServiceLoader will simply return all of them in its Iterator.

For incorrect configuration files, a ServiceConfigurationError is thrown, from the next() method of the ServiceLoader’s Iterator. From the documentation:

Error thrown when something goes wrong while loading a service provider.

This error will be thrown in the following situations:

  • The format of a provider-configuration file violates the specification;
  • An IOException occurs while reading a provider-configuration file;
  • A concrete provider class named in a provider-configuration file cannot be found;
  • A concrete provider class is not a subclass of the service class;
  • A concrete provider class cannot be instantiated; or
  • Some other kind of error occurs.

In summary:

  • The service provider implementation class is usually in the same .jar as the META-INF/services descriptor file.
  • The service provider class is usually a lightweight class that allows the application to access the more heavyweight classes, if and only if the application decides it needs them.
  • Many parts of Java SE use ServiceLoader, particularly the *.spi packages.
  • If there are many implementations of a service, all are returned in the iterator.
  • Incorrect descriptors result in a ServiceConfigurationError.
like image 158
VGR Avatar answered Oct 16 '22 11:10

VGR