From ceef811c1e3046d2c0ae7986a16d4130ef158cb8 Mon Sep 17 00:00:00 2001 From: DaScheid <75588422+dascheid@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:11:26 +0200 Subject: [PATCH] Cache ServiceLoader-calls without result 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. --- .../ws/util/ServiceClassLoaderCacheKey.java | 34 +++++++++++++ .../com/sun/xml/ws/util/ServiceFinder.java | 51 +++++++++++++++---- 2 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 jaxws-ri/runtime/rt/src/main/java/com/sun/xml/ws/util/ServiceClassLoaderCacheKey.java diff --git a/jaxws-ri/runtime/rt/src/main/java/com/sun/xml/ws/util/ServiceClassLoaderCacheKey.java b/jaxws-ri/runtime/rt/src/main/java/com/sun/xml/ws/util/ServiceClassLoaderCacheKey.java new file mode 100644 index 000000000..55d66ade2 --- /dev/null +++ b/jaxws-ri/runtime/rt/src/main/java/com/sun/xml/ws/util/ServiceClassLoaderCacheKey.java @@ -0,0 +1,34 @@ +package com.sun.xml.ws.util; + +import java.util.Objects; + +public class ServiceClassLoaderCacheKey { + private final Class service; + private final ClassLoader loader; + + public ServiceClassLoaderCacheKey(Class service, ClassLoader loader) { + this.service = service; + this.loader = loader; + } + + public Class 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); + } +} diff --git a/jaxws-ri/runtime/rt/src/main/java/com/sun/xml/ws/util/ServiceFinder.java b/jaxws-ri/runtime/rt/src/main/java/com/sun/xml/ws/util/ServiceFinder.java index 5a16a6f36..165dd8ac6 100644 --- a/jaxws-ri/runtime/rt/src/main/java/com/sun/xml/ws/util/ServiceFinder.java +++ b/jaxws-ri/runtime/rt/src/main/java/com/sun/xml/ws/util/ServiceFinder.java @@ -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; /** @@ -116,22 +119,26 @@ public final class ServiceFinder implements Iterable { private final @NotNull Class serviceClass; - private final @NotNull ServiceLoader serviceLoader; + private final @NotNull Iterable serviceLoaderIterable; private final @Nullable ComponentEx component; + private static final Map noResultServiceLoaderCache = new ConcurrentHashMap<>(); + private static final ReentrantLock cacheLock = new ReentrantLock(); + private static final int MAX_CACHE_SIZE = 100; + public static ServiceFinder find(@NotNull Class 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 ServiceFinder find(@NotNull Class service, Component component, @NotNull ServiceLoader serviceLoader) { + public static ServiceFinder find(@NotNull Class service, Component component, @NotNull Iterable serviceLoaderIterable) { Class svc = Objects.requireNonNull(service); - ServiceLoader sl = Objects.requireNonNull(serviceLoader); + Iterable sl = Objects.requireNonNull(serviceLoaderIterable); return new ServiceFinder<>(svc, component, sl); } public static ServiceFinder find(@NotNull Class service, Component component) { - return find(service, component, ServiceLoader.load(service, Thread.currentThread().getContextClassLoader())); + return find(service, component, retrieveServiceLoaderFromCacheOrCreateNew(service, Thread.currentThread().getContextClassLoader())); } /** @@ -184,17 +191,17 @@ public static ServiceFinder find(@NotNull Class service, @Nullable Cla * @see #find(Class, ClassLoader) */ public static ServiceFinder find(@NotNull Class service) { - return find(service, ServiceLoader.load(service, Thread.currentThread().getContextClassLoader())); + return find(service, retrieveServiceLoaderFromCacheOrCreateNew(service, Thread.currentThread().getContextClassLoader())); } - public static ServiceFinder find(@NotNull Class service, @NotNull ServiceLoader serviceLoader) { - return find(service, ContainerResolver.getInstance().getContainer(), serviceLoader); + public static ServiceFinder find(@NotNull Class service, @NotNull Iterable serviceLoaderIterable) { + return find(service, ContainerResolver.getInstance().getContainer(), serviceLoaderIterable); } - private ServiceFinder(Class service, Component component, ServiceLoader serviceLoader) { + private ServiceFinder(Class service, Component component, Iterable serviceLoaderIterable) { this.serviceClass = service; this.component = getComponentEx(component); - this.serviceLoader = serviceLoader; + this.serviceLoaderIterable = serviceLoaderIterable; } /** @@ -209,7 +216,7 @@ private ServiceFinder(Class service, Component component, ServiceLoader se @Override @SuppressWarnings("unchecked") public Iterator iterator() { - Iterator it = serviceLoader.iterator(); + Iterator it = serviceLoaderIterable.iterator(); return component != null ? new CompositeIterator<>(component.getIterableSPI(serviceClass).iterator(), it) : it; @@ -238,6 +245,28 @@ private static ComponentEx getComponentEx(Component component) { return component != null ? new ComponentExWrapper(component) : null; } + private static Iterable retrieveServiceLoaderFromCacheOrCreateNew(Class service, ClassLoader cl) { + ServiceClassLoaderCacheKey cacheKey = new ServiceClassLoaderCacheKey<>(service, cl); + Iterable cachedServiceLoaderIterable = noResultServiceLoaderCache.get(cacheKey); + if (cachedServiceLoaderIterable != null) { + return cachedServiceLoaderIterable; + } + ServiceLoader 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;