Java @Value和@Autowired上的NPE
我有一个javafx+spring应用程序 应用程序侦听串行端口,读取数据并将其显示到UI。 outputLoggerFile上的控制器类和同一类上的serialPort由NPE引起的问题 这是我的带有PropertySource的配置文件,因此我的环境应该知道这些属性。 SpringConfigJava @Value和@Autowired上的NPE,java,spring,javafx,Java,Spring,Javafx,我有一个javafx+spring应用程序 应用程序侦听串行端口,读取数据并将其显示到UI。 outputLoggerFile上的控制器类和同一类上的serialPort由NPE引起的问题 这是我的带有PropertySource的配置文件,因此我的环境应该知道这些属性。 SpringConfig @Configuration @PropertySource({"classpath:com.properties", "classpath:application.properties"}) @Co
@Configuration
@PropertySource({"classpath:com.properties", "classpath:application.properties"})
@ComponentScan
public class SpringConfig {
@Bean
public SerialPort serialPort(@Value("${serialPort.portName}") String portName){
return new SerialPort(portName);
}
@Bean
public AnnotationMBeanExporter annotationMBeanExporter(){
AnnotationMBeanExporter annotationMBeanExporter = new AnnotationMBeanExporter();
annotationMBeanExporter.addExcludedBean("dataSource");
return annotationMBeanExporter;
}
}
这个类将我的属性设置为SerialPort对象,注入EventListener类和openning连接。很好。
ComReader
@Scope("singletone")
@Component
public class ComReader {
@Autowired
private EventListener eventListener;
@Autowired
public SerialPort serialPort;
@Value("${serialPort.baudRate}")
private int baudRate;
@Value("${serialPort.dataBits}")
private int dataBits;
@Value("${serialPort.stopBits}")
private int stopBits;
@Value("${serialPort.parity}")
private int parity;
@PostConstruct
public void init(){
try {
System.out.println("Opening port: " + serialPort.getPortName());
serialPort.openPort();
serialPort.setParams(baudRate,dataBits,stopBits,parity);
serialPort.addEventListener(eventListener, 1);
} catch (SerialPortException e) {
e.printStackTrace();
}
}
}
@org.springframework.stereotype.Controller
public class Controller {
@Value("${logger.outputFilePath}")
private String outputLoggerFile;
private SerialPort serialPort;
@Autowired
public void setSerialPort(SerialPort serialPort) {
this.serialPort = serialPort;
}
private static ObservableList<CallDetailRecord> list = FXCollections.observableArrayList();
@FXML
void initialize(){
Timer scheduler = new Timer();
scheduler.schedule(new TimerTask() {
@Override
public void run() {
if (serialPort.isOpened()) circlePortStatus.setFill(Color.GREEN); //(NPE HERE)
else circlePortStatus.setFill(Color.RED);
}
}, 5_000, 60_000);
counterCol.setCellValueFactory(new PropertyValueFactory<>("id"));
startTimeCol.setCellValueFactory(new PropertyValueFactory<>("startTime"));
stopTimeCol.setCellValueFactory(new PropertyValueFactory<>("stopTime"));
numberACol.setCellValueFactory(new PropertyValueFactory<>("numberB"));
numberBCol.setCellValueFactory(new PropertyValueFactory<>("numberA"));
rescodeCol.setCellValueFactory(new PropertyValueFactory<>("resultCode"));
subACol.setCellValueFactory(new PropertyValueFactory<>("subscriberB"));
subBCol.setCellValueFactory(new PropertyValueFactory<>("subscriberA"));
table.setItems(list);
Label webLinkLabel = new Label("Веб ресурс");
AppStart appStart = new AppStart();
webLinkLabel.setOnMouseClicked(event -> appStart.getHostServices().showDocument(getURLPropertie()));
webLink.setGraphic(webLinkLabel);
Label logsLinkLabel = new Label("Логи");
logsLinkLabel.setOnMouseClicked(event -> appStart.getHostServices().showDocument(outputLoggerFile)); //(NPE HERE)
logsLink.setGraphic(logsLinkLabel);
}
public void addCdr(CallDetailRecord cdr){
list.add(cdr);
list.sort(Comparator.comparingInt(CallDetailRecord::getId).reversed());
}
private String getURLPropertie(){
try(InputStream is = new FileInputStream(Objects.requireNonNull(getClass().getClassLoader().getResource("application.properties")).getFile())){
Properties prop = new Properties();
prop.load(is);
return prop.getProperty("url.link");
} catch (IOException e) {
e.printStackTrace();
}
return "https://google.com";
}
}
问题类一切正常,除了我想在这里注入的任何类/字段
控制器
@Scope("singletone")
@Component
public class ComReader {
@Autowired
private EventListener eventListener;
@Autowired
public SerialPort serialPort;
@Value("${serialPort.baudRate}")
private int baudRate;
@Value("${serialPort.dataBits}")
private int dataBits;
@Value("${serialPort.stopBits}")
private int stopBits;
@Value("${serialPort.parity}")
private int parity;
@PostConstruct
public void init(){
try {
System.out.println("Opening port: " + serialPort.getPortName());
serialPort.openPort();
serialPort.setParams(baudRate,dataBits,stopBits,parity);
serialPort.addEventListener(eventListener, 1);
} catch (SerialPortException e) {
e.printStackTrace();
}
}
}
@org.springframework.stereotype.Controller
public class Controller {
@Value("${logger.outputFilePath}")
private String outputLoggerFile;
private SerialPort serialPort;
@Autowired
public void setSerialPort(SerialPort serialPort) {
this.serialPort = serialPort;
}
private static ObservableList<CallDetailRecord> list = FXCollections.observableArrayList();
@FXML
void initialize(){
Timer scheduler = new Timer();
scheduler.schedule(new TimerTask() {
@Override
public void run() {
if (serialPort.isOpened()) circlePortStatus.setFill(Color.GREEN); //(NPE HERE)
else circlePortStatus.setFill(Color.RED);
}
}, 5_000, 60_000);
counterCol.setCellValueFactory(new PropertyValueFactory<>("id"));
startTimeCol.setCellValueFactory(new PropertyValueFactory<>("startTime"));
stopTimeCol.setCellValueFactory(new PropertyValueFactory<>("stopTime"));
numberACol.setCellValueFactory(new PropertyValueFactory<>("numberB"));
numberBCol.setCellValueFactory(new PropertyValueFactory<>("numberA"));
rescodeCol.setCellValueFactory(new PropertyValueFactory<>("resultCode"));
subACol.setCellValueFactory(new PropertyValueFactory<>("subscriberB"));
subBCol.setCellValueFactory(new PropertyValueFactory<>("subscriberA"));
table.setItems(list);
Label webLinkLabel = new Label("Веб ресурс");
AppStart appStart = new AppStart();
webLinkLabel.setOnMouseClicked(event -> appStart.getHostServices().showDocument(getURLPropertie()));
webLink.setGraphic(webLinkLabel);
Label logsLinkLabel = new Label("Логи");
logsLinkLabel.setOnMouseClicked(event -> appStart.getHostServices().showDocument(outputLoggerFile)); //(NPE HERE)
logsLink.setGraphic(logsLinkLabel);
}
public void addCdr(CallDetailRecord cdr){
list.add(cdr);
list.sort(Comparator.comparingInt(CallDetailRecord::getId).reversed());
}
private String getURLPropertie(){
try(InputStream is = new FileInputStream(Objects.requireNonNull(getClass().getClassLoader().getResource("application.properties")).getFile())){
Properties prop = new Properties();
prop.load(is);
return prop.getProperty("url.link");
} catch (IOException e) {
e.printStackTrace();
}
return "https://google.com";
}
}
如果我试图在另一个类中调试,使用它的控制器显示变量outputLoggerFile包含我的属性。我不知道为什么
sources-FXMLLoader的默认行为是通过实例化FXML文件的
fx:controller
属性中指定的类来创建控制器(调用其无参数构造函数);然后,它将@FXML
-注释字段注入控制器,并在解析FXML文件后,调用initialize()
方法(如果有)
由于控制器是通过直接调用其构造函数来实例化的,因此Spring应用程序上下文对其一无所知,并且不能将任何@Autowired
bean注入其中
要解决此问题,需要在FXMLLoader
上设置一个controllerFactory
,指示它从SpringApplicationContext
中“创建”(真正检索)控制器实例。控制器工厂基本上只是一个函数(一个@functioninterface
),它接受一个类
,并生成一个对象。由于这正是其中一个ApplicationContext.getBean()
方法的签名,因此其代码如下所示:
FXMLLoader loader = new FXMLLoader(getClass().getResource("/primal.fxml"));
loader.setControllerFactory(context::getBean);
Parent root = loader.load();
其中context
是SpringApplicationContext
(在加载FXML文件的方法中,您可能需要跳过一些限制来获取对它的引用;通常只是为它创建一个字段并注释该字段自动连线
起作用)
我还将对控制器类的配置进行一些调整。默认情况下,Spring将bean作为单例范围进行管理。这绝对不是您想要的:如果您第二次加载相同的FXML,您将需要一个不同的控制器实例(因为您将拥有一组不同的UI控件)。因此,您肯定需要将控制器作为原型来确定范围
其次,Spring@Controller
原型是为Spring MVC意义上的控制器设计的;所以我不认为这真的是你想要的(尽管我不认为这有什么坏处)。我将控制器类注释为
@Component
@Scope(BeanDefinition.PROTOTYPE_SCOPE)
public class Controller { /* ... */ }
您确定spring应用程序上下文(bean factory)正在管理您的
控制器
实例吗?您能否显示配置FXMLLoader来执行此操作的代码?this.primaryStage=primaryStage
Platform.setImplicitExit(false)代码>Parent root=fxmloader.load(getClass().getResource(“/primal.fxml”)代码>primaryStage.setTitle(“NIIAR”)代码>`primaryStage.getIcons().add(新图像(“/icon.png”);`<代码>primaryStage.setScene(新场景(root,1400900))代码>createTray()代码>primaryStage.show()代码>我想不是。但是我的控制器是管理UI的,用经典的方法来注释这个控制器是行不通的?真的很感激