Java ServiceLoader Package 器不工作,内部使用调用类

tvmytwxo  于 2023-02-21  发布在  Java
关注(0)|答案(1)|浏览(125)

因此,我一直在尝试在我的库中创建一个小的PluginLoader,它允许您将JAR文件加载到自定义的ModuleLayer中,并使用该层加载使用ServiceLoader.load(ModuleLayer, Class<?>)的服务。
然而,当调用ServiceProvider.load时,它在内部使用Reflection.getCallerClass来获取调用代码的类,这样它就可以从它的模块加载服务。
PluginLoader.java

package com.wexalian.common.plugin;

import com.wexalian.common.collection.wrapper.StreamWrapper;
import com.wexalian.nullability.annotations.Nonnull;

import java.nio.file.Path;
import java.util.ServiceLoader;
import java.util.stream.Stream;

@FunctionalInterface
public interface PluginLoader<T extends IAbstractPlugin> extends StreamWrapper.Iterable<T> {
    @Nonnull
    @Override
    Stream<T> get();
    
    static void init(@Nonnull ServiceLoaderLayerFunction serviceLoaderFunc) {
        PluginLoaderImpl.init(serviceLoaderFunc);
    }
    
    static void loadPlugins(@Nonnull Path path) {
        PluginLoaderImpl.loadPlugins(path);
    }
    
    @Nonnull
    static <T extends IAbstractPlugin> PluginLoader<T> load(@Nonnull Class<T> pluginClass) {
        return load(pluginClass, null);
    }
    
    @Nonnull
    static <T extends IAbstractPlugin> PluginLoader<T> load(@Nonnull Class<T> pluginClass, ServiceLoaderFallbackFunction fallbackServiceProvider) {
        return PluginLoaderImpl.load(pluginClass, fallbackServiceProvider);
    }
    
    @FunctionalInterface
    interface ServiceLoaderLayerFunction {
        @Nonnull
        <T> ServiceLoader<T> load(@Nonnull ModuleLayer layer, @Nonnull Class<T> clazz);
        
        @Nonnull
        default <T> Stream<T> stream(@Nonnull ModuleLayer layer, @Nonnull Class<T> clazz) {
            return load(layer, clazz).stream().map(ServiceLoader.Provider::get);
        }
    }
    
    @FunctionalInterface
    interface ServiceLoaderFallbackFunction {
        @Nonnull
        <T> ServiceLoader<T> load(@Nonnull Class<T> clazz);
        
        @Nonnull
        default <T> Stream<T> stream(@Nonnull Class<T> clazz) {
            return load(clazz).stream().map(ServiceLoader.Provider::get);
        }
    }
}

PluginLoaderImpl.java

package com.wexalian.common.plugin;

import com.wexalian.nullability.annotations.Nonnull;

import java.io.IOException;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

final class PluginLoaderImpl {
    private static final Set<ModuleLayer> pluginLayerSet = new HashSet<>();
    
    private static PluginLoader.ServiceLoaderLayerFunction serviceLoaderLayer;
    private static ModuleLayer coreLayer;
    private static ClassLoader coreLoader;
    
    private static boolean init = false;
    
    private PluginLoaderImpl() {}
    
    static void init(@Nonnull PluginLoader.ServiceLoaderLayerFunction serviceLoaderFunc) {
        if (!init) {
            serviceLoaderLayer = serviceLoaderFunc;
            
            Class<?> coreClass = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass();
            coreLayer = coreClass.getModule().getLayer();
            coreLoader = coreClass.getClassLoader();
            
            if (coreLayer == null) {
                throw new IllegalStateException("PluginLoaderImpl can only be initialized from a named module!");
            }
            else init = true;
        }
        else throw new IllegalStateException("PluginLoaderImpl can only be initialized once!");
    }
    
    static void loadPlugins(@Nonnull Path path) {
        if (init) {
            if (Files.exists(path)) {
                try (Stream<Path> paths = Files.list(path)) {
                    ModuleFinder moduleFinder = ModuleFinder.of(paths.toArray(Path[]::new));
                    List<String> moduleNames = moduleFinder.findAll().stream().map(ref -> ref.descriptor().name()).toList();
                    Configuration configuration = coreLayer.configuration().resolveAndBind(moduleFinder, ModuleFinder.of(), moduleNames);
                    ModuleLayer pluginLayer = coreLayer.defineModulesWithOneLoader(configuration, coreLoader);
                    pluginLayerSet.add(pluginLayer);
                }
                catch (IOException e) {
                    throw new IllegalStateException("Error loading plugins from path " + path, e);
                }
            }
        }
        else throw new IllegalStateException("PluginLoaderImpl has to be initialized before you can load plugins!");
    }
    
    static <T extends IAbstractPlugin> PluginLoader<T> load(Class<T> clazz, PluginLoader.ServiceLoaderFallbackFunction serviceLoader) {
        if (init) {
            if (!pluginLayerSet.isEmpty()) {
                return () -> pluginLayerSet.stream().flatMap(layer -> serviceLoaderLayer.stream(layer, clazz)).filter(IAbstractPlugin::isEnabled);
            }
            else {
                return () -> serviceLoaderLayer.stream(coreLayer, clazz).filter(IAbstractPlugin::isEnabled);
            }
        }
        else if (serviceLoader != null) {
            return () -> serviceLoader.stream(clazz);
        }
        else throw new IllegalStateException("PluginLoaderImpl has to be initialized before you can load services from plugins!");
    }
}

现在我的问题是:我目前正在写一个带有一些服务的程序,并使用那个库来加载JAR文件并加载它们,但是它识别PluginLoader作为调用者类,它“不声明uses”,因为库实际上并没有我想要的服务。
我已经找到了一个变通方法,即接受一个Function<ModuleLayer, Class<?>, ServiceProvider<?>,它将所有调用重定向到适当的模块,但我不希望在使用PluginLoader时都这样做。
除此之外我不知道其他解决办法,所以也许你们中的一个知道。
先谢了,韦克斯利安

7lrncoxx

7lrncoxx1#

使用ModuleLayer系统时,您还必须在和各种插件模块定义中定义usesprovides
您的模块:

uses com.wexalian.common.plugin.IAbstractPlugin;

在您的插件模块中:

provides com.wexalian.common.plugin.IAbstractPlugin with some.plugin.PluginFactory;

参见ServiceLoaderServiceLoader.Provider,这是一个模块中的服务加载器如何知道其他模块中的加载器。

相关问题