儿童优先类加载器和服务提供者接口(SPI)

huangapple go评论82阅读模式
英文:

Child first class loader and Service Provider Interface (SPI)

问题

我找到了一个自定义类加载器,它按照子优先原则加载类。它能够正常工作,但我遇到了以下问题。
当我尝试加载使用 SPI 的类时,我会遇到异常:

Exception in thread "main" java.util.ServiceConfigurationError: test.spi.SayMyNameProvider: test.spi.ImplProvider 不是子类型

我创建了一个简单的 SPI 项目,包括模块:spi-api、spi-impl 和 spi-app。

当我使用 URLClassLoader 时,它能够正常工作;然而,当我使用 ChildFirstClassLoader 时,会出现上述异常:

public class TestMain {
    public static void main(String[] args) throws MalformedURLException {

        //!!! 注释掉 ChildFirstClassLoader,并取消注释 URLClassLoader 以获得正确的行为
        ChildFirstClassLoader classLoader = getCustomClassLoader();
        //URLClassLoader classLoader = getUrlClassLoader();

        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(classLoader);

        try {
            List<SayMyNameProvider> providers = Speaker.providers();
            for (SayMyNameProvider provider : providers) {
                SayMyNameManager sayMyNameManager = provider.create();
                sayMyNameManager.sayIt("main");
            }
            System.out.println("done");

        } finally {
            Thread.currentThread().setContextClassLoader(contextClassLoader);
        }
    }

    private static ChildFirstClassLoader getCustomClassLoader() throws MalformedURLException {
        URL[] urls = getUrls();

        return new ChildFirstClassLoader(urls);
    }

    private static URLClassLoader getUrlClassLoader() throws MalformedURLException {
        URL[] urls = getUrls();

        return new URLClassLoader(urls);
    }

    private static URL[] getUrls() throws MalformedURLException {
        File spiImpl = Paths.get("spi-impl", "target", "spi-impl-1.0.0-SNAPSHOT.jar").toFile();
        File spiApi = Paths.get("spi-api", "target", "spi-api-1.0.0-SNAPSHOT.jar").toFile();

        URL[] urls = new URL[2];
        urls[0] = spiImpl.toURI().toURL();
        urls[1] = spiApi.toURI().toURL();
        return urls;
    }
}

也许有人之前已经遇到过这个问题,并知道如何解决。我会感谢任何帮助或建议。

英文:

I found a custom class loader, which loads classes by child-first principle. And it works fine, but I faced with the following issue.
When I try to load classes that use SPI I get the exception:

Exception in thread "main" java.util.ServiceConfigurationError: test.spi.SayMyNameProvider: test.spi.ImplProvider not a subtype

I created simple SPI project with modules: spi-api, spi-impl and spi-app.

And it works when I use URLClassLoader, however whenever I use ChildFirstClassLoader I get the exception mentioned above:

public class TestMain {
    public static void main(String[] args) throws MalformedURLException {

        //!!! comment ChildFirstClassLoader and uncomment URLClassLoader to get the correct behavior 
        ChildFirstClassLoader classLoader = getCustomClassLoader();
        //URLClassLoader classLoader = getUrlClassLoader();

        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(classLoader);

        try {
            List<SayMyNameProvider> providers = Speaker.providers();
            for (SayMyNameProvider provider : providers) {
                SayMyNameManager sayMyNameManager = provider.create();
                sayMyNameManager.sayIt("main");
            }
            System.out.println("done");

        } finally {
            Thread.currentThread().setContextClassLoader(contextClassLoader);
        }
    }

    private static ChildFirstClassLoader getCustomClassLoader() throws MalformedURLException {
        URL[] urls = getUrls();

        return new ChildFirstClassLoader(urls);
    }

    private static URLClassLoader getUrlClassLoader() throws MalformedURLException {
        URL[] urls = getUrls();

        return new URLClassLoader(urls);
    }

    private static URL[] getUrls() throws MalformedURLException {
        File spiImpl = Paths.get("spi-impl", "target", "spi-impl-1.0.0-SNAPSHOT.jar").toFile();
        File spiApi = Paths.get("spi-api", "target", "spi-api-1.0.0-SNAPSHOT.jar").toFile();

        URL[] urls = new URL[2];
        urls[0] = spiImpl.toURI().toURL();
        urls[1] = spiApi.toURI().toURL();
        return urls;
    }
}

Maybe someone has already faced this problem before and knows how to solve it. I would be grateful for any help or advice.

答案1

得分: 0

经过3天的等待,我终于得到了对我的问题的回答。并且它说我很蠢 儿童优先类加载器和服务提供者接口(SPI) 因为在那篇文章的最后,作者提供了一个正确行为的示例,它在我的情况下是有效的。
然而,在我使用slf4j和logback依赖的另一个测试中它不起作用。但令我惊讶的是,没有系统类加载器的代码是有效的。
简言之,我尝试使用不同版本的slf4j和logback。

Pom.xml:

<dependencies>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>

TestMain.class

public class TestMain {
    private static Logger log = LoggerFactory.getLogger(TestMain.class);

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, MalformedURLException {
        log.info("Hello");

        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

        ChildFirstClassLoader2 classLoader = new ChildFirstClassLoader2(getUrls());

        Thread.currentThread().setContextClassLoader(classLoader);

        try {
            Class<?> testClass = classLoader.loadClass("petrovskyi.TestClass");
            Object o = testClass.getDeclaredConstructor().newInstance();
            Method test = testClass.getMethod("test");
            test.setAccessible(true);
            test.invoke(o);

        } finally {
            Thread.currentThread().setContextClassLoader(contextClassLoader);
        }
    }

    private static URL[] getUrls() throws MalformedURLException {
        File libDir = Paths.get("src", "main", "resources", "testClasses").toFile();

        URL[] urls;

        List<URL> urlsList = new ArrayList<>();

        URL classUrl = libDir.toURI().toURL();
        urlsList.add(classUrl);

        try (Stream<Path> walk = Files.walk(libDir.toPath())) {
            List<File> result = walk.map(Path::toFile)
                    .filter(x -> x.getName().endsWith(".jar"))
                    .collect(Collectors.toList());

            for (File jarFile : result) {
                urlsList.add(jarFile.toURI().toURL());
            }
        } catch (IOException e) {
            throw new RuntimeException("Error while walking through " + libDir + " to find jar files", e);
        }

        urls = urlsList.toArray(new URL[0]);
        return urls;
    }
}

上面的代码尝试从某个目录获取所有的JAR文件。这些JAR文件如下:

  • logback-classic-1.3.0-alpha5.jar
  • logback-core-1.3.0-alpha5.jar
  • slf4j-api-2.0.0-alpha1.jar

TestClass.class

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import org.slf4j.spi.DefaultLoggingEventBuilder;
import org.slf4j.spi.LoggingEventBuilder;

public class TestClass {
    private static Logger log = LoggerFactory.getLogger(TestClass.class);

    public TestClass() {
    }

    public void test() {
        LoggingEventBuilder loggingEventBuilder = new DefaultLoggingEventBuilder(log, Level.ERROR);
        loggingEventBuilder.log(" =========== Hello, World! ===========");
        log.info("Test from test class");
    }
}

上面的类使用了在pom.xml中提到的slf4j版本中不存在的LoggingEventBuilder。

ChildFirstClassLoader2.class

public class ChildFirstClassLoader2 extends URLClassLoader {

    public ChildFirstClassLoader2(URL[] urls) {
        super(urls);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> loadedClass = findLoadedClass(name);
        if (loadedClass == null) {

            try {
                if (loadedClass == null) {
                    loadedClass = findClass(name);
                }

            } catch (ClassNotFoundException e) {
                loadedClass = super.loadClass(name, resolve);
            }
        }

        if (resolve) {
            resolveClass(loadedClass);
        }
        return loadedClass;
    }


    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        List<URL> allRes = new LinkedList<>();

        Enumeration<URL> thisRes = findResources(name);
        if (thisRes != null) {
            while (thisRes.hasMoreElements()) {
                allRes.add(thisRes.nextElement());
            }
        }

        Enumeration<URL> parentRes = super.findResources(name);
        if (parentRes != null) {
            while (parentRes.hasMoreElements()) {
                allRes.add(parentRes.nextElement());
            }
        }

        return new Enumeration<URL>() {
            Iterator<URL> it = allRes.iterator();

            @Override
            public boolean hasMoreElements() {
                return it.hasNext();
            }

            @Override
            public URL nextElement() {
                return it.next();
            }
        };
    }

    @Override
    public URL getResource(String name) {
        URL res = null;

        if (res == null) {
            res = findResource(name);
        }
        if (res == null) {
            res = super.getResource(name);
        }
        return res;
    }
}

输出:

2020-04-10 12:01:39,928 [main] INFO TestMain - Hello
2020-04-10 12:01:40,095 [main] ERROR TestClass -  =========== Hello, World! ===========
2020-04-10 12:01:40,097 [main] INFO TestClass - Test from test class
英文:

So, after 3 days I finally got an answer to my question. And it says that I am stupid 儿童优先类加载器和服务提供者接口(SPI) because in the article at the very end the author provided the example with correct behavior and it works in my case.
However, it doesn`t work in my other test with slf4j and logback dependencies. But to my surprise, the code without system class loader works.
In the nutshell, I try to use different versions of slf4j and logback.

Pom.xml:

&lt;dependencies&gt;
&lt;dependency&gt;
&lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
&lt;artifactId&gt;logback-classic&lt;/artifactId&gt;
&lt;version&gt;1.2.3&lt;/version&gt;
&lt;/dependency&gt;
&lt;/dependencies&gt;

TestMain.class

public class TestMain {
private static Logger log = LoggerFactory.getLogger(TestMain.class);
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, MalformedURLException {
log.info(&quot;Hello&quot;);
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
ChildFirstClassLoader2 classLoader = new ChildFirstClassLoader2(getUrls());
Thread.currentThread().setContextClassLoader(classLoader);
try {
Class&lt;?&gt; testClass = classLoader.loadClass(&quot;petrovskyi.TestClass&quot;);
Object o = testClass.getDeclaredConstructor().newInstance();
Method test = testClass.getMethod(&quot;test&quot;);
test.setAccessible(true);
test.invoke(o);
} finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
}
private static URL[] getUrls() throws MalformedURLException {
File libDir = Paths.get(&quot;src&quot;,&quot;main&quot;, &quot;resources&quot;, &quot;testClasses&quot;).toFile();
URL[] urls;
List&lt;URL&gt; urlsList = new ArrayList&lt;&gt;();
URL classUrl = libDir.toURI().toURL();
urlsList.add(classUrl);
try (Stream&lt;Path&gt; walk = Files.walk(libDir.toPath())) {
List&lt;File&gt; result = walk.map(Path::toFile)
.filter(x -&gt; x.getName().endsWith(&quot;.jar&quot;))
.collect(Collectors.toList());
for (File jarFile : result) {
urlsList.add(jarFile.toURI().toURL());
}
} catch (IOException e) {
throw new RuntimeException(&quot;Error while walking through &quot; + libDir + &quot; to find jar files&quot;, e);
}
urls = urlsList.toArray(new URL[0]);
return urls;
}
}

Above I try to get all jars from some directory. The jars are the next:

  • logback-classic-1.3.0-alpha5.jar
  • logback-core-1.3.0-alpha5.jar
  • slf4j-api-2.0.0-alpha1.jar

TestClass.class

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import org.slf4j.spi.DefaultLoggingEventBuilder;
import org.slf4j.spi.LoggingEventBuilder;
public class TestClass {
private static Logger log = LoggerFactory.getLogger(TestClass.class);
public TestClass() {
}
public void test() {
LoggingEventBuilder loggingEventBuilder = new DefaultLoggingEventBuilder(log, Level.ERROR);
loggingEventBuilder.log(&quot; =========== Hello, World! ===========&quot;);
log.info(&quot;Test from test class&quot;);
}
}

The class above uses LoggingEventBuilder that is not present in slf4j version mentioned in pom.xml

ChildFirstClassLoader2.class

public class ChildFirstClassLoader2 extends URLClassLoader {
public ChildFirstClassLoader2(URL[] urls) {
super(URLs);
}
@Override
protected Class&lt;?&gt; loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class&lt;?&gt; loadedClass = findLoadedClass(name);
if (loadedClass == null) {
try {
if (loadedClass == null) {
loadedClass = findClass(name);
}
} catch (ClassNotFoundException e) {
loadedClass = super.loadClass(name, resolve);
}
}
if (resolve) {
resolveClass(loadedClass);
}
return loadedClass;
}
@Override
public Enumeration&lt;URL&gt; getResources(String name) throws IOException {
List&lt;URL&gt; allRes = new LinkedList&lt;&gt;();
Enumeration&lt;URL&gt; thisRes = findResources(name);
if (thisRes != null) {
while (thisRes.hasMoreElements()) {
allRes.add(thisRes.nextElement());
}
}
Enumeration&lt;URL&gt; parentRes = super.findResources(name);
if (parentRes != null) {
while (parentRes.hasMoreElements()) {
allRes.add(parentRes.nextElement());
}
}
return new Enumeration&lt;URL&gt;() {
Iterator&lt;URL&gt; it = allRes.iterator();
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public URL nextElement() {
return it.next();
}
};
}
@Override
public URL getResource(String name) {
URL res = null;
if (res == null) {
res = findResource(name);
}
if (res == null) {
res = super.getResource(name);
}
return res;
}
}

The output:

2020-04-10 12:01:39,928 [main] INFO TestMain - Hello
2020-04-10 12:01:40,095 [main] ERROR TestClass -  =========== Hello, World! ===========
2020-04-10 12:01:40,097 [main] INFO TestClass - Test from test class

huangapple
  • 本文由 发表于 2020年4月8日 22:30:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/61103197.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定