enable ServiceLoader discovery across classloader layers

java.util.ServiceLoader uses findResources(), which was not
overriden in ReverseLookupClassLoader, causing resources available
in the descendant classloader not to be discovered when a service
loader instance was using the top classloader.
This commit is contained in:
Brice Jaglin 2020-05-08 11:44:03 +02:00
parent f5eae27c69
commit 331ca6ec9b
8 changed files with 72 additions and 0 deletions

View File

@ -8,7 +8,9 @@
package sbt.internal;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import sbt.util.Logger;
@ -64,6 +66,31 @@ final class ReverseLookupClassLoader extends ManagedClassLoader {
return url != null ? url : directDescendant.get().findResource(name);
}
@Override
public Enumeration<URL> findResources(String name) throws IOException {
final Enumeration<URL> parentResources = super.findResources(name);
if (directDescendant.get() == null) {
return parentResources;
}
final Enumeration<URL> directDescendantResources = directDescendant.get().findResources(name);
return new Enumeration<URL>() {
@Override
public boolean hasMoreElements() {
return parentResources.hasMoreElements() || directDescendantResources.hasMoreElements();
}
@Override
public URL nextElement() {
if (parentResources.hasMoreElements()) {
return parentResources.nextElement();
}
return directDescendantResources.nextElement();
}
};
}
void setup(final File tmpDir) {
setTempDir(tmpDir);
}

View File

@ -0,0 +1,4 @@
val dependency = project.settings(exportJars := true)
val descendant = project.dependsOn(dependency).settings(
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test"
)

View File

@ -0,0 +1,6 @@
package dependency;
public class Runnable implements java.lang.Runnable {
public void run() {}
}

View File

@ -0,0 +1,6 @@
package descendant
class Runnable extends java.lang.Runnable {
override def run(): Unit = ()
}

View File

@ -0,0 +1,20 @@
package test
import collection.JavaConverters._
import org.scalatest._
class ServiceLoaderTest extends FlatSpec {
val expected = Set(classOf[dependency.Runnable], classOf[descendant.Runnable])
val descendantClassLoader = classOf[descendant.Runnable].getClassLoader
val descendantRunnableLoader = java.util.ServiceLoader.load(classOf[java.lang.Runnable], descendantClassLoader)
val descendantLoadedClasses = descendantRunnableLoader.iterator().asScala.map(_.getClass).toSet
assert(descendantLoadedClasses == expected)
// this was the actual problem, when classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.AllLibraryJars
val dependencyClassLoader = classOf[dependency.Runnable].getClassLoader
val dependencyRunnableLoader = java.util.ServiceLoader.load(classOf[java.lang.Runnable], dependencyClassLoader)
val dependencyLoadedClasses = dependencyRunnableLoader.iterator().asScala.map(_.getClass).toSet
assert(dependencyLoadedClasses == expected)
}

View File

@ -0,0 +1,7 @@
> set descendant / Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.AllLibraryJars
> test
> set descendant / Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ScalaLibrary
> test