dromara/Sa-Token · error · SaTokenPluginException
插件 [
Error message
插件 [
What it means
SaTokenPluginException thrown by SaTokenPluginHolder.destroyPlugin(SaTokenPlugin) when the plugin's class is not currently installed (isInstalledPlugin(plugin.getClass()) returns false). Destroying an uninstalled plugin would skip its lifecycle pairing, so the holder refuses, naming the plugin class's canonical name in '插件 [ x ] 未安装,无法卸载'.
Source
Thrown at sa-token-core/src/main/java/cn/dev33/satoken/plugin/SaTokenPluginHolder.java:251
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new SaTokenPluginException(e);
}
}
/**
* 卸载指定插件
* @param plugin /
*/
public synchronized SaTokenPluginHolder destroyPlugin(SaTokenPlugin plugin) {
// 插件为空,拒绝卸载
if (plugin == null) {
throw new SaTokenPluginException("插件不可为空");
}
// 插件未被安装,拒绝卸载
if (!isInstalledPlugin(plugin.getClass())) {
throw new SaTokenPluginException("插件 [ " + plugin.getClass().getCanonicalName() + " ] 未安装,无法卸载");
}
// 执行该插件的 destroy 前置钩子
_consumeHooks(beforeDestroyHooks, plugin.getClass());
// 插件卸载
int consumeCount = _consumeHooks(destroyHooks, plugin.getClass());
if (consumeCount == 0) {
plugin.destroy();
}
// 执行该插件的 destroy 后置钩子
_consumeHooks(afterDestroyHooks, plugin.getClass());
// 返回对象自身,支持连缀风格调用
return this;
}
View on GitHub (pinned to ac2c7f6e94)
Solutions
- Check installation state first: if (holder.isInstalledPlugin(MyPlugin.class)) holder.destroyPlugin(...)
- Track install/destroy symmetrically — destroy exactly once, in reverse order of installation
- Ensure only one teardown path runs (guard shutdown hooks with an AtomicBoolean, or rely solely on the container's lifecycle)
Example fix
// before
SaManager.getSaTokenPluginHolder().destroyPlugin(myPlugin); // never installed / already destroyed -> exception
// after
SaTokenPluginHolder holder = SaManager.getSaTokenPluginHolder();
if (holder.isInstalledPlugin(MyPlugin.class)) {
holder.destroyPlugin(myPlugin);
} Defensive patterns
Strategy: validation
Validate before calling
SaTokenPluginHolder holder = SaManager.getSaTokenPluginHolder();
if (holder.isInstalledPlugin(MyPlugin.class)) {
holder.destroyPlugin(myPlugin);
} Prevention
- Make destroy strictly symmetric with install: exactly once, reverse order
- Guard double teardown (context close + shutdown hook) with an idempotency flag
- Before destroying starter-installed plugins, confirm your code installed them
When it happens
Trigger: Calling destroyPlugin with an instance whose class was never installed, or was already destroyed — including a fresh instance of a class whose original instance was uninstalled earlier (the check is per class, and a second destroy of the same class also fails).
Common situations: Shutdown hooks that destroy plugins in a project where the starter auto-installed different plugin instances/classes; double teardown (context close + manual shutdown hook); hot-reload restarts that destroy twice; destroying a plugin that failed to install earlier (install threw partway).
Related errors
- 插件 [ " + plugin.getClass().getCanonicalName() + " ] 已安装,不可重复
- SPI 插件加载失败: " + e.getMessage()
- 插件不可为空
- 10002
- 10002
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/475ef8df37be561c.
Report an issue: GitHub.