本文目录导读:

你提到的 JEditorPane 和 HTMLEditorKit 在解析 HTML 时出现的“连接挂起”或“Pending”问题,通常是由于默认的 HTMLEditorKit 在尝试加载外部资源(如图片、CSS、脚本或 iframe 中的内容)时,网络连接被阻塞或超时导致的。
这种情况在 Swing 的 JEditorPane 中非常常见,尤其是在加载包含复杂 HTML(如现代网页、或引用外部网络资源的 HTML)时。
以下是导致该问题的核心原因及解决方案:
核心原因
- 默认的
HTMLEditorKit是阻塞的:Swing 的 HTML 渲染引擎(javax.swing.text.html.HTMLEditorKit)是基于 Java 1.x 时代的旧版解析器,当它遇到<img src="http://...">或<link href="...">时,默认会尝试同步(阻塞)地下载该资源,如果该网络地址不可达、DNS 解析缓慢、或者需要代理且未配置,线程就会永远挂起在URLConnection.connect()上。 - 不支持现代 HTML/CSS/JavaScript:
JEditorPane不是真正的浏览器,如果你加载的 HTML 包含了复杂的 CSS 布局或 JavaScript(即使被忽略),解析器也可能在解析 DOM 树时进入死循环或非常缓慢的状态。 - Swing 事件调度线程(EDT)阻塞:如果你在 EDT 上直接调用
setPage()或setText()加载一个需要网络请求的 HTML,EDT 会被阻塞,导致整个 UI 冻结和挂起。
解决方案(按推荐顺序)
禁用外部资源加载(最有效、最安全)
如果你不需要加载网络图片或外部样式表,直接禁用 HTMLEditorKit 的自动资源加载,这可以立即解决 90% 的挂起问题。
import javax.swing.*;
import javax.swing.text.html.*;
import java.io.IOException;
public class SafeHtmlPane {
public static void main(String[] args) {
JFrame frame = new JFrame();
JEditorPane editorPane = new JEditorPane();
editorPane.setContentType("text/html");
// 使用自定义的 HTMLEditorKit 并重写 createInputStream 方法
editorPane.setEditorKit(new HTMLEditorKit() {
@Override
public Document createDefaultDocument() {
HTMLDocument doc = (HTMLDocument) super.createDefaultDocument();
// 关键:禁用在解析时自动加载外部图片
doc.setAsynchronousLoadPriority(-1);
// 或者设为更高的正数,但 -1 表示完全禁止异步加载
return doc;
}
});
// 加载本地或内联 HTML(不包含外部网络资源)
editorPane.setText("<html><body><h1>Hello</h1><img src='file:///C:/local/image.png'></body></html>"); // 仅限本地文件
// 或者加载时需要防止网络请求:
// editorPane.setPage(new URL("http://example.com")); // 建议放到后台线程
frame.add(new JScrollPane(editorPane));
frame.setSize(400, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
重点说明:
doc.setAsynchronousLoadPriority(-1):这个设置是阻止挂起的关键,它告诉解析器不要尝试异步下载任何外部资源,默认情况下,这个值可能是0或100,导致解析器尝试建立连接。
使用 SwingWorker 在后台加载页面
永远不要在主线程或 EDT 上直接调用 setPage(url)。
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
// 在后台线程获取 HTML 内容(手动获取字符串)
StringBuilder content = new StringBuilder();
try (InputStream in = new URL("http://some-slow-site.com").openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
}
// 将结果传递给 EDT
SwingUtilities.invokeLater(() -> {
editorPane.setText(content.toString()); // 使用 setText,不会触发网络连接
// 注意:setText 仍然会尝试解析 HTML,但它不会去下载外部资源(如果内容已包含在内)
});
return null;
}
};
worker.execute();
优点:即使外部网络很慢,UI 也不会卡死,只有当你手动调用了 setPage() 时,才会触发内部网络请求,使用 setText() 则不会触发。
自定义 HTMLEditorKit 阻断网络连接
如果你无法控制 setAsynchronousLoadPriority 的行为,或者需要在解析时彻底屏蔽所有网络请求,可以重写 createInputStream 方法:
editorPane.setEditorKit(new HTMLEditorKit() {
@Override
public InputStream createInputStream(HTMLDocument doc, URL url) throws IOException {
// 完全禁止外部资源加载
// 抛出一个异常,或者返回一个空流
if (url.getProtocol().startsWith("http")) {
// 返回一个空的 InputStream 来替代挂起
return new java.io.ByteArrayInputStream(new byte[0]);
}
return super.createInputStream(doc, url);
}
});
考虑替代方案(如果必须渲染现代网页)
JEditorPane + HTMLEditorKit 对现代网页的支持非常有限,如果挂起问题持续出现且你无法控制 HTML 内容,建议使用 JavaFX WebView 或 JxBrowser(商业库):
- JavaFX WebView(推荐):
// 在 Swing 应用中嵌入 JavaFX JFXPanel jfxPanel = new JFXPanel(); Platform.runLater(() -> { WebView view = new WebView(); WebEngine engine = view.getEngine(); engine.load("http://example.com"); jfxPanel.setScene(new Scene(view)); });WebView 使用 Chromium 内核,没有 JSON 解析挂起问题,且支持现代 Web 标准。
| 原因 | 解决方案 |
|---|---|
| 解析器尝试下载外部资源(图片/CSS)导致网络阻塞 | 设置 document.setAsynchronousLoadPriority(-1) |
| 在 EDT 上直接调用 setPage(url) | 使用 SwingWorker 在后台线程中获取 HTML 内容,然后调用 setText() |
| HTML 包含复杂 JS 或格式 | 清理 HTML,或改用 JavaFX WebView |
| 代理或 DNS 问题 | 配置 Java 网络代理:System.setProperty("http.proxyHost", "...") |
第一步尝试:
先检查你的代码中是否有 editorPane.setPage(new URL(...)),如果有,换成 editorPane.setText(htmlString) 并启用 setAsynchronousLoadPriority(-1),绝大多数情况下,这足以解决“连接挂起”问题。