Selective service instantiation

In the preceding examples, we have queried the ServiceLoader APIs to get all provider instances in an Iterable. We then looped through them and picked one. This is not a problem here because our services are simple and lightweight Java classes. That may not always be ideal. Imagine if services are more complex and need time and resources to instantiate. In such cases, you wouldn't want to instantiate every service provider when you know you won't use all of them.

The Java module system is quite smart about how it manages service instances in an application. First of all, all service instances are lazily loaded. In other words, service provider instances are not automatically instantiated when the application starts up. The runtime creates a service instance only when the type is needed, like when some consumer asks for instances of the service using ServiceProvider.load().

Secondly, any service instances that are created during the lifetime of the application are always cached. The service loader maintains this cache and keeps track of all the service instances that have been created. When the second consumer requests the service, the instance is fetched off of the cache directly. It also intelligently makes sure that the order of the service instances returned always includes the cached instances first.

Caching is automatic with service instances. If you want to clear the entire service provider cache during application execution, you can do so using the ServiceProvider.reload() API.

The ServiceLoader API has an option of streaming instances of an intermediate type called Provider, which can then be used to create service provider instances. Rather than getting all the service instances directly, what you get instead is Provider instances--one instance per service implementation found. You can then instantiate only the service providers you want by using the Provider.get() method on those instances.

As an example, consider the getProviderInstanceLazy() method on SortUtil. Rather than directly use ServiceLoader.load(SortUtil.class), we can instead use ServiceLoader.load(SortUtil.class).stream(), which returns a Stream of Provider instances:

    Stream<Provider<SortUtil>> providers =
ServiceLoader.load(SortUtil.class).stream();

The Provider instances can then be inspected for things such as annotations and other type information. Here, we are just sorting them by type name, which is silly, but it works as a minimal example:

    Stream<Provider<SortUtil>> providers = 
ServiceLoader.load(SortUtil.class).stream() .sorted(Comparator.comparing(p -> p.type().getName()));

At this time, no service instances are created. The actual instantiation of the service provider types happens when the Provider.get is called:

    SortUtil util = providers.map(Provider::get)
.findAny() .orElse(null);

In the preceding code, we call Provider.get() on each provider instance through the map function and pick one. This way, we can defer the creation of instances as well as selectively instantiate provider types by calling Provider.get only on the ones we need.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.61.218