使用 Java 下载 JDK





5.00/5 (4投票s)
介绍如何使用 JavaFX 的 WebView 从 Oracle.com 下载 Java。
引言
我想分享我从 Oracle.com 下载 Java JDK 的经验——我需要自动化用户在下载 Java 时需要执行的网站导航操作——有几种场景,其中一些需要登录 Oracle 账户。我的工作成果 已在 GitHub 上共享。安装 Java 应用程序的整个过程对开发者和最终用户来说一直很麻烦,本文的目的是展示一种更轻松的自动安装 JDK 的方法。顺便说一下,它被用在作者的 远程自动化工具 Bear 中。
背景
从 Oracle.com 下载最新稳定版 JDK 时,用户只需点击两下鼠标即可。
在本文中,我将只重点介绍解决此 Java 任务中最有趣的部分,如果您有任何疑问,请在下面的评论区留言,我会回答并更新文章。
结果是几行代码即可下载 JDK。
import static bear.fx.DownloadFxApp.*;
public static void main(String[] args){
version = "7u51";
tempDestDir = new File(".");
miniMode = false; // mini-mode hides the browser window
launchUI();
DownloadFxApp downloadFxApp = getInstance().awaitDownload(1, TimeUnit.HOURS);
DownloadFxApp.DownloadResult r = downloadFxApp.downloadResult.get();
downloadFxApp.stop();
System.out.println("Downloaded to: " + r.file.getPath());
}
网上没有公开可用的 JDK 存档,因为 Oracle 的许可证禁止这样做,尽管 Java 被认为是一个开放平台。这很奇怪,Oracle。
本文包含对 Oracle 的一些批评,这些批评旨在具有建设性。我作为一名 Java 开发者已有 8 年多的经验,我真的希望在指出 Oracle 的不足之处时避免任何对抗。
选择哪种方法或技术?
我找到了两个现有的开源解决方案,它们可以从 Oracle 网站下载 JDK。
- (Ruby) capistrano-jdk-installer
- (Java) Jenkins 源中的
JDKInstaller
第一种方法是用 Ruby,我尝试使用 HttpClient
和 Mechanize
来模仿。这种方法依赖于 Jenkins 网站上预先填充的 JDK URL 列表。您还需要登录 Oracle 网站才能下载旧版本。在实现过程中,我遇到了 SSL 证书支持和连续请求未登录的问题。根据我的经验,这类解决方案通常不起作用或需要过多的维护,因此我决定继续采用不太复杂的方案。
也就是浏览器模拟。当然!您始终可以像用户浏览网站、输入数据、填写表单、阅读等等一样下载文件。Oracle 在填写表单、90 年代的 UI 和下载前一个版本的 JDK 时非常混乱,-1 分。Oracle 再次因为阻止我的账户而扣分。:-) 别担心,这很可能不会发生在你身上——我只是进行了太多的调试会话。及时更新很好,但在大多数情况下,您的代码上线时并非如此。
Selenium WebDriver 很好用而且稳定,但它带来了很多依赖项,并且对于不使用它的项目来说可能不是一个选项。所以我的选择是 JavaFX 2 的 WebView
。
使用 WebView
WebView 是 JavaFX 中的一个简单组件,它基于 WebKit 浏览器,可用于浏览网页。在 Java 8 中,WebView 有了一个新的快速 JavaScript 引擎 Nashorn,它比 Java 7 中的 Rhino 快 20 倍,因此整个浏览体验非常流畅,与 Chrome 或 Firefox 等常规浏览器几乎没有区别。(好吧,Nashorn 加 5 分,WebView 开箱即用缺少 JSR-221 集成减 1 分)
我对 WebView 与 JavaFX 的看法?如今,桌面应用程序似乎有些过时,所以我对 Oracle 更新 Swing 到 JavaFX 2 感到困惑。为什么他们不能使用 WebView 来渲染现代 Web UI 技术,如 Backbone 或 AngularJS,并将它们绑定到应用程序?例如,Bear 使用 JavaFX 来渲染本地网页 UI,因此它使用 AngularJS 和 Twitter Bootstrap 而不是 JavaFX 2 组件。当这种情况成为主流,并且桌面/Web 应用程序对开发者来说是透明的时(也就是说,您为一个服务器编写 UI 应用程序,并且这段代码也可以在本地运行),为什么还有人想聘请 JavaFX 开发者来创建一些疯狂的 FXML 表单,而他们本可以基于更高效的 JavaScript UI 堆栈构建他们的应用程序?这会便宜多少倍?2 倍、5 倍、10 倍?我真的希望 Oracle 能考虑关注快速发展的现代 Web。
算法
下面是遍历下载页面和填写注册表的流程图。入口点是页面 最新 JDK 版本页面 或 Oracle Java 存档。这个图的一些元素将在下面更详细地描述。
SimpleBrowser
我开发了一个 SimpleBrowser
组件,它是 WebView
的一个包装器。目前,SimpleBrowser
为 WebView
添加了以下功能:
useFirebug
- 嵌入 Firebug Lite 面板。useJQuery
- 在加载页面时嵌入 jQuery。load(url, callback)
- 打开页面并在准备好后运行回调。getHTML()
- 获取当前页面的 HTML。createWebView
-WebView
是一个可选的可视化组件,这启用了仅WebEngine
模式。然而,在 Java 8 中,仍然需要启动 Stage,并且 JavaFX 跟踪器中有关于使 JavaFX 可用于无头环境的功能请求。waitFor()
- 等待 jQuery 条件变为true
,例如,元素出现。- 通过 JavaScript 的
alert()
进行日志记录。
从网页获取链接
当您拥有 jQuery 时,这很简单——只需添加一个使用 jQuery 选择器访问 DOM 树节点并返回找到的链接的函数。
browser.getEngine().executeScript(
scriptText() + "\n " + // adds function definitions
"downloadIfFound('" + version + "', true, 'linux');"
);
scriptText()
的来源 在 GitHub 上。请注意,此脚本相当长,为每个 Java 到 JS 调用编译它效率不高,但对于要求不高的任务或演示目的来说还可以。
点击链接
我从这个 Stackoverflow.com 的回答中复制代码:如何用 JavaScript 模拟点击? clickIt
将会点击 jQuery 选择器的结果,这将触发 WebEngine
的导航事件。
var clickIt = function($el){
var el = $el[0];
var etype = 'click';
clickDom(el, etype);
}
var clickDom = function(el, etype){
if (el.fireEvent) {
el.fireEvent('on' + etype);
} else {
var evObj = document.createEvent('Events');
evObj.initEvent(etype, true, false);
el.dispatchEvent(evObj);
}
}
嵌入 jQuery
要将 jQuery(或任何其他库)嵌入现有页面,只需向 <head ...>
节点添加一个 <script ...>
条目,这对于 JavaScript 来说很容易。
问题是这个脚本是异步加载的,所以我需要提供一个 Java 回调,当脚本加载时,该回调会从 JavaScript 中调用。可能还会出现过于频繁的顺序加载问题,因此有一个队列,它会存储回调及其 eventId
,并在脚本在给定初始 eventId
的网页中加载时触发它们。
页面加载图的简短版本如下所示。
注意:这一切对 SimpleBrowser
的用户来说都是隐藏的。
void SimpleBrowser::load(url, Runnable onLoad) {
int eventId = random.nextInt();
webEngine.load(url);
whenPageLoaded({
jQueryLoads.put(eventId, load);
if(useJQuery) embedJQuery(eventId);
} as Runnable);
}
void SimpleBrowser::embedJQuery(int eventId){
webEngine.executeScript("insertNode($eventId);");
};
//JavaScript:
var insertNode = function(eventId){
var scriptNode = ...; // inserts a script node
scriptNode.onload = function(){
// run a callback on a Java side
window.simpleBrowser.jQueryReady(eventId);
}
}
跟踪重定向
WebView
会为您处理此事,但是,我不知道这是否是 bug,但我无法以常规方式等待重定向后的页面加载。所以我添加了 SimpleBrowser::waitFor
,它在一个单独的线程中等待元素出现,该元素基于 jQuery 条件,即 $('#id').length > 0
。
日志记录
我发现了两种记录 JavaScript 的方法,目前两者都更像是个笑话——WebView
减 1 分(其他方面很棒且独特的技术加 10 分)。第一种是使用第三方 FirebugLite 的控制台。尽管这个扩展很棒,但我完全不喜欢在 WebView
中使用它。最新的 1.4 版本对我来说根本不好用,有时你会花几个小时几乎哭着说“Firebug 面板,快回来!”,Oracle 情感虐待扣 1 分。哦,开玩笑的。
另一个方法是为 WebEngine
定义一个 alert 事件处理程序,并使用 alert()
来记录 JS。这效果很好,但伙计们,说真的?
下载文件
当 WebEngine
发生位置属性更改事件时,下载开始,该事件提供下载位置。我使用 Apache 的 HttpClient
库,我还启动了一个单独的线程来下载 JDK。一个进度条显示下载进度:

关注点
在本文中,我展示了如何使用 WebView 加载页面、将外部 JS 库嵌入页面、点击链接以及等待内容出现。
此过程中最混乱的事情之一是调试 WebView
的状态,理解 WebEngine
加载页面的状态,以及在出现堆转储时查找问题,即在您执行非法操作时——比如用错误的参数从 JavaScript 调用 Java 方法,或者出现奇怪的 bug,比如调用已垃圾回收的对象的方法(这是我猜测的类型,我没弄明白的 bug)。
这部分代码可以用作一个库的基础,该库可以构建在 WebView
之上。我很高兴最终能够简化像使用 Java 下载 Java 这样具有挑战性的任务。