{"record":{"id":"96c58ab7af8bf03d","repo":"dromara/Sa-Token","slug":"error","errorCode":null,"errorMessage":"插件不可为空","messagePattern":"插件不可为空","errorType":"validation","errorClass":"SaTokenPluginException","httpStatus":null,"severity":"error","filePath":"sa-token-core/src/main/java/cn/dev33/satoken/plugin/SaTokenPluginHolder.java","lineNumber":198,"sourceCode":"\t\t\t\ti--;\n\t\t\t\tconsumeCount++;\n\t\t\t}\n\t\t}\n\t\treturn consumeCount;\n\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());","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-core/src/main/java/cn/dev33/satoken/plugin/SaTokenPluginHolder.java#L180-L216","documentation":"SaTokenPluginException thrown by SaTokenPluginHolder.installPlugin(SaTokenPlugin) when the plugin argument is null. Installation would immediately NPE on plugin.getClass(), and a null plugin is always a caller bug, so the holder rejects it before touching any hook or install logic.","triggerScenarios":"Calling saTokenPluginHolder.installPlugin(null) (directly or via SaManager.getSaTokenPluginHolder()) — e.g. registering a bean that a DI context failed to provide, or a conditionally constructed plugin that was null.","commonSituations":"Auto-configuration loops registering optional plugin beans without null checks; getBean returning null for an optional dependency; test code passing a mock that returned null; refactoring left a registration call but removed the construction.","solutions":["Guard the call: if (plugin != null) holder.installPlugin(plugin)","Fix the source of the null (bean definition, factory method) — a plugin that cannot be constructed should fail construction loudly","In startup loops, filter nulls before registering"],"exampleFix":"// before\nSaTokenPlugin p = env.isEnabled(\"x\") ? new XPlugin() : null;\nSaManager.getSaTokenPluginHolder().installPlugin(p); // -> 插件不可为空\n\n// after\nif (p != null) {\n    SaManager.getSaTokenPluginHolder().installPlugin(p);\n}","handlingStrategy":"validation","validationCode":"if (plugin != null) {\n    SaManager.getSaTokenPluginHolder().installPlugin(plugin);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Null-check optional plugin beans before installing","Filter nulls when installing from a collection of candidate plugins","Treat a null plugin reference as a construction bug; fix the factory, not the call site only"],"tags":["plugin-system","validation","null-safety","sa-token"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}