dromara/Sa-Token · error · SaTokenPluginException

SPI 插件加载失败: " + e.getMessage()

Error message

SPI 插件加载失败: " + e.getMessage()

What it means

SaTokenPluginException thrown by SaTokenPluginHolder's SPI loader (outer catch) when loading sa-token plugins from META-INF/services fails for any reason outside the per-file reading block — e.g. failing to locate/open the service resource itself, classloader errors, or exceptions raised while iterating providers. The message embeds the underlying cause's message ('SPI 插件加载失败: ' + e.getMessage()) and the original exception is chained, so the real reason is in getCause().

Source

Thrown at sa-token-core/src/main/java/cn/dev33/satoken/plugin/SaTokenPluginHolder.java:111

		try {
			ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			Enumeration<URL> resources = classLoader.getResources(path);
			while (resources.hasMoreElements()) {
				URL url = resources.nextElement();
				try (InputStream is = url.openStream()) {
					BufferedReader reader = new BufferedReader(new InputStreamReader(is));
					String line;
					while ((line = reader.readLine()) != null) {
						line = line.trim();
						// 忽略空行和注释行
						if (!line.isEmpty() && !line.startsWith("#")) {
							Class<?> clazz = Class.forName(line, true, classLoader);
							T instance = serviceInterface.cast(clazz.getDeclaredConstructor().newInstance());
							providers.add(instance);
						}
					}
				} catch (Exception e) {
					throw new SaTokenPluginException("SPI 插件加载失败: " + e.getMessage(), e);
				}
			}
		} catch (Exception e) {
			throw new SaTokenPluginException("SPI 插件加载失败: " + e.getMessage(), e);
		}
		return providers;
	}



	// ------------------- 插件管理 -------------------

	/**
	 * 所有插件的集合
	 */
	private final List<SaTokenPlugin> pluginList = new ArrayList<>();

	/**

View on GitHub (pinned to ac2c7f6e94)

Solutions

  1. Inspect the chained cause: catch SaTokenPluginException and read getCause() to find the real failure (ClassNotFound, IOException, etc.)
  2. Verify the sa-token integration jars' META-INF/services files survive packaging (enable ServicesResourceTransformer in maven-shade-plugin)
  3. Align all sa-token modules to one version and remove duplicates from the dependency tree (mvn dependency:tree)

Example fix

// before: shade plugin merges jars without merging services
// startup -> SaTokenPluginException: SPI 插件加载失败: ...

// after (pom.xml)
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <configuration>
    <transformers>
      <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
    </transformers>
  </configuration>
</plugin>
Defensive patterns

Strategy: try-catch

Validate before calling

// before relying on SPI plugins, verify the service resources are visible:
Enumeration<URL> urls = getClass().getClassLoader().getResources("META-INF/services/cn.dev33.satoken.plugin.SaTokenPlugin");
if (!urls.hasMoreElements()) { /* no plugins will load; check packaging */ }

Try / catch

try {
    // trigger plugin loading
} catch (SaTokenPluginException e) {
    Throwable cause = e.getCause();
    // classify: ClassNotFound -> version/classpath; IOException -> packaging; log cause for diagnosis
    throw e;
}

Prevention

When it happens

Trigger: SaTokenPluginHolder's service-loading routine runs at startup; any exception other than the inner line-parsing/instantiation block (e.g. getResources() failure, stream/IO errors at the outer level) lands in this outer catch and is rethrown as 'SPI 插件加载失败: ...'.

Common situations: Broken fat-jar/shaded jar that mangles META-INF/services entries; classloader restrictions in app servers (Tomcat shared libs, OSGi) preventing resource access; a service file naming a class whose static initializer throws; conflicting sa-token plugin versions on the classpath.

Related errors


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