{"record":{"id":"6df7dbe301878c28","repo":"dromara/Sa-Token","slug":"plugin-getclass-getcanonicalname","errorCode":null,"errorMessage":"插件 [ \" + plugin.getClass().getCanonicalName() + \" ] 已安装，不可重复安装","messagePattern":"插件 \\[ \" \\+ plugin\\.getClass\\(\\)\\.getCanonicalName\\(\\) \\+ \" \\] 已安装，不可重复安装","errorType":"validation","errorClass":"SaTokenPluginException","httpStatus":null,"severity":"error","filePath":"sa-token-core/src/main/java/cn/dev33/satoken/plugin/SaTokenPluginHolder.java","lineNumber":203,"sourceCode":"\t}\n\n\n\t// ------------------- 插件 Install 与 Destroy -------------------\n\n\t/**\n\t * 安装指定插件\n\t * @param plugin /\n\t */\n\tpublic synchronized SaTokenPluginHolder installPlugin(SaTokenPlugin plugin) {\n\n\t\t// 插件为空，拒绝安装\n\t\tif (plugin == null) {\n\t\t\tthrow new SaTokenPluginException(\"插件不可为空\");\n\t\t}\n\n\t\t// 插件已经被安装过了，拒绝再次安装\n\t\tif (isInstalledPlugin(plugin.getClass())) {\n\t\t\tthrow new SaTokenPluginException(\"插件 [ \" + plugin.getClass().getCanonicalName() + \" ] 已安装，不可重复安装\");\n\t\t}\n\n\t\t// 执行该插件的 install 前置钩子\n\t\t_consumeHooks(beforeInstallHooks, plugin.getClass());\n\n\t\t// 插件安装\n\t\tint consumeCount = _consumeHooks(installHooks, plugin.getClass());\n\t\tif (consumeCount == 0) {\n\t\t\tplugin.install();\n\t\t}\n\n\t\t// 执行该插件的 install 后置钩子\n\t\t_consumeHooks(afterInstallHooks, plugin.getClass());\n\n\t\t// 添加到插件集合\n\t\tpluginList.add(plugin);\n\n\t\t// 返回对象自身，支持连缀风格调用","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-core/src/main/java/cn/dev33/satoken/plugin/SaTokenPluginHolder.java#L185-L221","documentation":"SaTokenPluginException thrown by SaTokenPluginHolder.installPlugin(SaTokenPlugin) when a plugin of the same class (isInstalledPlugin(plugin.getClass())) is already installed. Sa-Token enforces one instance per plugin class to keep install/destroy hook bookkeeping deterministic; duplicate installation is rejected with a message naming the offending class's canonical name.","triggerScenarios":"Calling installPlugin twice with the same plugin class — including two different instances of the same class (the check is by Class, not by identity). Common in code that re-runs setup, or when both manual registration and an auto-configuration/starter path install the same plugin.","commonSituations":"Manually installing a plugin (e.g. sa-sso, sa-temp) that the starter already auto-installed; app code that installs plugins on each re-initialization (hot reload/devtools restart); copying example setup code into a project that already configures the plugin.","solutions":["Check before installing: if (!holder.isInstalledPlugin(MyPlugin.class)) holder.installPlugin(new MyPlugin())","If the plugin is auto-installed by a starter, remove the manual installPlugin call entirely","For hot-reload scenarios, call destroyPlugin first (or reset the holder) before re-installing"],"exampleFix":"// before\nSaManager.getSaTokenPluginHolder().installPlugin(new SaSsoPlugin()); // starter already installed it -> exception\n\n// after\nSaTokenPluginHolder holder = SaManager.getSaTokenPluginHolder();\nif (!holder.isInstalledPlugin(SaSsoPlugin.class)) {\n    holder.installPlugin(new SaSsoPlugin());\n}","handlingStrategy":"validation","validationCode":"SaTokenPluginHolder holder = SaManager.getSaTokenPluginHolder();\nif (!holder.isInstalledPlugin(MyPlugin.class)) {\n    holder.installPlugin(new MyPlugin());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Know which plugins your starter auto-installs before adding manual installPlugin calls","Wrap installPlugin with an isInstalledPlugin(Class) guard in any code that may re-run","In devtools/hot-reload setups, destroy plugins before re-installing them"],"tags":["plugin-system","duplicate-registration","lifecycle","sa-token"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}