Skip to content

Commit

Permalink
Cache ServiceLoader-calls without result
Browse files Browse the repository at this point in the history
Iterating over the ServiceLoader results in class- and resource-loading, which becomes expensive if done extensively.
A common pattern used (in e.g. "com.sun.xml.ws.api.pipe.TransportTubeFactory::create") is to search first for multiple factory-implementations before falling back to a "default" factory / implementation:
  public Type exampleFunc(...) {
    for (_ : ServiceFinder.find(FactoryType1.class) { return if FactoryType1-impl found }
    for (_ : ServiceFinder.find(FactoryType2.class) { return if FactoryType2-impl found }
    return DEFAULT_FACTORY.createType(..);
  }
If there are no other implementations present besides the default-fallback-implementation, then each call to a method with this structure starts searching (again) for all non-default implementations - only to not find any implementing classes and finally falling back to the default-implementation.
Invoking such method-structures often, results in multiple unnecessary ServiceLoader-calls, because if the corresponding service-class and classloader are identical to a previous call and for this previous call the classloader was not able to determine the service-implementation, then it still won't be able to find it when retrying the ServiceLoader-call with the same parameters.
  • Loading branch information
DaScheid committed Jun 19, 2024
1 parent 94e5388 commit ceef811
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.sun.xml.ws.util;

import java.util.Objects;

public class ServiceClassLoaderCacheKey<T> {
private final Class<T> service;
private final ClassLoader loader;

public ServiceClassLoaderCacheKey(Class<T> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}

public Class<T> getService() {
return service;
}

public ClassLoader getLoader() {
return loader;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ServiceClassLoaderCacheKey)) return false;
ServiceClassLoaderCacheKey<?> that = (ServiceClassLoaderCacheKey<?>) o;
return Objects.equals(service, that.service) && Objects.equals(loader, that.loader);
}

@Override
public int hashCode() {
return Objects.hash(service, loader);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;


/**
Expand Down Expand Up @@ -116,22 +119,26 @@
public final class ServiceFinder<T> implements Iterable<T> {

private final @NotNull Class<T> serviceClass;
private final @NotNull ServiceLoader<T> serviceLoader;
private final @NotNull Iterable<T> serviceLoaderIterable;
private final @Nullable ComponentEx component;

private static final Map<ServiceClassLoaderCacheKey, Iterable> noResultServiceLoaderCache = new ConcurrentHashMap<>();
private static final ReentrantLock cacheLock = new ReentrantLock();
private static final int MAX_CACHE_SIZE = 100;

public static <T> ServiceFinder<T> find(@NotNull Class<T> service, @Nullable ClassLoader loader, Component component) {
ClassLoader cl = loader == null ? Thread.currentThread().getContextClassLoader() : loader;
return find(service, component, ServiceLoader.load(service, cl));
return find(service, component, retrieveServiceLoaderFromCacheOrCreateNew(service, cl));
}

public static <T> ServiceFinder<T> find(@NotNull Class<T> service, Component component, @NotNull ServiceLoader<T> serviceLoader) {
public static <T> ServiceFinder<T> find(@NotNull Class<T> service, Component component, @NotNull Iterable<T> serviceLoaderIterable) {
Class<T> svc = Objects.requireNonNull(service);
ServiceLoader<T> sl = Objects.requireNonNull(serviceLoader);
Iterable<T> sl = Objects.requireNonNull(serviceLoaderIterable);
return new ServiceFinder<>(svc, component, sl);
}

public static <T> ServiceFinder<T> find(@NotNull Class<T> service, Component component) {
return find(service, component, ServiceLoader.load(service, Thread.currentThread().getContextClassLoader()));
return find(service, component, retrieveServiceLoaderFromCacheOrCreateNew(service, Thread.currentThread().getContextClassLoader()));
}

/**
Expand Down Expand Up @@ -184,17 +191,17 @@ public static <T> ServiceFinder<T> find(@NotNull Class<T> service, @Nullable Cla
* @see #find(Class, ClassLoader)
*/
public static <T> ServiceFinder<T> find(@NotNull Class<T> service) {
return find(service, ServiceLoader.load(service, Thread.currentThread().getContextClassLoader()));
return find(service, retrieveServiceLoaderFromCacheOrCreateNew(service, Thread.currentThread().getContextClassLoader()));
}

public static <T> ServiceFinder<T> find(@NotNull Class<T> service, @NotNull ServiceLoader<T> serviceLoader) {
return find(service, ContainerResolver.getInstance().getContainer(), serviceLoader);
public static <T> ServiceFinder<T> find(@NotNull Class<T> service, @NotNull Iterable<T> serviceLoaderIterable) {
return find(service, ContainerResolver.getInstance().getContainer(), serviceLoaderIterable);
}

private ServiceFinder(Class<T> service, Component component, ServiceLoader<T> serviceLoader) {
private ServiceFinder(Class<T> service, Component component, Iterable<T> serviceLoaderIterable) {
this.serviceClass = service;
this.component = getComponentEx(component);
this.serviceLoader = serviceLoader;
this.serviceLoaderIterable = serviceLoaderIterable;
}

/**
Expand All @@ -209,7 +216,7 @@ private ServiceFinder(Class<T> service, Component component, ServiceLoader<T> se
@Override
@SuppressWarnings("unchecked")
public Iterator<T> iterator() {
Iterator<T> it = serviceLoader.iterator();
Iterator<T> it = serviceLoaderIterable.iterator();
return component != null
? new CompositeIterator<>(component.getIterableSPI(serviceClass).iterator(), it)
: it;
Expand Down Expand Up @@ -238,6 +245,28 @@ private static ComponentEx getComponentEx(Component component) {
return component != null ? new ComponentExWrapper(component) : null;
}

private static <T> Iterable<T> retrieveServiceLoaderFromCacheOrCreateNew(Class<T> service, ClassLoader cl) {
ServiceClassLoaderCacheKey<T> cacheKey = new ServiceClassLoaderCacheKey<>(service, cl);
Iterable<T> cachedServiceLoaderIterable = noResultServiceLoaderCache.get(cacheKey);
if (cachedServiceLoaderIterable != null) {
return cachedServiceLoaderIterable;
}
ServiceLoader<T> serviceLoader = ServiceLoader.load(service, cl);
if (!serviceLoader.iterator().hasNext()) {
cacheLock.lock();
try {
if (noResultServiceLoaderCache.size() >= MAX_CACHE_SIZE) {
noResultServiceLoaderCache.remove(noResultServiceLoaderCache.keySet().iterator().next());
}
noResultServiceLoaderCache.put(cacheKey, Collections.emptyList());
} finally {
cacheLock.unlock();
}
return Collections.emptyList();
}
return serviceLoader;
}

private static class ComponentExWrapper implements ComponentEx {

private final Component component;
Expand Down

0 comments on commit ceef811

Please sign in to comment.