使用 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 同步,这是最佳流程:
- 主线程进入休眠状态
- 用户浏览 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(...)
并传入显示模态对话框的 Runnable
。 invokeAndWait
将阻塞主线程的执行,直到 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();
}
}
这可能是一个微不足道的问题,但我对 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 同步,这是最佳流程:
- 主线程进入休眠状态
- 用户浏览 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(...)
并传入显示模态对话框的 Runnable
。 invokeAndWait
将阻塞主线程的执行,直到 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();
}
}