HHH-13551 Restucture ClassPathAndModulePathAggregatedServiceLoader

This does not change the behavior of the class at all: it simply
restructures the code to allow for the changes in the next commits.
This commit is contained in:
Yoann Rodière 2019-08-09 19:02:03 +02:00 committed by Sanne Grinovero
parent 3583074619
commit 31fb14e0d9
1 changed files with 57 additions and 47 deletions

View File

@ -145,17 +145,14 @@ abstract class AggregatedServiceLoader<S> {
* @param <S> The type of loaded services.
*/
private static class ClassPathAndModulePathAggregatedServiceLoader<S> extends AggregatedServiceLoader<S> {
private final ServiceLoader<S> aggregatedClassLoaderServiceLoader;
private final List<ServiceLoader<S>> delegates;
private Collection<S> cache = null;
private ClassPathAndModulePathAggregatedServiceLoader(AggregatedClassLoader aggregatedClassLoader,
Class<S> serviceContract) {
this.delegates = new ArrayList<>();
// Always try the aggregated class loader first
this.delegates.add( ServiceLoader.load( serviceContract, aggregatedClassLoader ) );
// Then also try the individual class loaders,
// because only them can instantiate services provided by jars in the module path
this.aggregatedClassLoaderServiceLoader = ServiceLoader.load( serviceContract, aggregatedClassLoader );
final Iterator<ClassLoader> clIterator = aggregatedClassLoader.newClassLoaderIterator();
while ( clIterator.hasNext() ) {
this.delegates.add(
@ -185,54 +182,67 @@ abstract class AggregatedServiceLoader<S> {
return cache;
}
@SuppressWarnings("unchecked")
private Collection<S> loadAll() {
Set<String> alreadyEncountered = new HashSet<>();
Set<S> result = new LinkedHashSet<>();
delegates.stream()
// Each loader's stream() method returns a stream of service providers: flatten these into a single stream
.flatMap( delegate -> {
try {
return (Stream<? extends Supplier<S>>) SERVICE_LOADER_STREAM_METHOD.invoke( delegate );
}
catch (RuntimeException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionFailure( "Error calling ServiceLoader.stream()", e );
}
} )
// For each provider, check its type to be sure we don't use a provider twice, then get the service
.forEach( provider -> {
Class<?> type;
try {
type = (Class<?>) PROVIDER_TYPE_METHOD.invoke( provider );
}
catch (RuntimeException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionFailure( "Error calling ServiceLoader.Provider.type()", e );
}
String typeName = type.getName();
/*
* We may encounter the same service provider multiple times,
* because the individual class loaders may give access to the same types
* (at the very least a single class loader may be present twice in the aggregated class loader).
* However, we only want to get the service from each provider once.
*
* ServiceLoader.stream() is useful in that regard,
* since it allows us to check the type of the service provider
* before the service is even instantiated.
*
* We could just instantiate every service and check their type afterwards,
* but 1. it would lead to unnecessary instantiation which could have side effects,
* in particular regarding class loading,
* and 2. the type of the provider may not always be the type of the service,
* and one provider may return different types of services
* depending on conditions known only to itself.
*/
if ( alreadyEncountered.add( typeName ) ) {
result.add( provider.get() );
}
} );
// Always try the aggregated class loader first
providerStream( aggregatedClassLoaderServiceLoader )
.forEach( provider -> collectServiceIfNotDuplicate( result, alreadyEncountered, provider ) );
/*
* Then also try the individual class loaders,
* because only them can instantiate services provided by jars in the module path.
*/
for ( ServiceLoader<S> delegate : delegates ) {
providerStream( delegate )
.forEach( provider -> collectServiceIfNotDuplicate( result, alreadyEncountered, provider ) );
}
return result;
}
@SuppressWarnings("unchecked")
private Stream<? extends Supplier<S>> providerStream(ServiceLoader<S> serviceLoader) {
try {
return ( (Stream<? extends Supplier<S>>) SERVICE_LOADER_STREAM_METHOD.invoke( serviceLoader ) );
}
catch (RuntimeException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionFailure( "Error calling ServiceLoader.stream()", e );
}
}
/*
* We may encounter the same service provider multiple times,
* because the individual class loaders may give access to the same types
* (at the very least a single class loader may be present twice in the aggregated class loader).
* However, we only want to get the service from each provider once.
*
* ServiceLoader.stream() is useful in that regard,
* since it allows us to check the type of the service provider
* before the service is even instantiated.
*
* We could just instantiate every service and check their type afterwards,
* but 1. it would lead to unnecessary instantiation which could have side effects,
* in particular regarding class loading,
* and 2. the type of the provider may not always be the type of the service,
* and one provider may return different types of services
* depending on conditions known only to itself.
*/
private void collectServiceIfNotDuplicate(Set<S> result, Set<String> alreadyEncountered, Supplier<S> provider) {
Class<?> type;
try {
type = (Class<?>) PROVIDER_TYPE_METHOD.invoke( provider );
}
catch (RuntimeException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionFailure( "Error calling ServiceLoader.Provider.type()", e );
}
String typeName = type.getName();
if ( alreadyEncountered.add( typeName ) ) {
result.add( provider.get() );
}
}
@Override
public void close() {
cache = null;