refactor: plugin development initializer (#3539)

#### What type of PR is this?
/kind improvement
/area core
/milestone 2.4.x

#### What this PR does / why we need it:
去掉开发模式初始化器中加载插件的逻辑,以解决开发模式下启动插件时会遇到的问题
1. 重启插件/Halo启动时 PluginApplicationContext#hash has been closed already.
2. 卸载插件时 NullPointerException at pluginWrapper.getPlugin().stop() becuase 'pluginWrapper' is null
3. Halo 启动时 `Most likely this exception is thrown because the called constructor (xxx) cannot handle 'null' parameters. Original message was:  ...`
4. 以及启动插件后插件的 bean 没有被创建等
```shell
2023-03-20T13:28:02.811+08:00 ERROR 59170 --- [nReconciler-t-1] r.h.a.e.controller.DefaultController     : Reconciler in run.halo.app.core.extension.reconciler.PluginReconciler-worker-1 aborted with an error, re-enqueuing...

java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 10
	at java.base/java.util.ArrayList.fastRemove(ArrayList.java:642) ~[na:na]
	at java.base/java.util.ArrayList.remove(ArrayList.java:629) ~[na:na]
	at org.pf4j.AbstractPluginManager.resolvePlugins(AbstractPluginManager.java:797) ~[pf4j-3.9.0.jar:3.9.0]
	at org.pf4j.AbstractPluginManager.loadPlugin(AbstractPluginManager.java:204) ~[pf4j-3.9.0.jar:3.9.0]
	at run.halo.app.core.extension.reconciler.PluginReconciler.lambda$ensurePluginLoaded$17(PluginReconciler.java:495) ~[main/:na]
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) ~[na:na]
	at run.halo.app.core.extension.reconciler.PluginReconciler.ensurePluginLoaded(PluginReconciler.java:493) ~[main/:na]
	at run.halo.app.core.extension.reconciler.PluginReconciler.getPluginWrapper(PluginReconciler.java:322) ~[main/:na]
	at run.halo.app.core.extension.reconciler.PluginReconciler.lambda$readinessDetection$1(PluginReconciler.java:117) ~[main/:na]
	at java.base/java.util.Optional.map(Optional.java:260) ~[na:na]
	at run.halo.app.core.extension.reconciler.PluginReconciler.readinessDetection(PluginReconciler.java:107) ~[main/:na]
	at run.halo.app.core.extension.reconciler.PluginReconciler.lambda$reconcile$0(PluginReconciler.java:95) ~[main/:na]
	at java.base/java.util.Optional.map(Optional.java:260) ~[na:na]
	at run.halo.app.core.extension.reconciler.PluginReconciler.reconcile(PluginReconciler.java:87) ~[main/:na]
	at run.halo.app.core.extension.reconciler.PluginReconciler.reconcile(PluginReconciler.java:70) ~[main/:na]
	at run.halo.app.extension.controller.DefaultController$Worker.run(DefaultController.java:163) ~[main/:na]
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[na:na]
	at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264) ~[na:na]
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
```
这些异常信息都是因为原先的 PluginManager 实现不是线程安全的,在 webflux 下使用并且有 Reconciler 的情况下非常容易复现。
所以除了在 Reconciler 调用以外尽量保证 PluginManager 的调用是单线程的,以避免 PluginManager 出现线程安全问题。

how to test it?
在开发模式下配置插件的 fixedPath 来检查差价启用功能是否正常。

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/3540/head
guqing 2023-03-20 18:06:26 +08:00 committed by GitHub
parent e5bbf48360
commit 5bc4a63e59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 4 additions and 32 deletions

View File

@ -3,7 +3,6 @@ package run.halo.app.plugin;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginWrapper;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.dao.OptimisticLockingFailureException;
@ -40,30 +39,12 @@ public class PluginDevelopmentInitializer implements ApplicationListener<Applica
if (!haloPluginManager.isDevelopment()) { if (!haloPluginManager.isDevelopment()) {
return; return;
} }
createFixedPluginIfNecessary(haloPluginManager); createFixedPluginIfNecessary();
} }
private void createFixedPluginIfNecessary(HaloPluginManager pluginManager) { private void createFixedPluginIfNecessary() {
for (Path path : pluginProperties.getFixedPluginPath()) { for (Path path : pluginProperties.getFixedPluginPath()) {
Plugin plugin = new YamlPluginFinder().find(path);
// Already loaded do not load again
String pluginId = idForPath(path);
// for issue #2901
if (pluginId == null) {
try {
pluginId = pluginManager.loadPlugin(path);
} catch (Exception e) {
log.warn(e.getMessage(), e);
continue;
}
}
PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
if (pluginWrapper == null) {
continue;
}
Plugin plugin = new YamlPluginFinder().find(pluginWrapper.getPluginPath());
extensionClient.fetch(Plugin.class, plugin.getMetadata().getName()) extensionClient.fetch(Plugin.class, plugin.getMetadata().getName())
.flatMap(persistent -> { .flatMap(persistent -> {
plugin.getMetadata().setVersion(persistent.getMetadata().getVersion()); plugin.getMetadata().setVersion(persistent.getMetadata().getVersion());
@ -75,13 +56,4 @@ public class PluginDevelopmentInitializer implements ApplicationListener<Applica
.block(); .block();
} }
} }
}
protected String idForPath(Path pluginPath) {
for (PluginWrapper plugin : haloPluginManager.getPlugins()) {
if (plugin.getPluginPath().equals(pluginPath)) {
return plugin.getPluginId();
}
}
return null;
}
}