65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Java 下载 JDK

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2014年2月4日

公共领域

7分钟阅读

viewsIcon

15687

介绍如何使用 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,我尝试使用 HttpClientMechanize 来模仿。这种方法依赖于 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 的一个包装器。目前,SimpleBrowserWebView 添加了以下功能:

  • 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。一个进度条显示下载进度: 

Download Starts

关注点

在本文中,我展示了如何使用 WebView 加载页面、将外部 JS 库嵌入页面、点击链接以及等待内容出现。

此过程中最混乱的事情之一是调试 WebView 的状态,理解 WebEngine 加载页面的状态,以及在出现堆转储时查找问题,即在您执行非法操作时——比如用错误的参数从 JavaScript 调用 Java 方法,或者出现奇怪的 bug,比如调用已垃圾回收的对象的方法(这是我猜测的类型,我没弄明白的 bug)。

这部分代码可以用作一个库的基础,该库可以构建在 WebView 之上。我很高兴最终能够简化像使用 Java 下载 Java 这样具有挑战性的任务。

© . All rights reserved.