奇怪的JavaFX NPE“位置是必需的”

时间:2018-04-20 09:20:30

标签: java maven intellij-idea javafx

我正在构建一个包含大量场景的大型JavaFX应用程序,现在正尝试使用Maven部署应用程序。

Maven期待资源,例如FXML文件,放在src/main/resources下。这与我将新场景加载到主场景容器中的方式发生冲突。

我正在使用jewelsea的小框架来加载新内容:Small JavaFX framework for swapping in and out child panes in a main FXML container.

到目前为止,这种方法运作良好。但是,现在我正在尝试从src/main/resources获取资源(FXML文件),我正在运行NullPointerExceptions。

我可以使用下面的方法加载我的MAIN视图,但所有其他视图都会抛出NPE。

MAIN场景加载如下:

FXMLLoader loader = new FXMLLoader();
Pane mainPane = loader.load(getClass().getResource(ViewNavigator.MAIN));
MainController mainController = loader.getController();
ViewNavigator.setMainController(mainController);
ViewNavigator.loadVista(ViewNavigator.CONFIGURE);

记下最后一行。我使用函数loadVista来注入CONFIGURE视图。此函数在ViewNavigator类中声明:

static void loadVista(String fxml) {
    try {
        mainController.setVista(
                FXMLLoader.load(
                        ViewNavigator.class.getClass().getResource(
                                fxml
                        )
                )
        );
    } catch (IOException e) {
        e.printStackTrace();
    }
}

正如您所看到的,完全相同的方法用于获取CONFIGURE场景,但这次我遇到了NPE:

Caused by: java.lang.NullPointerException: Location is required.
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3246)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3210)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3179)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3152)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3129)
at javafx.fxml/javafx.fxml.FXMLLoader.load(FXMLLoader.java:3122)
at com.demant.controller.ViewNavigator.loadVista(ViewNavigator.java:33)
at com.demant.controller.MainController.configureView(MainController.java:99)
... 58 more

ViewNavigator内,MAINCONFIGURE视图的网址声明如下:

static final String MAIN      = "MainControllerView.fxml";
static final String CONFIGURE = "ConfigureView.fxml";

我希望显然可以将我的资源文件放在src/main/resources下,并将该包标记为 resources root

希望有人能指出我正确的方向。

谢谢。

3 个答案:

答案 0 :(得分:1)

FXML文件是在运行时加载的,因此从某种意义上说,源代码层次结构的组织完全无关紧要;唯一重要的是类路径的内容。当然,在这种情况下,您的IDE通过Maven确定如何编译源层次结构中的源代码和资源,并将其部署到类路径层次结构中。

为了使这项工作成功,您需要了解两件事:第一,在不同源文件夹中定义的各种资源的类路径中的最终位置,第二,Class.getResource(resourceName)如何解决指定的resourceName来查找资源。

对于第一个问题,Maven喜欢将需要编译为类文件的Java源文件与其他"静态"资源。 (这在Web应用程序中是有意义的,在这些应用程序中,这些应用程序通常部署到不同的位置;在桌面应用程序中它不太合理;但这有点偏离主题。)

默认情况下,Java源代码位于文件夹src/main/java中。编译该文件夹中的源代码,并将生成的类文件部署到构建文件夹(默认情况下为target/classes)。关于Java包的通常规则适用,因此如果在名为Main的包中有一个类org.example,则源代码将位于src/main/java/org/example/Main.java中,编译后的代码将位于{{1}中}}。

默认情况下,非Java资源位于target/classes/org/example/Main.class。此文件夹中的文件被部署(即复制)到同一个构建文件夹。因此,如果你有一个FXML文件,比如src/main/resources,并且你希望它最终放在包Sample.fxml中,你可以将它放在org.example中。构建项目时,它将被复制到src/main/resources/org/example/Sample.fxml

对于第二个问题,target/classes/org/example/Sample.fxml实例具有Class方法,该方法将返回表示资源的URL。 (因此,如果从文件系统中的类运行,它将返回带有getResource(resourceName)方案的URL;如果从jar文件运行,它将返回带有file:方案的URL等等。我喜欢将jar:视为含义"在找到当前类的同一个地方找到资源"。如果您的资源名称是绝对,意味着它以前导Class.getResource(...)开头,那么将从类路径的根搜索资源。如果资源名称是 relative ,即它不以/开头,则从当前类的包中搜索资源。请注意,资源名称的组件(/之间的部分)必须是有效的Java标识符。特别是,这意味着您不能假设您正在从文件系统加载资源(通常您不是),并且包含/.的名称无效。因此,您只能搜索当前类的包,该包的子包或使用绝对资源名称。

这是一个简单的例子。假设我们正在使用联系信息制作通常的演示JavaFX程序。我们需要(至少)一个主要类,我将调用..,一个FXML文件,ContactApp和一个控制器类Contacts.fxml。我将主类ContactController放在名为ContactApp的包中,将控制器类放在子包org.jamesd.fxtest中。

我喜欢将FXML文件放在与其相应控制器类相同的包中(这样做的好处很快就会显现),因此org.jamesd.fxtest.contacts也会进入Contacts.fxml

以下是项目布局的屏幕截图:

enter image description here

要确认这些是按预期部署的,请使用系统文件浏览器(Finder,Windows资源管理器等)查看" build"文件夹,默认为org.jamesd.fxtest.contacts

enter image description here

正如您所见,正如预期的那样,FXML文件和控制器类已部署到包target/classes,主类已部署到org.jamesd.fxtest.contacts

要从org.jamesd.fxtest类加载FXML文件,我可以指定绝对资源名称(同样,这意味着相对于类路径):

ContactApp

或者,我可以使用相对资源名称。由于当前类位于URL location = getClass().getResource("/org/jamesd/fxtest/contacts/Contacts.fxml"); 且我想要的资源位于org.jamesd.fxtest,因此以下内容将起作用:

org.jamesd.fxtest.contacts

我更喜欢的方法是将FXML文件放在与其相应控制器相同的包中。这样做的好处是我可以使用控制器类来加载资源。这里的一个优点是重构事物要容易得多,因为代码不太依赖于FMXL文件的实际位置(任何IDE都可以轻松处理重构导入)。这是使用此方法的整个URL location = getClass().getResource("contacts/Contacts.fxml"); 课程。

ContactApp

因此,在您的示例中,我建议将您的FXML文件放在与package org.jamesd.fxtest; import java.net.URL; import org.jamesd.fxtest.contacts.ContactController; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class ContactApp extends Application { @Override public void start(Stage primaryStage) throws Exception { URL location = ContactController.class.getResource("Contacts.fxml"); FXMLLoader loader = new FXMLLoader(location); Parent root = loader.load() ; Scene scene = new Scene(root); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } } 相同的包中:这意味着如果ViewNavigator位于包ViewNavigator中,那么FXML文件就会出现在com.demant.controller。然后只需使用src/main/resources/com/demant/controller即可。如果它不起作用,那么您可以在系统文件浏览器中打开ViewNavigator.class.getResource("ConfigureView.fxml")来诊断问题。

最后,请注意您想要

target/classes

ViewNavigator.class.getResource(...)

在后一段代码中,ViewNavigator.class.getClass().getResource(...) 解析为ViewNavigator.class的实例,因此Class<ViewNavigator>解析为ViewNavigator.class.getClass()的实例。由于Class<Class<?>>位于Class,您将在类路径中搜索java.lang,这显然不存在(并且不应该存在)。

答案 1 :(得分:0)

您在Class.class上使用ViewNavigator.class。 (Class计算包含ViewNavigator类信息的getClass()对象,getResource返回此对象的类型。)/java/lang/ResourceName因此正在寻找路径ViewNavigator中的资源。

您需要使用mainController.setVista( FXMLLoader.load( ViewNavigator.class.getResource( fxml ) ) ); 类来解析资源:

static final String MAIN      = "/com/demant/controller/MainControllerView.fxml";
static final String CONFIGURE = "/com/demant/controller/ConfigureView.fxml";

或使用绝对路径:

import urllib2
from bs4 import BeautifulSoup
import sys
import StringIO
import re


search_term = 'automobile'
patent_list = []
for i in range(100): #for the first 100 pages of results
    web_page = 'https://www.lens.org/lens/search?q=' + str(search_term) + '&sat=P&l=en&st=true&p=' + str(i) + '&n=100'
    page = urllib2.urlopen(web_page)
    soup = BeautifulSoup(page,'html.parser')

    for aref in soup.findAll("a",href=True):
        if re.findall('/lens/patent',aref['href']):
            link = aref['href']
            split_link = link.split('/')
            if len(split_link) == 4:
                patent_list.append(split_link[-1])

print '\n'.join(set(patent_list))

答案 2 :(得分:-2)

在fxml文件中,您需要指定要使用的控制器

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import java.util.ArrayList?>

<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.MyController">
    <Label  fx:id="label1"/>
    <Label  fx:id="label2"/>
</VBox>