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

时间:2018-04-28 20:50:33

标签: java spring multithreading spring-boot javafx

背景

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

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


我的思维过程

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

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


问题

当我点击最终启动服务的按钮时,服务最终失败并进入FAILED状态。当我查看调试器时,由于文件浏览器控制器为空,由于空指针异常,似乎发生了异常。但是,当文件浏览器控制器由服务createTask()方法实例化时,应该将其自动连接到任务类中。

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


代码

控制器类(启动服务)

@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();
        }
    }
}


服务类(创建并返回新的Task实例)

@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();
    }
}



我尝试过的解决方案

最初我还想将Tasks声明为原型spring bean,然后将它们作为属性自动装入服务,然后在createTask()方法中返回自动装配的值。这个解决方案可以在我第一次点击按钮时工作,但第二次它会抛出异常,因为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子类中保持失败的确切代码完全相同,但我想。如果它对其他人有帮助,请参阅下面的代码段。

@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());
                    }
                }
            }
        };
    }   
}

1 个答案:

答案 0 :(得分:0)

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

解决方案

@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());
                    }
                }
            }
        };
    }   
}