From 1d8a25cd69ae5241ff942ba30273049b1dcd84d5 Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Thu, 6 Mar 2025 09:48:57 +0800 Subject: [PATCH] refactor: modify plugin class loading order to follow parent delegation mechanism (#7258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /area core /milestone 2.20.x #### What this PR does / why we need it: 修改插件类加载顺序遵循双亲委派机制,以避免插件需要手动排除冲突类的问题 此 PR 的动力是: 1. 插件排除依赖复杂而麻烦 2. 尝试多次无法很好的通过工具实现这一点 3. 对于一些依赖如 kotlin 何 spring security oauth 等同一 JVM 只能加载一次(即不能再次从插件加载)且插件可能无法排除依赖或排除依赖后功能不正确如遇到链接错误等 4. 签名文件冲突等问题 resources 下的资源文件加载顺序还是插件优先,避免与 halo 同名文件不加载的问题 进过测试,插件依赖功能以及其他插件的功能不受影响,建议 Reviewer 再测试一遍 #### Does this PR introduce a user-facing change? ```release-note 调整插件类的加载顺序使其遵循双亲委派机制,替代原先的 Plugin -> Dependent Plugin -> Halo 加载顺序 ``` --- .../halo/app/plugin/HaloPluginManager.java | 13 +++- .../run/halo/app/plugin/SpringPlugin.java | 2 +- .../plugin/{ => loader}/DevPluginLoader.java | 11 ++- .../plugin/loader/HaloPluginClassLoader.java | 78 +++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) rename application/src/main/java/run/halo/app/plugin/{ => loader}/DevPluginLoader.java (78%) create mode 100644 application/src/main/java/run/halo/app/plugin/loader/HaloPluginClassLoader.java diff --git a/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java b/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java index 4d131d424..63c7d741d 100644 --- a/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java +++ b/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java @@ -13,6 +13,7 @@ import org.pf4j.ExtensionFactory; import org.pf4j.ExtensionFinder; import org.pf4j.JarPluginLoader; import org.pf4j.JarPluginRepository; +import org.pf4j.PluginDescriptor; import org.pf4j.PluginDescriptorFinder; import org.pf4j.PluginFactory; import org.pf4j.PluginLoader; @@ -27,6 +28,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.data.util.Lazy; import run.halo.app.infra.SystemVersionSupplier; import run.halo.app.plugin.event.PluginStartedEvent; +import run.halo.app.plugin.loader.DevPluginLoader; +import run.halo.app.plugin.loader.HaloPluginClassLoader; /** * PluginManager to hold the main ApplicationContext. @@ -106,7 +109,15 @@ public class HaloPluginManager extends DefaultPluginManager protected PluginLoader createPluginLoader() { var compoundLoader = new CompoundPluginLoader(); compoundLoader.add(new DevPluginLoader(this, this.pluginProperties), this::isDevelopment); - compoundLoader.add(new JarPluginLoader(this)); + compoundLoader.add(new JarPluginLoader(this) { + @Override + public ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor) { + var pluginClassLoader = new HaloPluginClassLoader(this.pluginManager, + pluginDescriptor, this.getClass().getClassLoader()); + pluginClassLoader.addFile(pluginPath.toFile()); + return pluginClassLoader; + } + }); return compoundLoader; } diff --git a/application/src/main/java/run/halo/app/plugin/SpringPlugin.java b/application/src/main/java/run/halo/app/plugin/SpringPlugin.java index 0acc14478..0e932f737 100644 --- a/application/src/main/java/run/halo/app/plugin/SpringPlugin.java +++ b/application/src/main/java/run/halo/app/plugin/SpringPlugin.java @@ -51,7 +51,7 @@ public class SpringPlugin extends Plugin { // try to stop plugin for cleaning resources if something went wrong log.error( "Cleaning up plugin resources for plugin {} due to not being able to start plugin.", - pluginId); + pluginId, t); this.stop(); // propagate exception to invoker. throw t; diff --git a/application/src/main/java/run/halo/app/plugin/DevPluginLoader.java b/application/src/main/java/run/halo/app/plugin/loader/DevPluginLoader.java similarity index 78% rename from application/src/main/java/run/halo/app/plugin/DevPluginLoader.java rename to application/src/main/java/run/halo/app/plugin/loader/DevPluginLoader.java index 9698b711f..e7c0ad64c 100644 --- a/application/src/main/java/run/halo/app/plugin/DevPluginLoader.java +++ b/application/src/main/java/run/halo/app/plugin/loader/DevPluginLoader.java @@ -1,10 +1,12 @@ -package run.halo.app.plugin; +package run.halo.app.plugin.loader; import java.nio.file.Files; import java.nio.file.Path; import org.pf4j.DevelopmentPluginLoader; +import org.pf4j.PluginClassLoader; import org.pf4j.PluginDescriptor; import org.pf4j.PluginManager; +import run.halo.app.plugin.PluginProperties; public class DevPluginLoader extends DevelopmentPluginLoader { @@ -40,4 +42,11 @@ public class DevPluginLoader extends DevelopmentPluginLoader { // Currently we only support a plugin loading from directory in dev mode. return Files.isDirectory(pluginPath); } + + @Override + protected PluginClassLoader createPluginClassLoader(Path pluginPath, + PluginDescriptor pluginDescriptor) { + return new HaloPluginClassLoader(this.pluginManager, pluginDescriptor, + this.getClass().getClassLoader()); + } } diff --git a/application/src/main/java/run/halo/app/plugin/loader/HaloPluginClassLoader.java b/application/src/main/java/run/halo/app/plugin/loader/HaloPluginClassLoader.java new file mode 100644 index 000000000..9736c0aaf --- /dev/null +++ b/application/src/main/java/run/halo/app/plugin/loader/HaloPluginClassLoader.java @@ -0,0 +1,78 @@ +package run.halo.app.plugin.loader; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.ClassLoadingStrategy; +import org.pf4j.PluginClassLoader; +import org.pf4j.PluginDescriptor; +import org.pf4j.PluginManager; + +@Slf4j +public class HaloPluginClassLoader extends PluginClassLoader { + + /** + * see also gh-4610. + */ + private final ClassLoadingStrategy resourceLoadingStrategy = ClassLoadingStrategy.PDA; + + public HaloPluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, + ClassLoader parent) { + super(pluginManager, pluginDescriptor, parent, ClassLoadingStrategy.APD); + } + + @Override + public URL getResource(String name) { + for (ClassLoadingStrategy.Source classLoadingSource : + resourceLoadingStrategy.getSources()) { + URL url = switch (classLoadingSource) { + case APPLICATION -> super.getResource(name); + case PLUGIN -> this.findResource(name); + case DEPENDENCIES -> this.findResourceFromDependencies(name); + }; + + if (url != null) { + log.trace("Found resource '{}' in {} classpath", name, + classLoadingSource); + return url; + } + + log.trace("Couldn't find resource '{}' in {}", name, + classLoadingSource); + } + + return null; + } + + @Override + public Enumeration getResources(String name) throws IOException { + List resources = new ArrayList<>(); + log.trace("Received request to load resources '{}'", name); + + for (ClassLoadingStrategy.Source classLoadingSource : + resourceLoadingStrategy.getSources()) { + switch (classLoadingSource) { + case APPLICATION: + if (this.getParent() != null) { + resources.addAll( + Collections.list(this.getParent().getResources(name))); + } + break; + case PLUGIN: + resources.addAll(Collections.list(this.findResources(name))); + break; + case DEPENDENCIES: + resources.addAll(this.findResourcesFromDependencies(name)); + break; + default: + // Do nothing + } + } + + return Collections.enumeration(resources); + } +}