Spring-使用 Swing 启动 UI

Spring-Boot with Swing UI

我想在 Spring-Boot 应用程序中为我的 Swing UI 组件使用依赖注入,但很难弄清楚如何正确执行 UI 行为事件调度线程。

我首先想到的是这样的:

应用程序

@SpringBootApplication
public class App {

    private static AppView view;

    @Bean
    public AppView appView() {
        return view;
    }

    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeLater(() -> view = new AppView());

        SpringApplication app = new SpringApplication(App.class);
        app.run(args);
    }

}

AppView

public class AppView extends JFrame {

    ...

    @Inject
    private DependencyWithTimeConsumingOperations backendController;

    @Inject
    private JPanel someChildComponent;

    @PostConstruct
    public void init() {
        constructView(); // inits frame properties and child components
        showView();
    }

    private void showView() {
        SwingUtilities.invokeLater(() -> {
            pack();
            setVisible(true);
        });
    }

    ...
}

当某些 UI 事件发生时,会调用后端依赖项。我观察到的是,后端调用是在 EDT 而不是主应用程序线程上执行的,我认为这很糟糕。据我所知,没有太多的 Swing 经验是,只有 UI 更新应该在 EDT 上执行。

是否有更好的方法来连接我的依赖项,以便所有内容都在其正确的线程中执行?到目前为止我能找到的东西似乎有点过时或者我显然不明白答案:-)

不知道过了这么久它是否仍然与你相关:),但因为它可能对其他人有帮助,所以我会尽力回答。

Spring 只注入对象,不管理线程。如果您手动实例化并设置 backendController,行为将是相同的,这意味着 EDT(或调用该操作的任何线程)将是在控制器上执行代码的那个。

如果您明确想要 运行 在不同的线程中,我们需要了解更多关于控制器中方法的信息。它们是您想要调用而不是等待回复(即发即忘)的方法吗?或者您可能需要回复,但可以同时 运行 多个回复吗?在这些情况下,您可以利用 Executors class 并执行以下操作:

    Executors.newSingleThreadExecutor().execute(() -> backendController.timeConsumingOperation1()); // Fire and forget. The operation timeConsumingOperation1 will be executed by a separate thread and the EDT will continue to the next line (won't freeze your GUI)

如果您需要结果,您可以将其提交到池中并轮询结果(可能使用屏幕上的 "refresh" 按钮)。请记住,一旦您调用 "get()",当前线程将等待池线程完成,然后再继续下一行。

    Future result = Executors.newSingleThreadExecutor().execute(() -> backendController.timeConsumingOperation2);
    result.isDone(); // You can add a "refresh" button or a scheduled task to check the state...
    doSomething(result.get()); // This will hold the current thread until there is a response from the thread running the timeConsumingOperation

或者您可能确实想冻结 GUI,直到您收到来自控制器中调用的所有方法的响应,但可以安全地并行调用它们:

    ExecutorService executorService = Executors.newFixedThreadPool(2);
    List<Future<Object>> results = executorService.invokeAll(
            Arrays.asList(() -> backendController.timeConsumingOp3(), () -> backendController.timeConsumingOp4));
    results.forEach(e -> doSomething(e.get())); // The tasks will be executed in parallel and "doSomething()"  will be called as soon as the result for the given index is available
    executorService.shutdown(); // Always shutdown

当然这只是一个示例,但在大型 Swing 应用程序中,创建线程池(由控制器共享)是一种很好的做法,我们可以向其提交长 运行ning 任务。您可以根据核心数 (Runtime.getRuntime().availableProcessors()) 配置池大小,以最好地利用机器上可用的资源(提交的任务将不受限制地排队,但只有 X 线程会并行执行任务,其中 X 是池大小)。

只需使用代码

SpringApplicationBuilder(Main.class).headless(false).运行(args);