使用 JFXPanel 为本机应用程序构建 Facebook OAuth 登录流程

Build a Facebook OAuth login flow for a native application using JFXPanel

这可能是一个微不足道的问题,但我对 Swing 和 JavaFX 还很陌生。

我正在尝试为几乎完全批处理的应用程序构建 FB OAuth 登录表单。我知道我必须打开一个 webview 来获取 'code',然后与 'token' 进行交换。为此,我想使用 JFXPanel 作为使用 WebEngine 的微不足道的内部 Web 浏览器。

我的是一个简单的数据处理批处理作业,仅在主线程中完成繁重的工作。该代码必须嵌入到我无法控制主线程以外的线程的另一个代码中。为简单起见,假设我必须使用这个简单的主要方法调用 WebView:

 public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                ComponentBrowser browser = new ComponentBrowser();
                browser.setVisible(true);
                browser.loadURL("https://www.facebook.com/connect/login_success.html");
           }    


       });
    }

ComponentBrowser 已定义 here

我需要 WebView 同步,这是最佳流程:

我缺少的是如何将响应的内容(正文中只有 'Success' 一词)和重定向后的 url 返回给调用者(其中具有 'code' 查询字符串参数)。然后,调用者应该优雅地关闭 window.

问题是除了 sleeping/resuming 之外,我 没有 对主线程的控制权。 class with main() 代码也是如此:此代码由数据处理平台自动生成,我必须将我的代码嵌入其中。所以我无法修改 main() 本身之外的那部分代码(向 class 添加方法或侦听器是不可能的,但我 可以 添加语句到 main() 代码本身)

这就是为什么我在 JavaFX 和批处理代码之间的通信必须尽可能简单,甚至微不足道。

您可以使用 web 引擎的负载工作者的状态注册一个侦听器 属性:

engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
    if (newState == Worker.State.SUCCEEDED) {
        // document is successfully loaded
    }
});

从服务器收到响应时会收到通知。

要检索内容,您可以使用 WebEngine.getDocument(),它 return 是一个 org.w3c.dom.Document 对象。 dom API 有点笨拙(imo),但我认为在你的情况下你需要的只是

String content = engine.getDocument().getDocumentElement().getTextContent();

然后您可以检查它是否是 "Success"

线程部分变得棘手。如果我没理解错的话,你在这里基本上是运行一个几乎完全非GUI的应用程序,但你只需要一点UI在启动时验证登录,然后想要return到主线程并执行代码。

如果这完全是 Swing(用于登录验证),我将按如下方式处理:

从主线程调用 SwingUtilities.invokeAndWait(...) 并传入显示模态对话框的 RunnableinvokeAndWait 将阻塞主线程的执行,直到 Runnable(在 AWT 线程上执行)完成。通过使用模态对话框,您可以在对话框关闭之前阻止代码的执行,因此现在 Runnable 在对话框关闭之前不会完成,因此主线程将在对话框关闭之前阻塞。所以现在您需要做的就是安排在验证登录时关闭对话框。

通过在 Swing 对话框中放置 JFXPanel,这会变得稍微复杂一些。您现在需要使用 Platform.runLater(...) 将修改 JavaFX 组件的任何代码传递给 JavaFX 应用程序线程,并且如果您更改任何 Swing 组件(例如关闭对话框),则需要将其交还给 AWT 事件线程使用 SwingUtilities.runLater(...).

下面是一个快速演示。我只是在这里使用了一个按钮,按下时将 "Success" 加载到网络视图中。显然,您可以通过加载真实的登录屏幕来替换它。

import java.awt.Window;

import javafx.application.Platform;
import javafx.concurrent.Worker;
import javafx.embed.swing.JFXPanel;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;

import javax.swing.JDialog;
import javax.swing.SwingUtilities;

import org.w3c.dom.Document;


public class WaitingForLogin {

    public WaitingForLogin() throws Exception {
        System.out.println("Showing window...");

        // Run initAndShowUI() on AWT event thread. Block until that is complete:
        SwingUtilities.invokeAndWait(() -> initAndShowUI());

        System.out.println("Now running application");
        for (int i=1; i <=10; i++) {
            System.out.println("Counting: "+i);
            Thread.sleep(500);
        }
    }

    public void initAndShowUI() {
        JDialog dialog = new JDialog((Window)null);
        dialog.setModal(true);
        JFXPanel jfxPanel = new JFXPanel();
        Platform.runLater(() -> initJFX(jfxPanel, dialog));
        dialog.add(jfxPanel);
        dialog.setSize(400, 400);
        dialog.setLocationRelativeTo(null);

        // Since the dialog is modal, this will block execution (of the AWT event thread)
        // until the dialog is closed:
        dialog.setVisible(true);
    }

    private void initJFX(JFXPanel jfxPanel, Window dialog) {

        // Create a web view:
        WebView webView = new WebView();
        WebEngine engine = webView.getEngine();

        // Check for a new document being loaded. If the document just contains the 
        // text "Success", then close the dialog (unblocking all threads waiting for it...)
        engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
            if (newState == Worker.State.SUCCEEDED) {
                Document doc = engine.getDocument();
                if ("Success".equals(doc.getDocumentElement().getTextContent())) {
                    // Close dialog: this must be done on the AWT event thread
                    SwingUtilities.invokeLater(() -> dialog.dispose());
                }
            }
        });

        // Just for testing
        // simulate login with simple button:

        Button button = new Button("Login");
        button.setOnAction(event -> engine.loadContent("Success", "text/plain"));
        HBox controls = new HBox(button);
        controls.setAlignment(Pos.CENTER);
        controls.setPadding(new Insets(10));

        jfxPanel.setScene(new Scene(new BorderPane(webView, null, null, controls, null)));
    }

    public static void main(String[] args) throws Exception {
        new WaitingForLogin();

    }

}