Skip to content

Commit ceef811

Browse files
committed
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.
1 parent 94e5388 commit ceef811

File tree

2 files changed

+74
-11
lines changed

2 files changed

+74
-11
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.sun.xml.ws.util;
2+
3+
import java.util.Objects;
4+
5+
public class ServiceClassLoaderCacheKey<T> {
6+
private final Class<T> service;
7+
private final ClassLoader loader;
8+
9+
public ServiceClassLoaderCacheKey(Class<T> service, ClassLoader loader) {
10+
this.service = service;
11+
this.loader = loader;
12+
}
13+
14+
public Class<T> getService() {
15+
return service;
16+
}
17+
18+
public ClassLoader getLoader() {
19+
return loader;
20+
}
21+
22+
@Override
23+
public boolean equals(Object o) {
24+
if (this == o) return true;
25+
if (!(o instanceof ServiceClassLoaderCacheKey)) return false;
26+
ServiceClassLoaderCacheKey<?> that = (ServiceClassLoaderCacheKey<?>) o;
27+
return Objects.equals(service, that.service) && Objects.equals(loader, that.loader);
28+
}
29+
30+
@Override
31+
public int hashCode() {
32+
return Objects.hash(service, loader);
33+
}
34+
}

jaxws-ri/runtime/rt/src/main/java/com/sun/xml/ws/util/ServiceFinder.java

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
import java.util.Collections;
2424
import java.util.Iterator;
2525
import java.util.List;
26+
import java.util.Map;
2627
import java.util.NoSuchElementException;
2728
import java.util.Objects;
2829
import java.util.ServiceLoader;
30+
import java.util.concurrent.ConcurrentHashMap;
31+
import java.util.concurrent.locks.ReentrantLock;
2932

3033

3134
/**
@@ -116,22 +119,26 @@
116119
public final class ServiceFinder<T> implements Iterable<T> {
117120

118121
private final @NotNull Class<T> serviceClass;
119-
private final @NotNull ServiceLoader<T> serviceLoader;
122+
private final @NotNull Iterable<T> serviceLoaderIterable;
120123
private final @Nullable ComponentEx component;
121124

125+
private static final Map<ServiceClassLoaderCacheKey, Iterable> noResultServiceLoaderCache = new ConcurrentHashMap<>();
126+
private static final ReentrantLock cacheLock = new ReentrantLock();
127+
private static final int MAX_CACHE_SIZE = 100;
128+
122129
public static <T> ServiceFinder<T> find(@NotNull Class<T> service, @Nullable ClassLoader loader, Component component) {
123130
ClassLoader cl = loader == null ? Thread.currentThread().getContextClassLoader() : loader;
124-
return find(service, component, ServiceLoader.load(service, cl));
131+
return find(service, component, retrieveServiceLoaderFromCacheOrCreateNew(service, cl));
125132
}
126133

127-
public static <T> ServiceFinder<T> find(@NotNull Class<T> service, Component component, @NotNull ServiceLoader<T> serviceLoader) {
134+
public static <T> ServiceFinder<T> find(@NotNull Class<T> service, Component component, @NotNull Iterable<T> serviceLoaderIterable) {
128135
Class<T> svc = Objects.requireNonNull(service);
129-
ServiceLoader<T> sl = Objects.requireNonNull(serviceLoader);
136+
Iterable<T> sl = Objects.requireNonNull(serviceLoaderIterable);
130137
return new ServiceFinder<>(svc, component, sl);
131138
}
132139

133140
public static <T> ServiceFinder<T> find(@NotNull Class<T> service, Component component) {
134-
return find(service, component, ServiceLoader.load(service, Thread.currentThread().getContextClassLoader()));
141+
return find(service, component, retrieveServiceLoaderFromCacheOrCreateNew(service, Thread.currentThread().getContextClassLoader()));
135142
}
136143

137144
/**
@@ -184,17 +191,17 @@ public static <T> ServiceFinder<T> find(@NotNull Class<T> service, @Nullable Cla
184191
* @see #find(Class, ClassLoader)
185192
*/
186193
public static <T> ServiceFinder<T> find(@NotNull Class<T> service) {
187-
return find(service, ServiceLoader.load(service, Thread.currentThread().getContextClassLoader()));
194+
return find(service, retrieveServiceLoaderFromCacheOrCreateNew(service, Thread.currentThread().getContextClassLoader()));
188195
}
189196

190-
public static <T> ServiceFinder<T> find(@NotNull Class<T> service, @NotNull ServiceLoader<T> serviceLoader) {
191-
return find(service, ContainerResolver.getInstance().getContainer(), serviceLoader);
197+
public static <T> ServiceFinder<T> find(@NotNull Class<T> service, @NotNull Iterable<T> serviceLoaderIterable) {
198+
return find(service, ContainerResolver.getInstance().getContainer(), serviceLoaderIterable);
192199
}
193200

194-
private ServiceFinder(Class<T> service, Component component, ServiceLoader<T> serviceLoader) {
201+
private ServiceFinder(Class<T> service, Component component, Iterable<T> serviceLoaderIterable) {
195202
this.serviceClass = service;
196203
this.component = getComponentEx(component);
197-
this.serviceLoader = serviceLoader;
204+
this.serviceLoaderIterable = serviceLoaderIterable;
198205
}
199206

200207
/**
@@ -209,7 +216,7 @@ private ServiceFinder(Class<T> service, Component component, ServiceLoader<T> se
209216
@Override
210217
@SuppressWarnings("unchecked")
211218
public Iterator<T> iterator() {
212-
Iterator<T> it = serviceLoader.iterator();
219+
Iterator<T> it = serviceLoaderIterable.iterator();
213220
return component != null
214221
? new CompositeIterator<>(component.getIterableSPI(serviceClass).iterator(), it)
215222
: it;
@@ -238,6 +245,28 @@ private static ComponentEx getComponentEx(Component component) {
238245
return component != null ? new ComponentExWrapper(component) : null;
239246
}
240247

248+
private static <T> Iterable<T> retrieveServiceLoaderFromCacheOrCreateNew(Class<T> service, ClassLoader cl) {
249+
ServiceClassLoaderCacheKey<T> cacheKey = new ServiceClassLoaderCacheKey<>(service, cl);
250+
Iterable<T> cachedServiceLoaderIterable = noResultServiceLoaderCache.get(cacheKey);
251+
if (cachedServiceLoaderIterable != null) {
252+
return cachedServiceLoaderIterable;
253+
}
254+
ServiceLoader<T> serviceLoader = ServiceLoader.load(service, cl);
255+
if (!serviceLoader.iterator().hasNext()) {
256+
cacheLock.lock();
257+
try {
258+
if (noResultServiceLoaderCache.size() >= MAX_CACHE_SIZE) {
259+
noResultServiceLoaderCache.remove(noResultServiceLoaderCache.keySet().iterator().next());
260+
}
261+
noResultServiceLoaderCache.put(cacheKey, Collections.emptyList());
262+
} finally {
263+
cacheLock.unlock();
264+
}
265+
return Collections.emptyList();
266+
}
267+
return serviceLoader;
268+
}
269+
241270
private static class ComponentExWrapper implements ComponentEx {
242271

243272
private final Component component;

0 commit comments

Comments
 (0)