refactor: modify plugin class loading order to follow parent delegation mechanism (#7258)

#### 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 加载顺序
```
pull/7272/head
guqing 2025-03-06 09:48:57 +08:00 committed by GitHub
parent 00c8cbb7bb
commit 1d8a25cd69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 101 additions and 3 deletions

View File

@ -13,6 +13,7 @@ import org.pf4j.ExtensionFactory;
import org.pf4j.ExtensionFinder; import org.pf4j.ExtensionFinder;
import org.pf4j.JarPluginLoader; import org.pf4j.JarPluginLoader;
import org.pf4j.JarPluginRepository; import org.pf4j.JarPluginRepository;
import org.pf4j.PluginDescriptor;
import org.pf4j.PluginDescriptorFinder; import org.pf4j.PluginDescriptorFinder;
import org.pf4j.PluginFactory; import org.pf4j.PluginFactory;
import org.pf4j.PluginLoader; import org.pf4j.PluginLoader;
@ -27,6 +28,8 @@ import org.springframework.context.ApplicationContext;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
import run.halo.app.infra.SystemVersionSupplier; import run.halo.app.infra.SystemVersionSupplier;
import run.halo.app.plugin.event.PluginStartedEvent; 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. * PluginManager to hold the main ApplicationContext.
@ -106,7 +109,15 @@ public class HaloPluginManager extends DefaultPluginManager
protected PluginLoader createPluginLoader() { protected PluginLoader createPluginLoader() {
var compoundLoader = new CompoundPluginLoader(); var compoundLoader = new CompoundPluginLoader();
compoundLoader.add(new DevPluginLoader(this, this.pluginProperties), this::isDevelopment); 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; return compoundLoader;
} }

View File

@ -51,7 +51,7 @@ public class SpringPlugin extends Plugin {
// try to stop plugin for cleaning resources if something went wrong // try to stop plugin for cleaning resources if something went wrong
log.error( log.error(
"Cleaning up plugin resources for plugin {} due to not being able to start plugin.", "Cleaning up plugin resources for plugin {} due to not being able to start plugin.",
pluginId); pluginId, t);
this.stop(); this.stop();
// propagate exception to invoker. // propagate exception to invoker.
throw t; throw t;

View File

@ -1,10 +1,12 @@
package run.halo.app.plugin; package run.halo.app.plugin.loader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import org.pf4j.DevelopmentPluginLoader; import org.pf4j.DevelopmentPluginLoader;
import org.pf4j.PluginClassLoader;
import org.pf4j.PluginDescriptor; import org.pf4j.PluginDescriptor;
import org.pf4j.PluginManager; import org.pf4j.PluginManager;
import run.halo.app.plugin.PluginProperties;
public class DevPluginLoader extends DevelopmentPluginLoader { 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. // Currently we only support a plugin loading from directory in dev mode.
return Files.isDirectory(pluginPath); return Files.isDirectory(pluginPath);
} }
@Override
protected PluginClassLoader createPluginClassLoader(Path pluginPath,
PluginDescriptor pluginDescriptor) {
return new HaloPluginClassLoader(this.pluginManager, pluginDescriptor,
this.getClass().getClassLoader());
}
} }

View File

@ -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 <a href="https://github.com/halo-dev/halo/issues/4610">gh-4610</a>.
*/
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<URL> getResources(String name) throws IOException {
List<URL> 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);
}
}