Files
halo-dark-mode-plugin/.agents/skills/halo-plugin-dev/references/server-shared-beans.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

13 KiB

Shared Beans (Dependency Injection)

Halo exposes several core beans that any plugin can inject via constructor injection.

Source references (Halo main branch):

Docs Routing

This file lists frequently used beans, not every injectable type in Halo. For method signatures and newer APIs, verify against the official docs or source before coding.

Need Official docs
ExtensionClient / ReactiveExtensionClient https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/server/extension-client.md
ExtensionGetter and extension points https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/server/extension-getter.md
SettingFetcher / ReactiveSettingFetcher https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/server/setting-fetcher.md
Notifications https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/server/notification.md
Reverse proxy helpers https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/server/reverseproxy.md
WebSocket helpers https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/server/websocket.md
Login handler enhancement https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/server/login-handler-enhancer.md
Template and finder helpers for themes https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/server/template-for-theme.md

When working from a local docs checkout, use the same paths under docs/developer-guide/...; versioned docs live under versioned_docs/version-2.25/....

Quick Index

ReactiveExtensionClient

Reactive CRUD for custom extensions.

private final ReactiveExtensionClient client;

// List with options
client.listBy(Person.class, query.toListOptions(), query.toPageRequest());

// Get by name
client.fetch(Person.class, "person-name");

// Create
client.create(person);

// Update
client.update(person);

// Delete
client.delete(person);

ExtensionClient

Blocking version of ReactiveExtensionClient. Use only in non-NIO threads (e.g., background tasks).

SchemeManager

Register/unregister custom extension types.

schemeManager.register(Person.class);
schemeManager.register(Person.class, indexSpecs -> { /* ... */ });
schemeManager.unregister(Scheme.buildFromType(Person.class));

ExtensionGetter

Retrieve implementations of an extension point.

private final ExtensionGetter extensionGetter;

// Get all implementations
extensionGetter.getExtensions(AttachmentHandler.class);

UserService

User operations: get info, update password, create users.

ReactiveUserDetailsService

Mono<UserDetails> findByUsername(String username);

RoleService

Role operations: query roles, bindings, dependencies.

AttachmentService

Upload, delete, get access URLs for attachments.

PostContentService

Get post content with version management.

postContentService.getHeadContent(postName);      // latest draft
postContentService.getReleaseContent(postName);   // published version
postContentService.getSpecifiedContent(snapshotName);
postContentService.listSnapshots(postName);

ExternalLinkProcessor

Convert relative URLs to absolute using configured externalUrl.

externalLinkProcessor.processLink("/post/1");
// -> "https://example.com/post/1"

ExternalUrlSupplier

Get the configured external URL.

NotificationReasonEmitter / NotificationCenter

Send notifications and manage subscriptions.

ServerSecurityContextRepository

Access authentication context. Required if your filter runs before Spring Security's ReactorContextWebFilter.

CryptoService

Decrypt login passwords or reuse the public key.

cryptoService.readPublicKey();
cryptoService.decrypt(encryptedPassword);

RateLimiterRegistry

Create rate limiters (remember to clean up in stop()).

var rateLimiter = rateLimiterRegistry.rateLimiter(key,
    new RateLimiterConfig.Builder()
        .limitForPeriod(1)
        .limitRefreshPeriod(Duration.ofSeconds(60))
        .build());

SystemInfoGetter (Halo 2.20.11+)

Mono<SystemInfo> info = systemInfoGetter.get();
// Contains: title, subtitle, logo, favicon, url, version, seo, locale, timeZone, activatedThemeName

ReactiveSettingFetcher / SettingFetcher

Fetch plugin configuration defined in plugin.yaml (settingName / configMapName). The fetcher caches values internally and auto-refreshes when configuration changes.

Reactive (WebFlux):

private final ReactiveSettingFetcher settingFetcher;

// Fetch a typed config object by group name
settingFetcher.fetch("seo", SeoSetting.class)
    .doOnNext(seo -> { ... });

// Get raw JsonNode by group
settingFetcher.getSettingValue("seo");

// Get all groups as a map
settingFetcher.getSettingValues();

Blocking (background tasks / non-reactive code):

private final SettingFetcher settingFetcher;

Optional<SeoSetting> seo = settingFetcher.fetch("seo", SeoSetting.class);
JsonNode raw = settingFetcher.getSettingValue("seo");
Map<String, JsonNode> all = settingFetcher.getSettingValues();

Listen for config changes:

@EventListener
public void onConfigUpdated(PluginConfigUpdatedEvent event) {
    if (event.getNewConfig().containsKey("seo")) {
        // re-apply configuration
    }
}

Prerequisite: In plugin.yaml, declare settingName: my-settings and configMapName: my-configmap, and create a corresponding settings.yaml extension resource defining the form schema and groups.

JsonUtils

JSON serialization/deserialization utilities.

Note: @Deprecated(forRemoval = true, since = "2.23.0") — prefer tools.jackson.databind.json.JsonMapper in newer Halo versions.

// Object to JSON string
String json = JsonUtils.objectToJson(myObject);

// JSON string to object
MyObject obj = JsonUtils.jsonToObject(json, MyObject.class);

// JSON string with generics
Set<String> tags = JsonUtils.jsonToObject(json, new TypeReference<>() {});

// Map to object
MyObject obj = JsonUtils.mapToObject(map, MyObject.class);

// Deep copy
MyObject copy = JsonUtils.deepCopy(original);

// Access the underlying ObjectMapper
ObjectMapper mapper = JsonUtils.mapper();
mapper.convertValue(data, new TypeReference<>() {});

ExtensionUtil / MetadataUtil

Utility methods for Extension lifecycle and metadata.

// Check deletion state
boolean deleted = ExtensionUtil.isDeleted(extension);

// Query for non-deleted resources (for ListOptions)
ListOptions.builder().fieldQuery(ExtensionUtil.notDeleting()).build();

// Finalizer management
ExtensionUtil.addFinalizers(metadata, Set.of("my-plugin/finalizer"));
ExtensionUtil.removeFinalizers(metadata, Set.of("my-plugin/finalizer"));

// Default sort: creationTimestamp desc, name asc
Sort sort = ExtensionUtil.defaultSort();

// Safe metadata access (auto-initializes null collections)
Map<String, String> labels = MetadataUtil.nullSafeLabels(extension);
Map<String, String> annotations = MetadataUtil.nullSafeAnnotations(extension);

PageRequestImpl / PageRequest

Pagination for list queries. Page numbers are 1-based.

// Basic pagination
PageRequestImpl.of(page, size);                          // page >= 1, size <= 1000
PageRequestImpl.of(page, size, Sort.by("name"));
PageRequestImpl.ofSize(size);                            // page 1, given size

// With sorting
PageRequestImpl.of(1, 20, Sort.by(
    Sort.Order.desc("metadata.creationTimestamp"),
    Sort.Order.asc("metadata.name")
));

SortResolver / SortableRequest

Resolve sorting from HTTP query parameters.

// In a CustomEndpoint handler
Sort sort = SortResolver.defaultInstance.resolve(exchange);

// Or extend SortableRequest for standardized list queries
public class MyQuery extends SortableRequest {
    public MyQuery(ServerWebExchange exchange) { super(exchange); }

    public ListOptions toListOptions() {
        return labelAndFieldSelectorToListOptions(getLabelSelector(), getFieldSelector());
    }
}

// Usage
MyQuery query = new MyQuery(request.exchange());
client.listBy(MyExtension.class, query.toListOptions(), query.toPageRequest());

Query parameter format: ?sort=field,asc or ?sort=field,desc. Multiple sorts: ?sort=field1,asc&sort=field2,desc.

AnonymousUserConst

Check for anonymous users:

if (AnonymousUserConst.isAnonymousUser(authentication.getName())) {
    // Handle anonymous user
}
// Constants: AnonymousUserConst.PRINCIPAL ("anonymousUser"), AnonymousUserConst.Role ("anonymous")

BackupRootGetter / PluginsRootGetter

Supplier<Path> for backup directory and plugin directory.

LoginHandlerEnhancer

Hook into login success/failure for custom logic.