mirror of https://github.com/halo-dev/halo
Support configuration properties mechanism for plugin in Halo core (#4043)
#### What type of PR is this? /kind feature /area core /area plugin #### What this PR does / why we need it: This PR adds property sources into PluginApplicationContext environment to support configuration properties mechanism. See https://github.com/halo-dev/halo/issues/4015 for more. #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/4015 #### Special notes for your reviewer: You can verify the mechanism in [plugin-starter](https://github.com/halo-dev/plugin-starter) according to documentation `docs/developer-guide/plugin-configuration-properties.md`. I've only tested it on macOS, looking forward to feedback on Windows. #### Does this PR introduce a user-facing change? ```release-note 支持在插件中定义 @ConfigurationProperties 注解 ```pull/4070/head
parent
a56d4f2a92
commit
31740e732f
|
@ -1,17 +1,30 @@
|
||||||
package run.halo.app.plugin;
|
package run.halo.app.plugin;
|
||||||
|
|
||||||
|
import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.pf4j.PluginWrapper;
|
import org.pf4j.PluginWrapper;
|
||||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.boot.env.PropertySourceLoader;
|
||||||
|
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
import org.springframework.core.io.DefaultResourceLoader;
|
import org.springframework.core.io.DefaultResourceLoader;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StopWatch;
|
import org.springframework.util.StopWatch;
|
||||||
|
import reactor.core.Exceptions;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.infra.properties.HaloProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin application initializer will create plugin application context by plugin id and
|
* Plugin application initializer will create plugin application context by plugin id and
|
||||||
|
@ -28,6 +41,8 @@ public class PluginApplicationInitializer {
|
||||||
private final SharedApplicationContextHolder sharedApplicationContextHolder;
|
private final SharedApplicationContextHolder sharedApplicationContextHolder;
|
||||||
private final ApplicationContext rootApplicationContext;
|
private final ApplicationContext rootApplicationContext;
|
||||||
|
|
||||||
|
private final HaloProperties haloProperties;
|
||||||
|
|
||||||
public PluginApplicationInitializer(HaloPluginManager haloPluginManager,
|
public PluginApplicationInitializer(HaloPluginManager haloPluginManager,
|
||||||
ApplicationContext rootApplicationContext) {
|
ApplicationContext rootApplicationContext) {
|
||||||
Assert.notNull(haloPluginManager, "The haloPluginManager must not be null");
|
Assert.notNull(haloPluginManager, "The haloPluginManager must not be null");
|
||||||
|
@ -36,6 +51,7 @@ public class PluginApplicationInitializer {
|
||||||
this.rootApplicationContext = rootApplicationContext;
|
this.rootApplicationContext = rootApplicationContext;
|
||||||
sharedApplicationContextHolder = rootApplicationContext
|
sharedApplicationContextHolder = rootApplicationContext
|
||||||
.getBean(SharedApplicationContextHolder.class);
|
.getBean(SharedApplicationContextHolder.class);
|
||||||
|
haloProperties = rootApplicationContext.getBean(HaloProperties.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PluginApplicationContext createPluginApplicationContext(String pluginId) {
|
private PluginApplicationContext createPluginApplicationContext(String pluginId) {
|
||||||
|
@ -45,12 +61,12 @@ public class PluginApplicationInitializer {
|
||||||
StopWatch stopWatch = new StopWatch("initialize-plugin-context");
|
StopWatch stopWatch = new StopWatch("initialize-plugin-context");
|
||||||
stopWatch.start("Create PluginApplicationContext");
|
stopWatch.start("Create PluginApplicationContext");
|
||||||
PluginApplicationContext pluginApplicationContext = new PluginApplicationContext();
|
PluginApplicationContext pluginApplicationContext = new PluginApplicationContext();
|
||||||
|
pluginApplicationContext.setClassLoader(pluginClassLoader);
|
||||||
|
|
||||||
if (sharedApplicationContextHolder != null) {
|
if (sharedApplicationContextHolder != null) {
|
||||||
pluginApplicationContext.setParent(sharedApplicationContextHolder.getInstance());
|
pluginApplicationContext.setParent(sharedApplicationContextHolder.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginApplicationContext.setClassLoader(pluginClassLoader);
|
|
||||||
// populate plugin to plugin application context
|
// populate plugin to plugin application context
|
||||||
pluginApplicationContext.setPluginId(pluginId);
|
pluginApplicationContext.setPluginId(pluginId);
|
||||||
stopWatch.stop();
|
stopWatch.stop();
|
||||||
|
@ -58,6 +74,11 @@ public class PluginApplicationInitializer {
|
||||||
stopWatch.start("Create DefaultResourceLoader");
|
stopWatch.start("Create DefaultResourceLoader");
|
||||||
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader(pluginClassLoader);
|
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader(pluginClassLoader);
|
||||||
pluginApplicationContext.setResourceLoader(defaultResourceLoader);
|
pluginApplicationContext.setResourceLoader(defaultResourceLoader);
|
||||||
|
|
||||||
|
var mutablePropertySources = pluginApplicationContext.getEnvironment().getPropertySources();
|
||||||
|
resolvePropertySources(pluginId, pluginApplicationContext)
|
||||||
|
.forEach(mutablePropertySources::addLast);
|
||||||
|
|
||||||
stopWatch.stop();
|
stopWatch.stop();
|
||||||
|
|
||||||
DefaultListableBeanFactory beanFactory =
|
DefaultListableBeanFactory beanFactory =
|
||||||
|
@ -169,4 +190,55 @@ public class PluginApplicationInitializer {
|
||||||
stopWatch.prettyPrint());
|
stopWatch.prettyPrint());
|
||||||
return candidateComponents;
|
return candidateComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<PropertySource<?>> resolvePropertySources(String pluginId,
|
||||||
|
ResourceLoader resourceLoader) {
|
||||||
|
var propertySourceLoader = new YamlPropertySourceLoader();
|
||||||
|
var propertySources = new ArrayList<PropertySource<?>>();
|
||||||
|
var configsPath = haloProperties.getWorkDir().resolve("plugins").resolve("configs");
|
||||||
|
|
||||||
|
// resolve user defined config
|
||||||
|
Stream.of(
|
||||||
|
configsPath.resolve(pluginId + ".yaml"),
|
||||||
|
configsPath.resolve(pluginId + ".yml")
|
||||||
|
)
|
||||||
|
.map(path -> resourceLoader.getResource(path.toUri().toString()))
|
||||||
|
.forEach(resource -> {
|
||||||
|
var sources =
|
||||||
|
loadPropertySources("user-defined-config", resource, propertySourceLoader);
|
||||||
|
propertySources.addAll(sources);
|
||||||
|
});
|
||||||
|
|
||||||
|
// resolve default config
|
||||||
|
Stream.of(
|
||||||
|
CLASSPATH_URL_PREFIX + "/config.yaml",
|
||||||
|
CLASSPATH_URL_PREFIX + "/config.yaml"
|
||||||
|
)
|
||||||
|
.map(resourceLoader::getResource)
|
||||||
|
.forEach(resource -> {
|
||||||
|
var sources = loadPropertySources("default-config", resource, propertySourceLoader);
|
||||||
|
propertySources.addAll(sources);
|
||||||
|
});
|
||||||
|
return propertySources;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PropertySource<?>> loadPropertySources(String propertySourceName,
|
||||||
|
Resource resource,
|
||||||
|
PropertySourceLoader propertySourceLoader) {
|
||||||
|
logConfigLocation(resource);
|
||||||
|
if (resource.exists()) {
|
||||||
|
try {
|
||||||
|
return propertySourceLoader.load(propertySourceName, resource);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw Exceptions.propagate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logConfigLocation(Resource resource) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Loading property sources from {}", resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
# 插件外部配置
|
||||||
|
|
||||||
|
插件外部配置功能允许用户在特定目录添加插件相关的配置,插件启动的时候能够自动读取到该配置。
|
||||||
|
|
||||||
|
## 配置优先级
|
||||||
|
|
||||||
|
> 优先级从上到下由高到低。
|
||||||
|
|
||||||
|
1. `${halo.work-dir}/plugins/configs/${plugin-id}.{yaml|yml}`
|
||||||
|
2. `classpath:/config.{yaml|yml}`
|
||||||
|
|
||||||
|
插件开发者可在 `Class Path` 下 添加 `config.{yaml|yml}` 作为默认配置。当 `.yaml` 和 `.yml` 同时出现时,以 `.yml` 的配置将会被忽略。
|
||||||
|
|
||||||
|
## 插件中定义配置并使用
|
||||||
|
|
||||||
|
- `src/main/java/my/plugin/MyPluginProperties.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties
|
||||||
|
public class MyPluginProperties {
|
||||||
|
|
||||||
|
private String encryptKey;
|
||||||
|
|
||||||
|
private String certPath;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `src/main/java/my/plugin/MyPluginConfiguration.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
@EnableConfigurationProperties(MyPluginProperties.class)
|
||||||
|
@Configuration
|
||||||
|
public class MyPluginConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `src/main/java/my/plugin/MyPlugin.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class MyPlugin extends BasePlugin {
|
||||||
|
|
||||||
|
private final MyPluginProperties storeProperties;
|
||||||
|
|
||||||
|
public MyPlugin(PluginWrapper wrapper, MyPluginProperties storeProperties) {
|
||||||
|
super(wrapper);
|
||||||
|
this.storeProperties = storeProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
log.info("My plugin properties: {}", storeProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `src/main/resources/config.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
encryptKey: encrytkey==
|
||||||
|
certPath: /path/to/cert
|
||||||
|
```
|
||||||
|
|
||||||
|
## 插件使用者配置
|
||||||
|
|
||||||
|
- `${halo.work-dir}/plugins/configs/${plugin-id}.{yaml|yml}`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
encryptKey: override encrytkey==
|
||||||
|
certPath: /another/path/to/cert
|
||||||
|
```
|
||||||
|
|
||||||
|
## 可能存在的问题
|
||||||
|
|
||||||
|
- 增加未来实现"集群"架构的难度。
|
Loading…
Reference in New Issue