Skip to content

Commit e16599e

Browse files
tsegismontgsmet
andauthored
Avoid calling ServiceLoader twice with same class loader (#5948)
* Avoid calling ServiceLoader twice with same class loader (#5903) * Avoid calling ServiceLoader twice with same class loader The ServiceLoader goes through all the jars to find its files and it seems like a good idea to avoid doing it twice for the same class loader. This is somehow related to the work we are doing in Quarkus for Leyden. * Simplify io.vertx.core.impl.ServiceHelper.loadFactories(java.lang.Class<T>, java.lang.ClassLoader) For readability Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Skip reloading factories if TCCL happens to be the same as the ServiceHelper's CL Saves the cost of scanning the classpath again Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Verify we don't try to get resource from CL twice when the TCCL is the same as the ServiceHelper's classloader Signed-off-by: Thomas Segismont <tsegismont@gmail.com> --------- Signed-off-by: Thomas Segismont <tsegismont@gmail.com> Co-authored-by: Thomas Segismont <tsegismont@gmail.com> * Fix test ServiceCommandLoaderTest.testNoCommandsWhenLoadedFromEmptyClassloader Bug was previously hidden by an incorrect test... Signed-off-by: Thomas Segismont <tsegismont@gmail.com> --------- Signed-off-by: Thomas Segismont <tsegismont@gmail.com> Co-authored-by: Guillaume Smet <guillaume.smet@gmail.com>
1 parent e700702 commit e16599e

3 files changed

Lines changed: 63 additions & 20 deletions

File tree

src/main/java/io/vertx/core/ServiceHelper.java

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
package io.vertx.core;
1313

14-
import java.util.*;
14+
import java.util.ArrayList;
15+
import java.util.Collection;
16+
import java.util.List;
17+
import java.util.ServiceLoader;
1518

1619
/**
1720
* A helper class for loading factories from the classpath.
@@ -45,29 +48,26 @@ public static <T> Collection<T> loadFactories(Class<T> clazz) {
4548
}
4649

4750
public static <T> Collection<T> loadFactories(Class<T> clazz, ClassLoader classLoader) {
48-
List<T> list = new ArrayList<>();
49-
ServiceLoader<T> factories;
5051
if (classLoader != null) {
51-
factories = ServiceLoader.load(clazz, classLoader);
52-
} else {
53-
// this is equivalent to:
54-
// ServiceLoader.load(clazz, TCCL);
55-
factories = ServiceLoader.load(clazz);
52+
return loadFactories(ServiceLoader.load(clazz, classLoader));
53+
}
54+
classLoader = Thread.currentThread().getContextClassLoader();
55+
List<T> list = loadFactories(ServiceLoader.load(clazz, classLoader));
56+
if (list.isEmpty() && classLoader != ServiceHelper.class.getClassLoader()) {
57+
// By default, ServiceLoader.load uses the TCCL, this may not be enough in environment dealing with
58+
// classloaders differently such as OSGi. So we should try to use the classloader having loaded this
59+
// class. In OSGi it would be the bundle exposing vert.x and so have access to all its classes.
60+
list = loadFactories(ServiceLoader.load(clazz, ServiceHelper.class.getClassLoader()));
5661
}
62+
return list;
63+
}
64+
65+
private static <T> List<T> loadFactories(ServiceLoader<T> factories) {
66+
List<T> list = new ArrayList<>();
5767
if (factories.iterator().hasNext()) {
5868
factories.iterator().forEachRemaining(list::add);
5969
return list;
60-
} else {
61-
// By default ServiceLoader.load uses the TCCL, this may not be enough in environment dealing with
62-
// classloaders differently such as OSGi. So we should try to use the classloader having loaded this
63-
// class. In OSGi it would be the bundle exposing vert.x and so have access to all its classes.
64-
factories = ServiceLoader.load(clazz, ServiceHelper.class.getClassLoader());
65-
if (factories.iterator().hasNext()) {
66-
factories.iterator().forEachRemaining(list::add);
67-
return list;
68-
} else {
69-
return Collections.emptyList();
70-
}
7170
}
71+
return list;
7272
}
7373
}

src/test/java/io/vertx/core/ServiceHelperTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@
1818
import org.junit.Test;
1919

2020
import java.io.File;
21+
import java.io.IOException;
2122
import java.lang.reflect.Method;
2223
import java.net.URL;
2324
import java.net.URLClassLoader;
2425
import java.util.Collection;
26+
import java.util.Enumeration;
27+
import java.util.concurrent.atomic.AtomicInteger;
2528

2629
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.junit.Assert.assertEquals;
2731

2832
/**
2933
* Check the service helper behavior.
@@ -128,4 +132,43 @@ public void loadFactoriesWithVertxClassloader() throws Exception {
128132
Collection collection = (Collection) method.invoke(null, someFactoryClass);
129133
assertThat(collection).hasSize(1);
130134
}
135+
136+
@Test
137+
public void shouldInvokeGetResourcesOnce() throws Exception {
138+
// We want to make sure we don't try to get resource from CL twice
139+
// When the TCCL is the same as the ServiceHelper's classloader
140+
141+
AtomicInteger invocations = new AtomicInteger();
142+
143+
// Load the ServiceHelper class from a custom classloader.
144+
ClassLoader custom = new URLClassLoader(new URL[]{
145+
new File(TestUtils.MAVEN_TARGET_DIR, "classes").toURI().toURL(),
146+
new File(TestUtils.MAVEN_TARGET_DIR, "test-classes").toURI().toURL(),
147+
serviceHelperFile.toURI().toURL(),
148+
}, null) {
149+
150+
@Override
151+
public Enumeration<URL> getResources(String name) throws IOException {
152+
invocations.incrementAndGet();
153+
return super.getResources(name);
154+
}
155+
};
156+
157+
Class serviceHelperClass = custom.loadClass(ServiceHelper.class.getName());
158+
Class notImplementedSPIClass = custom.loadClass(NotImplementedSPI.class.getName());
159+
assertThat(serviceHelperClass.getClassLoader()).isEqualTo(custom);
160+
assertThat(notImplementedSPIClass.getClassLoader()).isEqualTo(custom);
161+
162+
final ClassLoader originalTCCL = Thread.currentThread().getContextClassLoader();
163+
try {
164+
// Set TCCL to the CL that loaded the ServiceHelper class
165+
Thread.currentThread().setContextClassLoader(custom);
166+
Method method = serviceHelperClass.getMethod("loadFactories", Class.class);
167+
Collection collection = (Collection) method.invoke(null, notImplementedSPIClass);
168+
assertThat(collection).hasSize(0);
169+
assertEquals(1, invocations.get());
170+
} finally {
171+
Thread.currentThread().setContextClassLoader(originalTCCL);
172+
}
173+
}
131174
}

src/test/java/io/vertx/core/impl/launcher/ServiceCommandLoaderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public void testNoCommandsWhenLoadedFromEmptyClassloader() {
5555

5656
// We see the implementation from the classpath
5757
loader = new ServiceCommandFactoryLoader(classLoader);
58-
assertThat(loader.lookup()).isNotEmpty();
58+
assertThat(loader.lookup()).isEmpty();
5959
}
6060

6161

0 commit comments

Comments
 (0)