JavaFX+Afterburner.fx项目FXML文件加载失败问题求助
执行mvn clean javafx:run时出现FXML文件加载失败错误:
File: recipelistpresenter.fxml not found, attempting with camel case Cannot load file RecipeListPresenter.fxml Stopping initialization phase... Exception in Application start method java.lang.reflect.InvocationTargetException ...
已确认RecipeListPresenter.fxml存在于target目录:
-rw-rw-r-- 1 amaury amaury 291 janv. 11 21:23 RecipeListPresenter.fxml
项目代码
Main.java
package com.example.eatti; import com.airhacks.afterburner.injection.Injector; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; import com.example.eatti.view.recipe.RecipeListPresenter; public class Main extends Application { @Override public void start(Stage stage) { RecipeListPresenter recipeListPresenter = new RecipeListPresenter(); Scene scene = new Scene(recipeListPresenter.getView()); stage.setTitle("Recipe Manager"); stage.setScene(scene); stage.show(); } @Override public void stop() throws Exception { Injector.forgetAll(); // Cleanup Afterburner.fx injections } public static void main(String[] args) { launch(args); } }
FXMLView.java
package com.example.eatti.view; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FXMLView { private static final Logger log = LoggerFactory.getLogger(FXMLView.class); public static void load(String fxmlPath, Stage stage) { try { log.debug("Loading FXML from path: {}", fxmlPath); FXMLLoader loader = new FXMLLoader(FXMLView.class.getResource(fxmlPath)); Parent root = loader.load(); log.debug("FXML successfully loaded: {}", fxmlPath); Scene scene = new Scene(root); stage.setScene(scene); stage.show(); } catch (Exception e) { log.error("Error loading FXML file: {}", fxmlPath, e); throw new RuntimeException(e); } } }
RecipeListPresenter.java
package com.example.eatti.view.recipe; import javax.inject.Inject; import com.airhacks.afterburner.views.FXMLView; import com.example.eatti.model.entity.Recipe; import com.example.eatti.service.RecipeService; import javafx.fxml.FXML; import javafx.scene.Scene; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.input.MouseEvent; import javafx.stage.Stage; import javafx.util.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.UUID; public class RecipeListPresenter extends FXMLView { private static final Logger log = LoggerFactory.getLogger(RecipeListPresenter.class); @Inject private RecipeService recipeService; @FXML private ListView<Pair<UUID, String>> recipeListView; @FXML public void initialize() { loadRecipes(); setupListView(); } private void loadRecipes() { List<Recipe> recipes = recipeService.findAllRecipes(); for (Recipe recipe : recipes) { Pair<UUID, String> recipeItem = new Pair<>(recipe.getId(), recipe.getName()); recipeListView.getItems().add(recipeItem); } } private void setupListView() { recipeListView.setCellFactory(param -> new ListCell<Pair<UUID, String>>() { @Override protected void updateItem(Pair<UUID, String> item, boolean empty) { super.updateItem(item, empty); if (empty || item == null) { setText(null); } else { setText(item.getValue()); } } }); recipeListView.setOnMouseClicked(event -> { if (event.getClickCount() == 2) { handleRecipeDoubleClick(event); } }); } private void handleRecipeDoubleClick(MouseEvent event) { if (event.getClickCount() == 2) { Pair<UUID, String> selectedRecipe = recipeListView.getSelectionModel().getSelectedItem(); if (selectedRecipe != null) { UUID selectedRecipeId = selectedRecipe.getKey(); Recipe recipe = recipeService.findRecipeById(selectedRecipeId); if (recipe != null) { openRecipeDetails(recipe); } } } } private void openRecipeDetails(Recipe recipe) { log.debug("Opening recipe: {}", recipe.getName()); Stage stage = new Stage(); RecipeDetailsPresenter recipeDetailsPresenter = new RecipeDetailsPresenter(); recipeDetailsPresenter.setRecipe(recipe); stage.setScene(new Scene(recipeDetailsPresenter.getView())); stage.show(); } }
目录结构
src/main/ ├── java │ └── com │ └── example │ └── eatti │ ├── controller │ ├── Main.java │ ├── model │ │ ├── entity │ │ │ ├── Ingredient.java │ │ │ ├── RecipeIngredient.java │ │ │ ├── Recipe.java │ │ │ ├── RecipeUnit.java │ │ │ └── Unit.java │ │ └── repository │ │ ├── IngredientRepository.java │ │ ├── RecipeIngredientRepository.java │ │ ├── RecipeRepository.java │ │ ├── RecipeUnitRepository.java │ │ └── UnitRepository.java │ ├── service │ │ └── RecipeService.java │ ├── util │ │ └── HibernateUtil.java │ └── view │ ├── FXMLView.java │ └── recipe │ ├── RecipeDetailsPresenter.java │ ├── RecipeFormPresenter.java │ └── RecipeListPresenter.java └── resources ├── application.properties ├── com │ └── example │ └── eatti │ └── fxml │ ├── recipe-details.fxml │ ├── recipe-form.fxml │ └── RecipeListPresenter.fxml └── hibernate.cfg.xml
当前疑问:
- 如何检查当前的文件加载位置?
- 如何修改加载路径以适配项目结构需求?
1. 检查当前文件加载位置
在RecipeListPresenter的initialize方法开头添加调试代码,打印程序实际查找资源的路径:
// 打印当前类查找FXML的路径 System.out.println("Looking for FXML at: " + getClass().getResource("RecipeListPresenter.fxml")); // 打印类路径根目录 System.out.println("Classpath root: " + getClass().getResource("/"));
执行mvn clean javafx:run后,控制台会输出实际查找路径,对比target/classes/com/example/eatti/fxml/RecipeListPresenter.fxml的实际位置,即可定位路径差异。
另外也可以直接查看target/classes目录的结构,确认FXML文件的最终存放路径。
2. 修改加载路径适配项目结构
Afterburner.fx默认会在Presenter类所在包路径下查找同名FXML文件,而你的文件放在resources/com/example/eatti/fxml/目录下,需要手动指定路径:
方法一:重写getResource方法
在RecipeListPresenter中重写getResource()方法,指定正确的FXML路径:
@Override public URL getResource() { return getClass().getResource("/com/example/eatti/fxml/RecipeListPresenter.fxml"); }
路径开头的/表示从类路径根目录(即target/classes)开始查找。
方法二:统一配置基础Presenter类
如果所有FXML都放在fxml目录下,可自定义基础Presenter类统一处理路径:
package com.example.eatti.view; import com.airhacks.afterburner.views.FXMLView; import java.net.URL; public class BasePresenter extends FXMLView { @Override public URL getResource() { String fxmlName = getClass().getSimpleName() + ".fxml"; return getClass().getResource("/com/example/eatti/fxml/" + fxmlName); } }
然后让RecipeListPresenter继承这个BasePresenter,替换原有的FXMLView继承:
public class RecipeListPresenter extends BasePresenter { // 原有代码不变 }
方法三:调整FXML文件位置(不推荐)
将RecipeListPresenter.fxml移动到resources/com/example/eatti/view/recipe/目录下,与Presenter类同包,这样Afterburner.fx可自动找到文件,但会打乱当前目录结构,不推荐使用。
内容的提问来源于stack exchange,提问作者mopmop

