NPE на @Value и @Autowired - PullRequest
       1

NPE на @Value и @Autowired

0 голосов
/ 23 апреля 2020

У меня есть приложение javafx + spring.

Приложение прослушивает последовательный порт, читает данные и показывает их в пользовательском интерфейсе. Проблема, вызванная NPE с классом Controller для outputLoggerFile и serialPort для одного класса.

Это мой файл конфигурации с PropertySource, поэтому моя среда должна знать об этих свойствах. SpringConfig

@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 и открывающее соединение. Работает отлично. 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";
    }
}

Это код, который загружает и отображает F XML:

this.primaryStage = primaryStage; 
Platform.setImplicitExit(false); 
Parent root = FXMLLoader.load(getClass().getResource("/primal.fxml")); 
primaryStage.setTitle("NIIAR"); 
primaryStage.getIcons().add(new Image("/icon.png")); 
primaryStage.setScene(new Scene(root, 1400, 900)); 
createTray(); 
primaryStage.show();

Если я пытаюсь отладить в другом классе, который, используя Controller, показывает, что переменная outputLoggerFile содержит мое свойство ie , Я понятия не имею, почему.

источников - https://github.com/mindgame73/CDRListener-FX

1 Ответ

1 голос
/ 23 апреля 2020

Поведение по умолчанию FXMLLoader заключается в создании контроллера путем создания экземпляра класса, указанного в атрибуте fx:controller файла F XML (вызывая его конструктор без аргументов); затем он вводит @FXML -аннотированные поля в контроллер и после анализа файла F XML вызывает метод initialize() (если он есть).

Поскольку экземпляр создается с помощью непосредственно вызывая свой конструктор, контекст приложения Spring ничего не знает о нем и не может внедрить в него какие-либо компоненты @Autowired.

Чтобы это исправить, вам нужно установить controllerFactory для FXMLLoader , инструктируя его «создать» (действительно извлечь) экземпляр контроллера из Spring ApplicationContext. Фабрика контроллеров - это просто функция (@FunctionalInterface), которая принимает Class<?> и производит объект. Поскольку это в точности сигнатура одного из ApplicationContext.getBean() методов, код для этого выглядит следующим образом:

FXMLLoader loader = new FXMLLoader(getClass().getResource("/primal.fxml"));
loader.setControllerFactory(context::getBean);
Parent root = loader.load();

, где context - это пружина ApplicationContext (вам может понадобиться перепрыгнуть через некоторые обручи чтобы получить ссылку на это в методе, где вы загружаете свой файл F XML, обычно просто создаете для него поле и аннотируете поле Autowired работает).

Я бы сделал пару твиков для конфигурация класса контроллера тоже. По умолчанию Spring управляет компонентами как одноэлементной областью. Это определенно не то, что вам нужно: если бы вы загружали один и тот же F XML во второй раз, вам понадобился бы другой экземпляр контроллера (так как у вас был бы другой набор элементов управления пользовательского интерфейса). Поэтому вам определенно необходимо использовать контроллер в качестве прототипа.

Во-вторых, стереотип Spring @Controller предназначен для контроллеров в смысле Spring MVC; поэтому я не думаю, что это действительно то, что вы хотите здесь (хотя я не думаю, что это приносит вред). Я бы прокомментировал класс контроллера как

@Component
@Scope(BeanDefinition.PROTOTYPE_SCOPE)
public class Controller { /* ... */ }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...