feat: plugin class is registered as a bean at startup (#2255)

<!--  Thanks for sending a pull request!  Here are some tips for you:
1. 如果这是你的第一次,请阅读我们的贡献指南:<https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>。
1. If this is your first time, please read our contributor guidelines: <https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>.
2. 请根据你解决问题的类型为 Pull Request 添加合适的标签。
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request.
3. 请确保你已经添加并运行了适当的测试。
3. Ensure you have added or ran the appropriate tests for your PR.
-->

#### What type of PR is this?
/kind feature
/milestone 2.0
/area core
<!--
添加其中一个类别:
Add one of the following kinds:

/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind improvement

适当添加其中一个或多个类别(可选):
Optionally add one or more of the following kinds if applicable:

/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

#### What this PR does / why we need it:
插件 plugin.yaml 中不用在写 pluginClass 属性,插件的生命周期类通过标注 `@Component` 注解在启动时通过 PluginApplicationContext 依赖注入并创建实例,这得益于每个插件都有一个单独的 PluginApplicationContext 插件启动时可以将PluginWrapper 放到 Context 以支撑 pluginClass 实例的创建
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: Plugin
metadata:
  # The name defines how the plugin is invoked,A unique name
  name: PluginTemplate
spec:
  pluginClass: run.halo.template.TemplatePlugin
```
现在只需要如下方式即可无需配置 pluginClass
```java
@Component
public class TemplatePlugin extends BasePlugin {}
```
#### Which issue(s) this PR fixes:

<!--
PR 合并时自动关闭 issue。
Automatically closes linked issue when PR is merged.

用法:`Fixes #<issue 号>`,或者 `Fixes (粘贴 issue 完整链接)`
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

#### Special notes for your reviewer:
/cc @halo-dev/sig-halo 
#### Does this PR introduce a user-facing change?

<!--
如果当前 Pull Request 的修改不会造成用户侧的任何变更,在 `release-note` 代码块儿中填写 `NONE`。
否则请填写用户侧能够理解的 Release Note。如果当前 Pull Request 包含破坏性更新(Break Change),
Release Note 需要以 `action required` 开头。
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->

```release-note
None
```
pull/2249/head
guqing 2022-07-18 10:56:10 +08:00 committed by GitHub
parent ca3cff277a
commit 49ea6fbdec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 64 additions and 6 deletions

View File

@ -16,7 +16,6 @@ import org.pf4j.PluginState;
import org.springframework.lang.NonNull;
import run.halo.app.extension.AbstractExtension;
import run.halo.app.extension.GVK;
import run.halo.app.plugin.BasePlugin;
/**
* A custom resource for Plugin.
@ -74,7 +73,8 @@ public class Plugin extends AbstractExtension {
*/
private String requires = "*";
private String pluginClass = BasePlugin.class.getName();
@Deprecated
private String pluginClass;
private Boolean enabled = false;

View File

@ -173,7 +173,7 @@ public class PluginReconciler implements Reconciler {
private void ensureSpecUpToDateWhenDevelopmentMode(PluginWrapper pluginWrapper,
Plugin oldPlugin) {
if (!RuntimeMode.DEPLOYMENT.equals(pluginWrapper.getRuntimeMode())) {
if (RuntimeMode.DEPLOYMENT.equals(pluginWrapper.getRuntimeMode())) {
return;
}
YamlPluginFinder yamlPluginFinder = new YamlPluginFinder();

View File

@ -35,7 +35,7 @@ public class BasePlugin extends Plugin {
return applicationContext;
}
public HaloPluginManager getPluginManager() {
private HaloPluginManager getPluginManager() {
return (HaloPluginManager) getWrapper().getPluginManager();
}
}

View File

@ -0,0 +1,48 @@
package run.halo.app.plugin;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.Plugin;
import org.pf4j.PluginFactory;
import org.pf4j.PluginWrapper;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/**
* The default implementation for PluginFactory.
* <p>Get a {@link BasePlugin} instance from the {@link PluginApplicationContext}.</p>
*
* @author guqing
* @since 2.0.0
*/
@Slf4j
public class BasePluginFactory implements PluginFactory {
@Override
public Plugin create(PluginWrapper pluginWrapper) {
return getPluginContext(pluginWrapper)
.map(context -> {
try {
return context.getBean(BasePlugin.class);
} catch (NoSuchBeanDefinitionException e) {
log.info(
"No bean named 'basePlugin' found in the context create default instance");
DefaultListableBeanFactory beanFactory =
context.getDefaultListableBeanFactory();
BasePlugin pluginInstance = new BasePlugin(pluginWrapper);
beanFactory.registerSingleton(Plugin.class.getName(), pluginInstance);
return pluginInstance;
}
})
.orElse(null);
}
private Optional<PluginApplicationContext> getPluginContext(PluginWrapper pluginWrapper) {
try {
return Optional.of(ExtensionContextRegistry.getInstance())
.map(registry -> registry.getByPluginId(pluginWrapper.getPluginId()));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}
}

View File

@ -14,6 +14,7 @@ import org.pf4j.ExtensionFinder;
import org.pf4j.PluginDependency;
import org.pf4j.PluginDescriptor;
import org.pf4j.PluginDescriptorFinder;
import org.pf4j.PluginFactory;
import org.pf4j.PluginRepository;
import org.pf4j.PluginRuntimeException;
import org.pf4j.PluginState;
@ -80,6 +81,11 @@ public class HaloPluginManager extends DefaultPluginManager
return pluginApplicationInitializer.getPluginApplicationContext(pluginId);
}
@Override
protected PluginFactory createPluginFactory() {
return new BasePluginFactory();
}
@Override
public void afterPropertiesSet() {
this.pluginApplicationInitializer = new PluginApplicationInitializer(this);

View File

@ -66,6 +66,8 @@ public class PluginApplicationInitializer {
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
stopWatch.stop();
beanFactory.registerSingleton("pluginWrapper", haloPluginManager.getPlugin(pluginId));
populateSettingFetcher(pluginId, beanFactory);
log.debug("Total millis: {} ms -> {}", stopWatch.getTotalTimeMillis(),

View File

@ -50,7 +50,7 @@ public class YamlPluginDescriptorFinder implements PluginDescriptorFinder {
DefaultPluginDescriptor defaultPluginDescriptor =
new DefaultPluginDescriptor(pluginId,
spec.getDescription(),
spec.getPluginClass(),
BasePlugin.class.getName(),
spec.getVersion(),
spec.getRequires(),
spec.getAuthor(),

View File

@ -19,6 +19,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.pf4j.PluginState;
import org.pf4j.PluginWrapper;
import org.pf4j.RuntimeMode;
import run.halo.app.core.extension.Plugin;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Reconciler;
@ -53,6 +54,7 @@ class PluginReconcilerTest {
pluginReconciler = new PluginReconciler(extensionClient, haloPluginManager, jsBundleRule);
when(haloPluginManager.getPlugin(any())).thenReturn(pluginWrapper);
when(pluginWrapper.getRuntimeMode()).thenReturn(RuntimeMode.DEPLOYMENT);
when(haloPluginManager.getUnresolvedPlugins()).thenReturn(List.of());
}

View File

@ -92,7 +92,7 @@ class YamlPluginFinderTest {
}
],
"requires": ">=2.0.0",
"pluginClass": "run.halo.app.plugin.BasePlugin",
"pluginClass": null,
"enabled": false,
"extensionLocations": null,
settingName: null,