From 68d428aa29ba76f52e7de7d7dd5e8b2989501c49 Mon Sep 17 00:00:00 2001
From: guqing <38999863+guqing@users.noreply.github.com>
Date: Wed, 26 Jun 2024 19:20:51 +0800
Subject: [PATCH] refactor: enhance cache management in plugin setting config
(#6141)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
#### What type of PR is this?
/kind feature
/area plugin
/area core
/milestone 2.17.x
#### What this PR does / why we need it:
增强插件配置的缓存管理
1. 通过 SettingFetcher/ReactiveSettingFetcher 获取插件配置可以不在考虑获取数据的性能问题,当数据变更后会自动更新缓存
2. 现在你可以通过在插件中监听 `PluginConfigUpdatedEvent` 事件来做一些处理,它会在用户更改插件配置后被触发
#### Does this PR introduce a user-facing change?
```release-note
增强插件配置的缓存管理并支持通过监听 `PluginConfigUpdatedEvent` 事件做一些特殊处理
```
---
.../app/plugin/PluginConfigUpdatedEvent.java | 32 ++++
.../run/halo/app/plugin/PluginContext.java | 4 +
...efaultPluginApplicationContextFactory.java | 8 +-
.../halo/app/plugin/DefaultPluginGetter.java | 29 ++++
.../plugin/DefaultReactiveSettingFetcher.java | 160 +++++++++++++++---
.../halo/app/plugin/HaloPluginManager.java | 3 +-
.../run/halo/app/plugin/PluginGetter.java | 24 +++
.../app/plugin/SharedApplicationContext.java | 15 --
.../SharedApplicationContextFactory.java | 3 +
.../halo/app/plugin/SpringPluginFactory.java | 19 ++-
.../app/plugin/DefaultSettingFetcherTest.java | 105 +++++++++---
11 files changed, 327 insertions(+), 75 deletions(-)
create mode 100644 api/src/main/java/run/halo/app/plugin/PluginConfigUpdatedEvent.java
create mode 100644 application/src/main/java/run/halo/app/plugin/DefaultPluginGetter.java
create mode 100644 application/src/main/java/run/halo/app/plugin/PluginGetter.java
delete mode 100644 application/src/main/java/run/halo/app/plugin/SharedApplicationContext.java
diff --git a/api/src/main/java/run/halo/app/plugin/PluginConfigUpdatedEvent.java b/api/src/main/java/run/halo/app/plugin/PluginConfigUpdatedEvent.java
new file mode 100644
index 000000000..77877d0ca
--- /dev/null
+++ b/api/src/main/java/run/halo/app/plugin/PluginConfigUpdatedEvent.java
@@ -0,0 +1,32 @@
+package run.halo.app.plugin;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.Map;
+import lombok.Builder;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+import run.halo.app.core.extension.Plugin;
+import run.halo.app.extension.ConfigMap;
+
+/**
+ *
Event that is triggered when the {@link ConfigMap } represented by
+ * {@link Plugin.PluginSpec#getConfigMapName()} in the {@link Plugin} is updated.
+ * has two properties, oldConfig and newConfig, which represent the {@link ConfigMap#getData()}
+ * property value of the {@link ConfigMap}.
+ *
+ * @author guqing
+ * @since 2.17.0
+ */
+@Getter
+public class PluginConfigUpdatedEvent extends ApplicationEvent {
+ private final Map oldConfig;
+ private final Map newConfig;
+
+ @Builder
+ public PluginConfigUpdatedEvent(Object source, Map oldConfig,
+ Map newConfig) {
+ super(source);
+ this.oldConfig = oldConfig;
+ this.newConfig = newConfig;
+ }
+}
diff --git a/api/src/main/java/run/halo/app/plugin/PluginContext.java b/api/src/main/java/run/halo/app/plugin/PluginContext.java
index 01bae2f96..d5fa54989 100644
--- a/api/src/main/java/run/halo/app/plugin/PluginContext.java
+++ b/api/src/main/java/run/halo/app/plugin/PluginContext.java
@@ -1,5 +1,6 @@
package run.halo.app.plugin;
+import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.pf4j.RuntimeMode;
@@ -17,10 +18,13 @@ import org.pf4j.RuntimeMode;
* @since 2.10.0
*/
@Getter
+@Builder
@RequiredArgsConstructor
public class PluginContext {
private final String name;
+ private final String configMapName;
+
private final String version;
private final RuntimeMode runtimeMode;
diff --git a/application/src/main/java/run/halo/app/plugin/DefaultPluginApplicationContextFactory.java b/application/src/main/java/run/halo/app/plugin/DefaultPluginApplicationContextFactory.java
index c4cc430f0..d77916c48 100644
--- a/application/src/main/java/run/halo/app/plugin/DefaultPluginApplicationContextFactory.java
+++ b/application/src/main/java/run/halo/app/plugin/DefaultPluginApplicationContextFactory.java
@@ -1,5 +1,6 @@
package run.halo.app.plugin;
+import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_SINGLETON;
import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX;
import java.io.IOException;
@@ -101,10 +102,9 @@ public class DefaultPluginApplicationContextFactory implements PluginApplication
rootContext.getBeanProvider(ReactiveExtensionClient.class)
.ifUnique(client -> {
- var reactiveSettingFetcher = new DefaultReactiveSettingFetcher(client, pluginId);
- var settingFetcher = new DefaultSettingFetcher(reactiveSettingFetcher);
- beanFactory.registerSingleton("reactiveSettingFetcher", reactiveSettingFetcher);
- beanFactory.registerSingleton("settingFetcher", settingFetcher);
+ context.registerBean("reactiveSettingFetcher",
+ DefaultReactiveSettingFetcher.class, bhd -> bhd.setScope(SCOPE_SINGLETON));
+ beanFactory.registerSingleton("settingFetcher", DefaultSettingFetcher.class);
});
rootContext.getBeanProvider(PluginRequestMappingHandlerMapping.class)
diff --git a/application/src/main/java/run/halo/app/plugin/DefaultPluginGetter.java b/application/src/main/java/run/halo/app/plugin/DefaultPluginGetter.java
new file mode 100644
index 000000000..02d9018ac
--- /dev/null
+++ b/application/src/main/java/run/halo/app/plugin/DefaultPluginGetter.java
@@ -0,0 +1,29 @@
+package run.halo.app.plugin;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+import run.halo.app.core.extension.Plugin;
+import run.halo.app.extension.ExtensionClient;
+import run.halo.app.infra.exception.NotFoundException;
+
+/**
+ * Default implementation of {@link PluginGetter}.
+ *
+ * @author guqing
+ * @since 2.17.0
+ */
+@Component
+@RequiredArgsConstructor
+public class DefaultPluginGetter implements PluginGetter {
+ private final ExtensionClient client;
+
+ @Override
+ public Plugin getPlugin(String name) {
+ if (StringUtils.isBlank(name)) {
+ throw new IllegalArgumentException("Plugin name must not be blank");
+ }
+ return client.fetch(Plugin.class, name)
+ .orElseThrow(() -> new NotFoundException("Plugin not found"));
+ }
+}
diff --git a/application/src/main/java/run/halo/app/plugin/DefaultReactiveSettingFetcher.java b/application/src/main/java/run/halo/app/plugin/DefaultReactiveSettingFetcher.java
index dbb89efb9..3b0ba917d 100644
--- a/application/src/main/java/run/halo/app/plugin/DefaultReactiveSettingFetcher.java
+++ b/application/src/main/java/run/halo/app/plugin/DefaultReactiveSettingFetcher.java
@@ -1,16 +1,30 @@
package run.halo.app.plugin;
+import static run.halo.app.extension.index.query.QueryFactory.equal;
+
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
import reactor.core.publisher.Mono;
-import run.halo.app.core.extension.Plugin;
import run.halo.app.extension.ConfigMap;
+import run.halo.app.extension.DefaultExtensionMatcher;
+import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.ReactiveExtensionClient;
+import run.halo.app.extension.controller.Controller;
+import run.halo.app.extension.controller.ControllerBuilder;
+import run.halo.app.extension.controller.Reconciler;
+import run.halo.app.extension.router.selector.FieldSelector;
import run.halo.app.infra.utils.JsonParseException;
import run.halo.app.infra.utils.JsonUtils;
@@ -20,15 +34,36 @@ import run.halo.app.infra.utils.JsonUtils;
* @author guqing
* @since 2.0.0
*/
-public class DefaultReactiveSettingFetcher implements ReactiveSettingFetcher {
+public class DefaultReactiveSettingFetcher
+ implements ReactiveSettingFetcher, Reconciler, DisposableBean,
+ ApplicationContextAware {
private final ReactiveExtensionClient client;
+ private final ExtensionClient blockingClient;
+
+ private final CacheManager cacheManager;
+
+ /**
+ * The application context of the plugin.
+ */
+ private ApplicationContext applicationContext;
+
private final String pluginName;
- public DefaultReactiveSettingFetcher(ReactiveExtensionClient client, String pluginName) {
+ private final String configMapName;
+
+ private final String cacheName;
+
+ public DefaultReactiveSettingFetcher(PluginContext pluginContext,
+ ReactiveExtensionClient client, ExtensionClient blockingClient,
+ CacheManager cacheManager) {
this.client = client;
- this.pluginName = pluginName;
+ this.pluginName = pluginContext.getName();
+ this.configMapName = pluginContext.getConfigMapName();
+ this.blockingClient = blockingClient;
+ this.cacheManager = cacheManager;
+ this.cacheName = buildCacheKey(pluginName);
}
@Override
@@ -60,26 +95,31 @@ public class DefaultReactiveSettingFetcher implements ReactiveSettingFetcher {
.defaultIfEmpty(JsonNodeFactory.instance.missingNode());
}
- private Mono