Spring Boot+JavaFX打包JAR启动报错:类找不到问题求助
问题描述
开发基于Spring Boot 3.4.5与JavaFX的应用,通过带启停按钮的GUI控制Spring Boot服务的启动、重启与停止。IDE开发模式下运行正常,但构建fat jar后运行时,点击启动服务按钮抛出ClassNotFoundException,找不到org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer类。
错误日志
[22:48:10] ▶ Starting server... [22:48:10] ❌ Failed to start server: Unable to instantiate factory class [org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer] for factory type [org.springframework.context.ApplicationContextInitializer] [22:48:10] 📋 java.lang.IllegalArgumentException: Unable to instantiate factory class [org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer] for factory type [org.springframework.context.ApplicationContextInitializer] [22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader$FailureHandler.lambda$throwing$0(SpringFactoriesLoader.java:641) [22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader$FailureHandler.lambda$handleMessage$3(SpringFactoriesLoader.java:665) [22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader.instantiateFactory(SpringFactoriesLoader.java:227) [22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader.load(SpringFactoriesLoader.java:202) [22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader.load(SpringFactoriesLoader.java:158) [22:48:10] 📋 at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:466) [22:48:10] 📋 at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:462) [22:48:10] 📋 at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:278) [22:48:10] 📋 at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:257) [22:48:10] 📋 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1362) [22:48:10] 📋 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1351) [22:48:10] 📋 at com.novastream.config.ServerControlGUI.lambda$startServer$16(ServerControlGUI.java:403) [22:48:10] 📋 at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1848) [22:48:10] 📋 at java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1840) [22:48:10] 📋 at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507) [22:48:10] 📋 at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1458) [22:48:10] 📋 at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2034) [22:48:10] 📋 at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:189) [22:48:10] 📋 Caused by: java.lang.ClassNotFoundException: org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer [22:48:10] 📋 at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) [22:48:10] 📋 at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) [22:48:10] 📋 at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528) [22:48:10] 📋 at java.base/java.lang.Class.forName0(Native Method) [22:48:10] 📋 at java.base/java.lang.Class.forName(Class.java:578) [22:48:10] 📋 at java.base/java.lang.Class.forName(Class.java:557) [22:48:10] 📋 at org.springframework.util.ClassUtils.forName(ClassUtils.java:321) [22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader.instantiateFactory(SpringFactoriesLoader.java:220) [22:48:10] 📋 ... 15 more
核心代码片段
主入口类
package com.novastream; import com.novastream.config.MediaConfig; import com.novastream.config.ServerControlGUI; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.scheduling.annotation.EnableAsync; @EnableAsync @SpringBootApplication @EnableConfigurationProperties(MediaConfig.class) public class NovastreamBackendApplication { public static void main(String[] args) { System.setProperty("java.awt.headless", "false"); ServerControlGUI.launch(ServerControlGUI.class); } }
ServerControlGUI核心代码
@Component public class ServerControlGUI extends Application { @Override public void start(Stage primaryStage) { primaryStage.initStyle(StageStyle.TRANSPARENT); DropShadow shadow = new DropShadow(20, Color.BLACK); shadow.setOffsetX(0); shadow.setOffsetY(10); VBox root = createMainLayout(primaryStage); root.setEffect(shadow); root.getStyleClass().add("main-container"); Rectangle clip = new Rectangle(800, 600); clip.setArcWidth(16); clip.setArcHeight(16); root.setClip(clip); Scene scene = new Scene(root, 800, 600); scene.setFill(Color.TRANSPARENT); scene .getStylesheets() .add(getClass().getResource("/static/gui.css").toExternalForm()); primaryStage.setScene(scene); primaryStage.setTitle("NovaStream"); primaryStage.show(); animateStartup(primaryStage); primaryStage.setOnCloseRequest(e -> { stopServer(); Platform.exit(); System.exit(0); }); setupSystemStreamRedirection(); } private VBox createControlPanel() { VBox panel = new VBox(); panel.getStyleClass().add("control-panel"); Label title = new Label("Server Controls"); title.getStyleClass().add("control-title"); HBox controls = new HBox(); controls.getStyleClass().add("controls-container"); startRestartBtn = createActionButton("▶ Start Server", "start-button", this::startServer); stopBtn = createActionButton("⏹ Stop Server", "stop-button", this::stopServer); clearLogsBtn = createActionButton("🗑 Clear Logs", "clear-button", this::clearLogs); stopBtn.setDisable(true); clearLogsBtn.setDisable(true); controls.getChildren().addAll(startRestartBtn, stopBtn, clearLogsBtn); panel.getChildren().addAll(title, controls); return panel; } private void startServer() { if (loadingIndicator.isVisible() || serverRunning) return; Platform.runLater(() -> { loadingIndicator.setVisible(true); updateStatusLabel("Starting...", "status-starting"); startRestartBtn.setDisable(true); stopBtn.setDisable(true); }); CompletableFuture.runAsync(() -> { try { appendLog("▶ Starting server..."); springContext = SpringApplication.run(NovastreamBackendApplication.class); serverRunning = true; startTime = LocalDateTime.now(); Platform.runLater(() -> { loadingIndicator.setVisible(false); updateStatusLabel("Running", "status-running"); startRestartBtn.setDisable(false); stopBtn.setDisable(false); updateStartRestartButton(); startUptimeTimer(); }); appendLog("✅ Server started successfully"); appendLog("🌐 Server running on http://localhost:8080"); } catch (Exception e) { Platform.runLater(() -> { loadingIndicator.setVisible(false); updateStatusLabel("Failed", "status-failed"); startRestartBtn.setDisable(false); }); appendLog("❌ Failed to start server: " + e.getMessage()); e.printStackTrace(); } }); } }
pom.xml配置
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.5</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.novastream</groupId> <artifactId>novastream-backend</artifactId> <version>0.0.1-SNAPSHOT</version> <name>novastream-backend</name> <description>NovaStream Backend</description> <url /> <licenses> <license /> </licenses> <developers> <developer /> </developers> <scm> <connection /> <developerConnection /> <tag /> <url /> </scm> <properties> <java.version>23</java.version> <javafx.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>3.2.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> <version>${javafx.version}</version> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> <version>${javafx.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </path> </annotationProcessorPaths> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.4.5</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
问题根源
ConfigurationWarningsApplicationContextInitializer类在Spring Boot 3.x版本中已被移除,但spring-boot-devtools依赖的META-INF/spring.factories文件中仍保留了该类的配置项。打包fat jar时,该配置被包含进去,导致Spring应用启动时尝试加载这个已不存在的类,从而抛出ClassNotFoundException。
解决步骤
移除spring-boot-devtools依赖
spring-boot-devtools是开发环境专属工具,生产打包无需包含,直接删除pom.xml中的对应依赖:<!-- 删除此段依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency>清理spring.factories配置(若需要)
如果移除devtools后仍有问题,检查项目中是否存在src/main/resources/META-INF/spring.factories文件,删除其中所有关于ConfigurationWarningsApplicationContextInitializer的配置行。优化Spring Boot打包插件配置
确保打包插件正确识别主类,避免依赖缺失。可在spring-boot-maven-plugin中明确指定主类:<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.4.5</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> <!-- 明确指定主类 --> <mainClass>com.novastream.NovastreamBackendApplication</mainClass> </configuration> </plugin>JavaFX打包适配(针对Launch4J)
若后续用Launch4J生成exe,确保JavaFX依赖被正确打包。可添加javafx-maven-plugin辅助处理:<plugin> <groupId>org.openjfx</groupId> <artifactId>javafx-maven-plugin</artifactId> <version>0.0.8</version> <configuration> <mainClass>com.novastream.NovastreamBackendApplication</mainClass> </configuration> </plugin>
验证
执行mvn clean package重新打包,运行java -jar target/novastream-backend-0.0.1-SNAPSHOT.jar,点击启动按钮确认服务正常启动。
内容的提问来源于stack exchange,提问作者Ishaan Vadhan

