java - 将 Spring 与 JavaFX Service 类集成会导致 Service 失败

标签 java spring multithreading spring-boot javafx

背景

我正在开发一个 JavaFX 应用程序,以根据我的偏好自动为我管理、编辑和移动文件。最终,应用程序将有相当多的任务需要一些繁重的处理。我想在屏幕底部显示一个任务栏,每个任务在运行时可以使用和显示,否则隐藏。

由于任务可能非常耗时,我还希望在进度条旁边显示一个取消任务图标,该图标也可以是通用的,并在单击时取消当前正在执行的任务。

我的思考过程

为了实现这一点,我尽我所能提出的解决方案是为我的应用程序将具有的每个任务创建一个扩展 Task 的唯一类,这个类将是一个普通的(非 Spring bean)类。

然后还为这些任务中的每一个创建一个扩展 Service 的唯一类,然后在服务类中,重写的 createTask() 方法将返回相应任务的新实例。然而,这个服务类将是一个 spring bean,因此可以 Autowiring 到任何需要使用该服务来运行任务的 Controller 类中。

问题

当我单击最终启动服务的按钮时,服务最终会失败并进入 FAILED 状态。当我查看调试器时,似乎由于文件浏览器 Controller 为空而导致空指针异常而发生异常。但是,文件浏览器 Controller 应该在被服务 createTask() 方法实例化时 Autowiring 到任务类中。

我不是 100% 确定这是问题的原因,因为调试服务已被证明有点挑战性,但似乎是因为这是在设置服务之前设置为 Task 值的异常到“失败”

代码

Controller 类(启动服务)

@Component
public class FileBrowserController implements Initializable {

    @Autowired
    private GetSelectedFilesCountService getSelectedFilesCountService;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // Code to build the file browser tree
    }


     /*******************************************************************************************************
     ***************************               BUTTON FUNCTIONALITY               ***************************                                       
     *        Gets the total count of leaf files from the users selection in the File Browser window        *
     ********************************************************************************************************/

    @FXML
    private void handleButtonAction(ActionEvent event) {

        // Initially set value to 0 and then bind the value with the service value property, 
        // which ultimately gets its value form the GetSelectedFilesCountTask 
        ObjectProperty<Integer> fileCount = new SimpleObjectProperty<Integer>(0);
        fileCount.bind(getSelectedFilesCountService.valueProperty());


        // On success print the value to the screen to check that this is working properly
        getSelectedFilesCountService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {

            @Override
            public void handle(WorkerStateEvent event) {
                System.out.println("The Service was successful, file count: " + fileCount.get());
            }
        });

        if(getSelectedFilesCountService.getState() == Service.State.READY) {
            getSelectedFilesCountService.start();
        }
        else if(getSelectedFilesCountService.getState() != Service.State.RUNNING 
                || getSelectedFilesCountService.getState() != Service.State.SCHEDULED) {
            getSelectedFilesCountService.reset();
            getSelectedFilesCountService.start();
        }
    }
}

服务类(创建并返回新的任务实例)
@Component
public class GetSelectedFilesCountService extends Service<Integer> {

    @Override
    protected Task<Integer> createTask() {
        return new GetSelectedFilesCountTask();
    }   
}

任务类,执行实际任务
@Component
public class GetSelectedFilesCountTask extends Task<Integer> {

    @Autowired
    private FileBrowserController fileBrowserController;

    private Integer leafCount;

    @Override
    protected Integer call() {
        leafCount = 0;
        getSelectedItemsCount(fileBrowserController.getFileBrowser().getSelectionModel().getSelectedItems());
        return leafCount;
    }

    /*****************************************************************
     *Recursively counts the leaves within a single or group of Files 
     *****************************************************************/
    private void getSelectedItemsCount(List<TreeItem<File>> files) {

        for(TreeItem<File> f : files) {
            if(f.isLeaf()) {
                leafCount++;
            }else {
                getSelectedItemsLeafCount(f.getChildren());
            }
        }
    }

    @Override
    protected void cancelled() {
        updateMessage("Operation Cancelled");
    }
}

Spring 配置文件
@Configuration
public class SpringAppConfig {

    @Bean
    public DataModel datamodel() {
        return new DataModel();
    }

    @Bean
    public GetSelectedFilesCountService getSelectedFilesCountService() {
        return new GetSelectedFilesCountService();
    }
}

我尝试过的解决方案

最初我还想将任务声明为原型(prototype) spring bean,然后将它们作为属性 Autowiring 到服务中,然后在 createTask() 方法中返回 Autowiring 的值。这个解决方案在我第一次单击按钮时会起作用,但第二次它会抛出异常,因为 Spring 只会在第一次传递时创建任务属性的新实例,然后一旦服务被重置并且任务被设置为null 它永远不会被再次创建。请参阅下面的片段

带有自动连线任务的服务
@Component
public class GetSelectedFilesCountService extends Service<Integer> {

    @Autowired
    private GetSelectedFilesCountTask getSelectedFilesCountTask;

    @Override
    protected Task<Integer> createTask() {
        return getSelectedFilesCountTask;
    }   
}

在此之后,我决定也许我仍然可以通过为任务创建一个 bean 工厂然后调用 bean 工厂的 createBean() 方法返回一个新的任务实例来完成上述操作,但最终这并没有奏效,我觉得好像我可以通过基本上使用服务作为工厂来完成同样的事情,只用一半的代码来创建新实例。

更新

我仍然想知道上面代码的原始问题是什么(返回 Task 子类的新实例),但现在我找到了解决方案....或通过创建和返回匿名 Task 实例的解决方法.我想这是可以接受的,因为我不必重复代码,因为该服务可以多次使用,但我仍然希望将代码放在它自己的类中。这很奇怪,因为这实际上与我在 Task 子类中的完全相同的代码一直失败,但我猜。请参阅下面的代码段,以防对其他人有所帮助。
@Component
public class GetSelectedFilesCountService extends Service<Integer> {

    @Autowired
    private FileBrowserController fileBrowserController;

    private Integer leafCount;

    @Override
    protected Task<Integer> createTask() {

        return new Task<Integer>() {

            @Override
            protected Integer call() {
                leafCount = 0;
                getSelectedItemsLeafCount(fileBrowserController.getFileBrowser().getSelectionModel().getSelectedItems());
                return leafCount;
            }

            /*****************************************************************
             *Recursively counts the leaves within a single or group of Files 
             *****************************************************************/
            private void getSelectedItemsLeafCount(List<TreeItem<File>> files) {

                for(TreeItem<File> f : files) {
                    if(f.isLeaf()) {
                        leafCount++;
                    }else {
                        getSelectedItemsLeafCount(f.getChildren());
                    }
                }
            }
        };
    }   
}

最佳答案

不是一个完整的解决方案,但一个体面的解决方法是在服务的 createTask 方法中使用并返回一个匿名任务。由于某种原因,当我尝试返回未实例化 Spring bean 的 Task 的已定义子类的新实例时出现问题,如果您知道这是为什么,请发表评论。

解决方案

@Component
public class GetSelectedFilesCountService extends Service<Integer> {

    @Autowired
    private FileBrowserController fileBrowserController;

    private Integer leafCount;

    @Override
    protected Task<Integer> createTask() {

        return new Task<Integer>() {

            @Override
            protected Integer call() {
                leafCount = 0;
                getSelectedItemsLeafCount(fileBrowserController.getFileBrowser().getSelectionModel().getSelectedItems());
                return leafCount;
            }

            /*****************************************************************
             *Recursively counts the leaves within a single or group of Files 
             *****************************************************************/
            private void getSelectedItemsLeafCount(List<TreeItem<File>> files) {

                for(TreeItem<File> f : files) {
                    if(f.isLeaf()) {
                        leafCount++;
                    }else {
                        getSelectedItemsLeafCount(f.getChildren());
                    }
                }
            }
        };
    }   
}

关于java - 将 Spring 与 JavaFX Service 类集成会导致 Service 失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50080957/

相关文章:

java - 不确定这是什么问题?

java - 无法从 Spring 应用程序连接到 mssql 服务器

java - 乐观锁 - Hibernate 的并发问题

java - 创建用于内部应用程序的 Web 服务并将其公开给其他人

multithreading - 检查序列是否排序的并行算法

java - 如何查看当前线程堆和变量?

java - 使用 JSoup 登录 Linkedin

java 字符串类

java - 如何使用java将视频下载到文件中?

spring - 方面未在 Spring 中执行