I’ve been trying to implement a plugin(extension) feature for my Spring Boot application. Problem is I’m getting NoClassDefFoundError
for fat JAR classes I’m loading.
I successfully loaded Plugin using ServiceLoader, but I get NoClassDefFoundError
for classes in dependency library in the plugin.
Project Structure
server (spring-boot)
├── plugins/plugin-module-1.0.0.jar
├── src
├── build.gradle
├── plugin-module
│ ├── libs/vendor-crypto.jar
│ ├── build.gradle
│ ├── src
PluginLoader.java
@Component
public class PluginLoader {
@Value("${plugins.path:./plugins}")
private String pluginsPath;
public List<Plugin> loadPlugins() throws Exception {
File dir = new File(pluginsPath);
File[] files = dir.listFiles((d, name) -> name.endsWith(".jar"));
List<Plugin> plugins = new ArrayList<>();
if (files != null) {
for (File file : files) {
URL[] urls = {file.toURI().toURL()};
try (URLClassLoader classLoader = new URLClassLoader(urls, this.getClass().getClassLoader())) {
Thread.currentThread().setContextClassLoader(classLoader);
ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class, classLoader);
for (Plugin plugin : loader) {
plugins.add(plugin);
}
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
}
}
}
return plugins;
}
}
plugin-module/build.gradle
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '7.1.2'
}
group = 'com.example'
version = '1.0.0'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
flatDir {
dirs("libs")
}
}
dependencies {
implementation project(':plugin-common')
// Vendor
implementation 'com.vendor.crypto:SampleCore:1.0.0'
// test
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
jar {
manifest {
attributes(
'Implementation-Title': 'Vendor Plugin',
'Implementation-Version': version,
'Plugin-Class': 'com.example.plugins.VendorPlugin'
)
}
}
tasks.named('test') {
useJUnitPlatform()
}
task printVendor {
doLast {
println "Building for vendor: $vendor"
}
}
tasks.named('build') {
dependsOn shadowJar
}
Error Details
Caused by: java.lang.NoClassDefFoundError: com/vendor/crypto/SampleCore
at com.vendor.crypto.core.SomeClass.someMethod(SomeClass.java:10)
at com.vendor.plugins.VendorPlugin.someMethod(VendorPlugin.java:10)
at com.vendor.plugins.VendorPlugin.performAction(VendorPlugin.java:20)
at com.example.pluginloader.PluginLoader.loadPlugins(PluginLoader.java:25)
at com.example.pluginloader.PluginLoader.loadPlugins(PluginLoader.java:15)
...
The plugin-module is built using the Shadow plugin to create a fat JAR. I have verified that the SampleCore.class exists in the JAR file (plugin-module) located in the plugins folder. It is originally from the dependency library fatjar.
It seems that some classes have been successfully loaded by ServiceLoader and some are not. The problem would be solved by declaring the vendor-crypto dependency inside server/build.gradle as in plugin-module, but I only want to manage that in the plugin-module project.
Let me know if any additional information is needed.