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

  1. Check installation state first: if (holder.isInstalledPlugin(MyPlugin.class)) holder.destroyPlugin(...)
  2. Track install/destroy symmetrically — destroy exactly once, in reverse order of installation
  3. 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

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


AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14). Data as JSON: /api/errors/475ef8df37be561c. Report an issue: GitHub.