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;
|
||||
|
||||
import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginWrapper;
|
||||
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.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
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.util.Assert;
|
||||
import org.springframework.util.StopWatch;
|
||||
import reactor.core.Exceptions;
|
||||
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
|
||||
|
@ -28,6 +41,8 @@ public class PluginApplicationInitializer {
|
|||
private final SharedApplicationContextHolder sharedApplicationContextHolder;
|
||||
private final ApplicationContext rootApplicationContext;
|
||||
|
||||
private final HaloProperties haloProperties;
|
||||
|
||||
public PluginApplicationInitializer(HaloPluginManager haloPluginManager,
|
||||
ApplicationContext rootApplicationContext) {
|
||||
Assert.notNull(haloPluginManager, "The haloPluginManager must not be null");
|
||||
|
@ -36,6 +51,7 @@ public class PluginApplicationInitializer {
|
|||
this.rootApplicationContext = rootApplicationContext;
|
||||
sharedApplicationContextHolder = rootApplicationContext
|
||||
.getBean(SharedApplicationContextHolder.class);
|
||||
haloProperties = rootApplicationContext.getBean(HaloProperties.class);
|
||||
}
|
||||
|
||||
private PluginApplicationContext createPluginApplicationContext(String pluginId) {
|
||||
|
@ -45,12 +61,12 @@ public class PluginApplicationInitializer {
|
|||
StopWatch stopWatch = new StopWatch("initialize-plugin-context");
|
||||
stopWatch.start("Create PluginApplicationContext");
|
||||
PluginApplicationContext pluginApplicationContext = new PluginApplicationContext();
|
||||
pluginApplicationContext.setClassLoader(pluginClassLoader);
|
||||
|
||||
if (sharedApplicationContextHolder != null) {
|
||||
pluginApplicationContext.setParent(sharedApplicationContextHolder.getInstance());
|
||||
}
|
||||
|
||||
pluginApplicationContext.setClassLoader(pluginClassLoader);
|
||||
// populate plugin to plugin application context
|
||||
pluginApplicationContext.setPluginId(pluginId);
|
||||
stopWatch.stop();
|
||||
|
@ -58,6 +74,11 @@ public class PluginApplicationInitializer {
|
|||
stopWatch.start("Create DefaultResourceLoader");
|
||||
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader(pluginClassLoader);
|
||||
pluginApplicationContext.setResourceLoader(defaultResourceLoader);
|
||||
|
||||
var mutablePropertySources = pluginApplicationContext.getEnvironment().getPropertySources();
|
||||
resolvePropertySources(pluginId, pluginApplicationContext)
|
||||
.forEach(mutablePropertySources::addLast);
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
DefaultListableBeanFactory beanFactory =
|
||||
|
@ -169,4 +190,55 @@ public class PluginApplicationInitializer {
|
|||
stopWatch.prettyPrint());
|
||||
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