Files
halo-dark-mode-plugin/.agents/skills/halo-plugin-dev/references/server-lifecycle.md
T
Serendipity edbf78f236 feat: 初始化 Halo 暗色模式插件
- Halo Plugin 后端(Java/Gradle),含 DarkModePlugin 主类和测试
- Vue 3 + TypeScript 前端 UI,包含主题切换组件和设置页面
- 暗色模式 CSS 变量和覆盖样式(布局/编辑器/表单/滚动条等)
- 设计文档和调查文档
- Halo 插件/主题开发 Agent Skills

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 21:03:00 +08:00

153 lines
4.5 KiB
Markdown

# Plugin Lifecycle
The plugin entry class extends `run.halo.app.plugin.BasePlugin` and must be annotated with `@Component`.
> Source references (Halo main branch):
>
> - [BasePlugin](https://github.com/halo-dev/halo/blob/main/api/src/main/java/run/halo/app/plugin/BasePlugin.java)
> - [PluginContext](https://github.com/halo-dev/halo/blob/main/api/src/main/java/run/halo/app/plugin/PluginContext.java)
## Entry Class Template
```java
package com.example.myplugin;
import org.springframework.stereotype.Component;
import run.halo.app.plugin.BasePlugin;
import run.halo.app.plugin.PluginContext;
@Component
public class MyPlugin extends BasePlugin {
public MyPlugin(PluginContext pluginContext) {
super(pluginContext);
}
@Override
public void start() {
// Called after classes are loaded and before the plugin is marked active
// Register schemes, initialize caches, start watchers, etc.
}
@Override
public void stop() {
// Called when the plugin is stopped (disabled)
// Unregister schemes, dispose watchers, clean up resources
}
@Override
public void delete() {
// Called when the plugin is uninstalled
// Final cleanup, delete external data if needed
}
}
```
## Lifecycle Behavior
| Method | When Called | Typical Actions |
| ---------- | ---------------------------------------------- | ------------------------------------------------------------------- |
| `start()` | After plugin classes loaded, before activation | `schemeManager.register()`, init caches, register watchers |
| `stop()` | When plugin is disabled | `schemeManager.unregister()`, dispose watchers, clear rate limiters |
| `delete()` | When plugin is uninstalled | Delete external resources, cleanup |
## Important Rules
1. **Only ONE class** may extend `BasePlugin` and be annotated with `@Component`. Multiple candidates cause startup failure.
2. **Must have `@Component`** (or other Spring stereotype). Without it, lifecycle methods are never invoked.
3. **Use constructor injection** for dependencies (preferred over `@Autowired` fields).
## Scheme Registration / Cleanup
```java
@Component
public class MyPlugin extends BasePlugin {
private final SchemeManager schemeManager;
public MyPlugin(PluginContext ctx, SchemeManager schemeManager) {
super(ctx);
this.schemeManager = schemeManager;
}
@Override
public void start() {
schemeManager.register(MyExtension.class, indexSpecs -> {
indexSpecs.add(IndexSpecs.<MyExtension, String>single("spec.slug", String.class)
.indexFunc(ext -> ext.getSpec().getSlug()));
});
}
@Override
public void stop() {
schemeManager.unregister(Scheme.buildFromType(MyExtension.class));
}
}
```
## Watchers
Watch extension changes for cache invalidation or reactive workflows:
```java
@Component
public class MyPlugin extends BasePlugin {
private final ReactiveExtensionClient client;
private Watcher watcher;
// ... constructor
@Override
public void start() {
watcher = new Watcher() {
private volatile boolean disposed = false;
@Override
public void onAdd(Extension extension) {
if (extension instanceof MyExtension) {
// handle add
}
}
@Override
public void onUpdate(Extension oldObj, Extension newObj) {
if (newObj instanceof MyExtension) {
// handle update
}
}
@Override
public void onDelete(Extension extension) {
if (extension instanceof MyExtension) {
// handle delete
}
}
@Override
public void dispose() { disposed = true; }
@Override
public boolean isDisposed() { return disposed; }
};
client.watch(watcher);
}
@Override
public void stop() {
if (watcher != null) watcher.dispose();
}
}
```
## RateLimiter Cleanup
If creating rate limiters via `RateLimiterRegistry`, always clean up in `stop()`:
```java
private final Set<String> limiterNames = ConcurrentHashMap.newKeySet();
@Override
public void stop() {
limiterNames.forEach(rateLimiterRegistry::remove);
}
```