Дочерний загрузчик первого класса и интерфейс поставщика услуг (SPI) - PullRequest
0 голосов
/ 08 апреля 2020

Я нашел пользовательский загрузчик классов , который загружает классы по принципу «ребенок-первый». И работает нормально, но я столкнулся со следующей проблемой. Когда я пытаюсь загрузить классы, использующие SPI, я получаю исключение:

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

Я создал простой проект SPI с модулями: spi-api, spi-impl и spi-app.

И это работает, когда я использую URLClassLoader, однако всякий раз, когда я использую ChildFirstClassLoader, я получаю исключение, упомянутое выше:

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;
    }
}

Возможно, кто-то уже сталкивался с этой проблемой раньше и знает, как ее решить. Буду благодарен за любую помощь или совет.

1 Ответ

0 голосов
/ 10 апреля 2020

Итак, через 3 дня я наконец получил ответ на свой вопрос. И это говорит о том, что я тупой :) потому что в статье в самом конце автор привел пример с правильным поведением, и это работает в моем случае. Тем не менее, он не работает в моем другом тесте с зависимостями 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;
    }
}

Выше я пытаюсь получить все фляги из некоторого каталога. Банки следующие:

  • logback-classi c -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");
        }
    }

Приведенный выше класс использует LoggingEventBuilder, которого нет в версии slf4j, упомянутой в pom. xml

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
...