常见问题解答 - 调试和编译
- 为什么会出现所有这些缓存/非缓存内容和奇怪的文件名?
- 如何更改缓存/非缓存 HTML 文件的位置?
- 为什么我的 GWT 生成的 JavaScript 是乱码?
- 我如何加快 GWT 编译器速度?
- war 目录是否被 GWT 编译器用作编译过程的输入和输出目录?
- 新的 WAR 输出目录布局是否要求我在服务器端使用 Java?
- 可以将哪些选项传递给编译器进程?
注意:“托管模式”和“Web 模式”已分别重命名为*开发模式*和*生产模式*。
为什么会出现所有这些缓存/非缓存内容和奇怪的文件名?
在引导过程中,GWT 应用程序会经过一系列有时会以难以理解的名称命名的文件。这些由 GWT 编译器生成的文件通常对于 GWT 新手来说很奇怪。但是,为了有效地部署 GWT 应用程序,必须理解这些文件,以便能够将它们放置在 Web 服务器上的适当位置。
GWT 编译器生成的重要文件是
_<模块名称>_.nocache.js
_<字母数字>_.gwt.rpc
上面列出的每个项目将在下面进行描述。但是,首先,了解延迟绑定概念很重要,因为该概念是注入到<模块名称>
nocache.js` 文件中的引导过程的核心,因此您可能想阅读一些关于 延迟绑定 的内容,然后再继续。
在解释每个文件的作用之前,总结 GWT 应用程序的整体引导过程也很有用
- 浏览器加载并处理主机 HTML 页面。
- 当浏览器遇到页面的
<script src="<模块名称>.nocache.js">
标签时,它会立即下载并执行文件中的 JavaScript 代码。 .nocache.js
文件包含 JavaScript 代码,该代码解析延迟绑定配置(例如浏览器检测),然后使用 GWT 编译器生成的查找表来定位要使用的.cache.html
文件之一。- 然后,
.nocache.js
中的 JavaScript 代码创建一个隐藏的<iframe>
,将其插入到主机页面的 DOM 中,并将.cache.html
文件加载到该 iframe 中。 .cache.html
文件包含 GWT 应用程序的实际程序逻辑。
这就是该过程的概况。有关完整 GWT 应用程序的引导过程示例,请查看 开发者指南示例。以下部分详细描述了每个 GWT 应用程序文件。
.cache.html 文件
“缓存”文件包含应用程序的逻辑。如果您查看.cache.html
文件,您会发现它是在薄 HTML 包装器中包装的 JavaScript 代码。您可能想知道为什么 GWT 编译器不简单地将其作为 JavaScript .js 文件输出。原因是,某些浏览器在某些情况下无法正确处理纯 JavaScript 文件的压缩。这实际上意味着使用这种浏览器的用户将下载未压缩的 .js 文件。由于 GWT 的宗旨是实现无妥协、高性能的 AJAX 代码,因此 GWT 编译器将 JavaScript 包装在 HTML 文件中,以绕过此浏览器缺陷。
它们根据其内容的 MD5 校验和命名。这保证了 GWT 编译器的确定性行为:如果您在不更改代码的情况下重新编译应用程序,则输出的内容将不会更改,因此 MD5 校验和将保持不变。相反,如果您确实更改了源代码,则输出的 JavaScript 代码也会相应更改,因此 MD5 校验和以及文件名也会更改。
由于这种唯一性保证,浏览器安全地(实际上也更可取地)缓存这些文件,这反映在它们的.cache.html
文件扩展名中。
.nocache.js 文件
“nocache”文件是 延迟绑定 发生的地方。在应用程序可以运行之前,必须解析任何动态绑定代码。这可能包括类的特定于浏览器的版本、适合用户所选语言的特定字符串常量集等等。在 Java 中,这将通过简单地加载实现特定接口的适当服务提供者类来处理。但是,为了最大限度地提高性能并最小化下载大小,GWT 在“nocache”文件中预先执行了此选择。
该文件之所以命名为“.nocache.html”,是为了表明该文件永远不应该被缓存。也就是说,它必须在浏览器每次启动 GWT 应用程序时下载并执行。它必须每次重新下载的原因是 GWT 编译器每次都会重新生成它,但在同一个文件名下。如果允许浏览器缓存该文件,它们可能不会下载该文件的最新版本,而 GWT 应用程序是在服务器上重新编译和重新部署的。为了帮助防止缓存,gwt.js
中的代码实际上在文件名末尾附加了一个 HTTP GET 参数,其中包含一个唯一的时间戳。浏览器将此解释为一个动态 HTTP 请求,因此不应从缓存中加载该文件。
.gwt.rpc 文件
在 GWT 的早期版本中,如果您的应用程序使用 GWT RPC,您希望通过网络进行序列化的类型必须实现IsSerializable
接口。从 GWT 1.4 开始,实现java.io.Serializable
接口的类型现在也符合通过 RPC 进行序列化的条件,但有一些条件。
其中一个条件是,您希望通过网络进行序列化的类型必须包含在 GWT 编译器生成的 .gwt.rpc 文件中。.gwt.rpc 文件充当序列化策略,指示哪些实现java.io.Serializable
的类型可以跨网络进行序列化。有关此内容以及在 GWT RPC 中使用Serializable
类型的其他条件的更多详细信息,请查看此常见问题解答。
总结
这就是这些略显奇怪的 GWT 文件名背后的故事。<module>.nocache.js
执行延迟绑定解析,并根据执行上下文选择一个缓存文件
如何更改缓存/非缓存 HTML 文件的位置?
GWT 应用程序的引导过程涉及一个<
模块名称>
.nocache.js 文件,以及一些 .cache.html 文件。在开发模式环境中,这些应用程序会根据 -out 参数中指定的输出目录自动放置在一个 war 样式目录中。例如,如果在您的 build.xml 开发目标中指定的 -out 参数读取为myapp
,则开发模式进程将在/test/war/MyApp.html
中读取您的主机 HTML 页面。
- 应用程序的主机 HTML 文件(即包含引用
<
module>
.nocache.js 文件的<
script>
标签的文件)可以在网站的任何位置。对这个 URL 没有限制。 <module>.nocache.js
文件相对于主机 HTML 页面的位置并不重要;.nocache.js
文件试图相对于加载它的 URL(而不是相对于主机 HTML 页面的 URL)来定位文件。.cache.html
和 GWT 编译器生成的其它文件必须与 .nocache.js 文件位于同一个路径中。但是,当部署 GWT 应用程序时,它必须与网站的其余部分共存。为了将 GWT 应用程序正确地集成到网站的文件结构中,网站管理员必须了解 GWT 应用程序的引导方式。否则,这些文件可能不在 GWT 基础结构期望找到它们的位置,应用程序将无法加载。以下详细信息描述了这些主要组件的每一个,以及它们应该放置在何处才能正确加载 GWT 应用程序
使用 GWT 引导模型的示例下载顺序
在 GWT 1.4 的引导模型中,GWT 期望在其所有文件与 <module>.nocache.js
文件位于同一路径下。例如,假设您的 GWT 模块名称为 com.company.app .MyApp
,并且主机 HTML 文件位于 http://host.domain.tld/myApp/index.html
。进一步假设 <module>.nocache.js
和 GWT 编译器生成的其它文件与主机 HTML 文件位于同一路径下。在此配置下,将发生以下请求序列
http://host.domain.tld/myApp/index.html
http://host.domain.tld/myApp/MyApp.nocache.js
http://host.domain.tld/myApp/CAFEBABE12345678DEADBEEF87654321.cache.html
有关完整 GWT 应用程序的引导序列示例,请查看 开发者指南文档.
重新定位文件
上面的配置可能不适用于您的情况。例如,出于组织目的,您可能希望将主机 HTML 页面放置在根路径下,但将 GWT 相关文件放置在另一个位置,例如 /gwt-files
。
要将 GWT 应用程序文件重新定位到 /gwt-files
路径,您只需更新引用 <
script>
.nocache.js 文件的 <
script>
标签以包含该文件的相对(或绝对)路径。GWT 编译器生成的文件(包括 .cache.html 文件)也必须与 <
module>
.nocache.js 文件位于同一路径下。
在这种情况下,如果您的 GWT 模块是 com.company.app.MyApp
,则主机页面(index.html)中的 <
script>
标签将是
<script language="JavaScript" src="/gwt-files/MyApp.nocache.js"></script>
上面的示例将导致以下文件下载序列
http://host.domain.tld/index.html
http://host.domain.tld/gwt-files/MyApp.nocache.js
http://host.domain.tld/gwt-files/CAFEBABE12345678DEADBEEF87654321.cache.html
其他注意事项
通常,应用程序需要额外的内容,例如图像和 CSS 文件,当然还有动态资源,例如 RPC 或 JSON URL。上面描述的技术不仅会更改用于获取 GWT 应用程序文件的路径,还会更改 GWT.getModuleBaseURL() 和 GWT.getModuleBaseForStaticFiles() 方法返回的值。如果您的应用程序使用该方法来构建指向附加资源的 URL,请确保这些资源位于相应的目录中。例如,如果您将 GWT 文件重新映射到 ‘/gwt-files/’,但您的主机页面是 /index.html
,那么代码 GWT.getModuleBaseForStaticFiles() + "myImage.png"
将导致 URL /gwt-files/myImage.png
。
如果您的代码使用 GWT.getModuleBaseURL() 或 GWT.getModuleBaseForStaticFiles() 引用资源,请务必注意。这对于使用 Java Servlet 的人来说是一个特别常见的问题,因为必须在模块 XML 文件 (.gwt.xml) 中适当地设置 Servlet 映射。
为什么我的 GWT 生成的 JavaScript 是乱码?
默认情况下,GWT 会对生成的 JavaScript 进行混淆处理。这部分是为了保护您开发的应用程序的知识产权,但也因为混淆处理会减小生成的 JavaScript 文件的大小,从而使其下载和解析速度更快。
如果您不想让 GWT 对其输出进行混淆处理,则可以使用 GWT 编译器的 -style
标志。此标志有三个可能的值
OBF
(用于混淆),默认值PRETTY
,它使输出对人类可读DETAILED
,它在PRETTY
的基础上提供了更多细节(例如非常详细的变量名)
如果您好奇 GWT 生成的 JavaScript 在做什么,那么可以使用 -style PRETTY
。在您调试 GWT 输出的罕见情况下,-style DETAILED
可能会有所帮助。
如果您的代码使用 GWT.getModuleBaseURL() 或 GWT.getModuleBaseForStaticFiles() 引用资源,请务必注意。这对于使用 Java Servlet 的人来说是一个特别常见的问题,因为必须在模块 XML 文件 (.gwt.xml) 中适当地设置 Servlet 映射。
我可以加快 GWT 编译器速度吗?
如果您正在编译大型应用程序,您可能会发现编译到生产模式需要很长时间。一个问题是,编译器实际上会根据区域设置和浏览器的客户端属性构建您的应用程序的多个版本。对于部署来说,这是至关重要的,但在日常开发中,您可能只使用一个浏览器和区域设置。如果是这种情况,那么您可以走捷径,只在开发周期中编译单个排列。
创建此快捷方式设置需要创建一个新模块并操作客户端属性。您需要做的是创建一个从现有模块继承的模块,该模块为要固定的客户端属性指定确切的值。让我们以 Hello
为例……
- 创建一个名为
HelloFirefox
的模块,它继承了 Hello - 在
HelloFirefox
模块中使用<set-property>
,它显式地为user.agent
客户端属性设置一个值。(请参阅UserAgent.gwt.xml
中的<define-property>
,了解可能的值。)- 在
HelloFirefox
中使用rename-to
,使用与Hello
模块中相同的值,以便生成的.nocache.js
文件具有相同的名称(请参阅 重命名模块。)
- 在
- 编译
HelloFirefox
模块而不是Hello
模块。 - 查看
war/<modulename>/
目录:应该只编译了一个排列。
您可以对 locale
或任何其他客户端属性执行相同的操作。生成所有这些排列的子系统是完全可扩展的,因此此技术是一个通用的技术。因此,您可以并行保留两个模块 XML 文件,一个用于在所有浏览器上进行测试的稳定开发,另一个用于大多数排列被抑制的草稿代码的工作副本。
希望此示例也开始表明模块的概念并不像乍看起来那样微不足道。模块在让您确定您要构建的确切内容方面发挥着重要作用。您可以根据相同代码库的不同配置创建任意数量的模块。您可以想象像“MySuperBigModuleWithDebuggingAndLoggingTurnedOn.gwt.xml”这样的模块变体。
GWT 编译器是否将 war 目录用作编译过程的输入和输出目录?
从 GWT 1.6 开始,编译器会根据 Web 应用程序存档 (WAR) 标准布局生成输出目录结构。此功能使您能够更轻松地在任何 servlet 容器(如 Jetty、Tomcat 或 Google App Engine Java 运行时)上部署您的应用程序。有关 WAR 标准布局的更多详细信息,请参阅 Servlet 2.5 API 规范的第 9 章。
按照惯例,开发人员可以将其静态资源(包括其主机 HTML 页面)放置在 war/<appname>
目录中,其中 <appname>
是您在模块 XML 文件中通过 rename-to 属性指定的名称(即 <module rename-to="myapp">
)。问题是 GWT 编译器是否要求这些资源存在于将用于输出的相同 war 目录结构中,或者是否可以指定单独的输入源来读取所需的资源。答案是:确实可以使用完全独立的输入源,并且仅将 WAR 目录专用于编译器的生成输出。由于 GWT 构建过程现在使用 Apache Ant 构建工具,您只需在项目构建脚本 (build.xml) 中添加一个 copy 目标,该目标将复制并将所需的静态资源放置在最终的 war 输出目录中。
例如,以下是 GWT webAppCreator 脚本生成的 build.xml 文件
<target name="gwtc" depends="javac" description="GWT compile to JavaScript">
<java failonerror="true" fork="true" classname="com.google.gwt.dev.Compiler">
<classpath>
<pathelement location="src"/>
<path refid="project.class.path"/>
</classpath>
<!-- add jvmarg -Xss16M or similar if you see a StackOverflowError -->
<jvmarg value="-Xmx256M"/>
<!-- Additional arguments like -style PRETTY or -logLevel DEBUG -->
<arg value="com.google.gwt.sample.hello.Hello"/>
</java>
</target>
假设您要复制的静态资源位于 src/com/myapp/resources 文件夹中。您可以定义一个 copy 目标,该目标将这些资源复制到 war 输出目录,如下所示
<target name="copyresources" description="Copy static resources to war output directory">
<copy todir="war/myapp">
<fileset dir="src/com/myapp/resources">
<include name="**/*"/>
</fileset>
</copy>
</target>
最后,更新 gwtc 目标以依赖于新创建的 copyresources 目标
<target name="gwtc" depends="javac,copyresources" description="GWT compile to JavaScript">
此时,您应该可以开始运行了。当然,如果您打算在服务器端使用 Java。如果您在不同的服务器端技术上部署,新的 WAR 风格目录可能对您没有太大意义,但您仍然可以分别处理输入和输出目录,以及从 WAR 输出目录中提取您的应用程序使用的任何服务器端所需的内容。
新的 WAR 输出目录布局是否要求我在服务器上使用 Java?
与 GWT 的先前版本一样,您始终可以使用您选择的服务器端来部署您的 GWT 应用程序。GWT 编译器用于生成输出的新 WAR 输出目录结构是在 GWT 1.6 中引入的,以便使用 Java 的开发人员更容易地直接从生成的输出中部署其应用程序,例如 Google Eclipse 插件。
要将您的 GWT 应用程序部署到自定义 Web 服务器,您只需提取 GWT 编译期间生成的重要文件并将其放置在您的 Web 服务器上。这些文件是 <appname>nocache.js
和其他 <md5>cache.html
文件。有关生成文件的更多信息,请查看此 常见问题解答,其中更详细地描述了它们。生成的 JS 和 HTML 文件在编译期间放置在 war/<appname>
文件夹中。您只需将这些文件复制到 Web 服务器的活动目录中,以及您的应用程序所需的任何其他静态资源,例如图像或样式表,就可以了。
可以将哪些选项传递给编译器进程?
请参阅 GWT 编译器选项.