ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

JDK 7 demos 拆解指南:Swing/Java2D/JNI 与64位编译避坑

JDK 7 demos 拆解指南:Swing/Java2D/JNI 与64位编译避坑

简介:jdk-7u5-windows-x64-demos(64位)对应Java SE 7 Update 5的64位Windows开发环境,面向需要在旧版Java平台上编译、调试和运行程序的开发者,也适合维护历史项目或系统学习Java 7语法与API的人员。它可提供JDK与JRE相关工具链,帮助在64位系统中利用更大内存进行开发测试。包体为zip格式,压缩包约15.69MB;文件总数显示为0、类型明细暂无数据,内部构成需以实际解压为准。页面显示已有272人学习,说明该版本仍有一定参考需求。价值上,可结合Java 7引入的switch字符串、try-with-resources、NIO.2文件系统API、Fork/Join框架及钻石操作符等特性进行实践,理解旧版JDK的项目兼容、代码迁移与运行环境差异;对需要复现早期Java开发场景、查阅7u5版本行为的人来说,具备留存和对照意义。

1. 拿到一个 12 年前的 demos 包:先别急着双击,先看它验证什么

接手一套跑在 JDK 7 上的老系统时,真正卡人的往往不是业务代码,而是找不到可以对照的行为样本。界面刷新时序、打印预览、输入法候选框位置这类细节,文档里只有一句话,实际表现全靠猜。jdk-7u5-windows-x64-demos(64位)的价值就在这里:它是一份官方示例集合,把 Swing、Java2D、JNI、WebStart 这些能力按目录拆开,每个 demo 都能单独编译、单独运行、单独改一行看反应。

它适合三类人:要给老项目做兼容性验证的维护者、想弄清 Swing 事件分发和布局细节的开发者、需要一份离线可跑的 Java 桌面参考实现的同学。不适合想学现代 Java 的人,包里的写法停留在 Java 7 时代。读完能知道里面哪些 demo 今天还能跑、编译参数怎么给、64 位 native 库为什么会加载失败。

2. 目录地图:七个目录各自依赖什么、今天还能跑几个

解压之后第一件事不是找入口类,而是把目录和依赖列清楚。这个包的结构在多个 JDK 7 update 版本之间基本一致,demo根下通常能看到applets、jfc、jvmti、plugin、scripting、webstart、sample这几类,部分 7u 版本还会附带 JavaFX 示例目录。命名看起来相似,能不能跑差别极大,下面按「依赖什么、今天什么状态」逐个数。

2.1 解压与目录盘点

路径里带空格、带中文是 javac 和 native 编译最容易翻车的地方,先统一到一个短路径。用 PowerShell 解压到本地盘根目录下的英文路径:

# 解压到不含空格与中文的路径,后面所有命令都基于这个目录 $zip = "D:\downloads\jdk-7u5-windows-x64-demos.zip" $dst = "D:\jdksrc" Expand-Archive -Path $zip -DestinationPath $dst -Force # 只列一层目录,先看清有哪些顶层分类 Get-ChildItem "$dst\demo" -Directory | Select-Object Name

Expand-Archive对老 zip 里的长文件名和符号链接处理一般,遇到解压中断先确认目标盘剩余空间。如果压缩包来自浏览器下载,Windows 会给文件打上「来自其他计算机」标记,解压后某些 dll 可能被策略拦住,用Unblock-File处理单个文件即可。列目录这一步的意义在于确认包里到底有没有javafx-samples,不同小版本差异就体现在这一层。

2.2 各目录的运行条件对照

把依赖条件摊成一张表,比逐个试要快得多。下表是第一轮筛选的依据,判断标准是「是否需要浏览器插件」「是否需要 native 编译」「是否有 build.xml」:

目录典型内容运行依赖今天的状态
jfcSwingSet2、Font2DTest、Notepad、Java2D 示例纯 Java,部分带资源目录手工编译后基本可跑
applets小程序示例与 html 页面浏览器 Java 插件插件已被移除,只能读源码
plugin插件相关示例同上同上
webstartjnlp 描述文件与 jarjavaws 启动器JDK 7 自带,可跑但需签名
jvmtiJVMTI 代理、hprof 类示例C 编译器 + 头文件 + 位数匹配需自行编译 dll 才能跑
scripting脚本引擎调用示例JDK 7 自带 Rhino 脚本引擎可跑,注意引擎名
sample教学用的精简示例纯 Java可跑,适合改造成最小工程

这张表决定了后面的精力分配:jfc和sample是主力,jvmti是验证 64 位环境的试金石,applets和plugin只当源码读。

2.3 哪些 demo 值得先动手

在jfc下,SwingSet2 覆盖的组件面最广,菜单、表格、树、分隔条、皮肤切换都在里面,适合看事件监听怎么写、资源怎么加载。Font2DTest 是字体渲染和抗锯齿的对照工具,改一个渲染提示参数就能看到像素级差异。Notepad 体量小、依赖少,最适合作为「从源码到可运行 jar」的练手对象。Java2D 下的示例偏绘制管线,适合排查图形渲染问题。

sample目录里的例子通常几十行一个文件,适合拆开当模板。我的习惯是先挑 Notepad 跑通,再拿 SwingSet2 验证资源加载路径,最后用jvmti里的一个示例验证 native 编译链是否真的对上了 64 位。顺序反了会浪费很多时间在环境问题上,而不是 demo 本身。

提示:这个包对应的是早已停止公开更新的 JDK 7 时代产物,只适合离线学习与老项目对照,不要部署到对外提供服务的环境里。

3. 单文件编译跑通:javac 与 java 命令的四个关键参数

拿到源码直接javac *.java是新手最常见的起手式,在这个包里成功率不高,因为资源目录、包声明、源码编码三个变量同时存在。这一章用 Notepad 和 SwingSet2 两个对象,把编译参数逐个拆开讲清楚。

3.1 环境变量与编译前的三处确认

先确认 JDK 位数和版本,因为 demos 包是 64 位版本,混用 32 位 JDK 会在 native 环节暴露问题:

:: 切到 64 位 JDK 7,注意路径按本机实际安装位置改 set JAVA_HOME=C:\Java\jdk1.7.0_05 set PATH=%JAVA_HOME%\bin;%PATH% java -version :: 期望输出类似:java version "1.7.0_05" 与 Java HotSpot(TM) 64-Bit Server VM

java -version输出里的64-Bit Server VM是要确认的关键信息,它决定了后面能不能加载包内自带的 64 位 dll。第二处确认是源码编码,老示例的中文注释多为 GBK,用默认编码读会报「不可映射字符」。第三处确认是目录结构,先看清src与resources是不是平级,这决定-sourcepath和运行期 classpath 怎么写。

3.2 编译 Notepad 并处理资源目录

Notepad 的入口类通常带包声明,源码放在src下,图标等资源放在同级目录。按「源码一个根、资源一个根、输出一个根」的方式编译:

cd /d D:\jdksrc\demo\jfc\Notepad :: -encoding 指定源码编码,-d 指定输出根,-sourcepath 让 javac 找同项目依赖 javac -encoding GBK -sourcepath src -d ..\..\..\build\notepad src\*.java :: 运行时把资源目录一起挂到 classpath,否则图标与配置读不到 java -cp ..\..\..\build\notepad;src Notepad

-encoding GBK是这套老源码的默认值,如果源码已被人改成 UTF-8,改成-encoding UTF-8即可,报错信息会直接指明第几行第几个字符不可映射。-d把 class 输出到独立目录,避免污染源码目录,后面打包也干净。-cp里的src不是可选项,Notepad 通过ClassLoader.getResource按相对路径找资源,资源根不在 classpath 上就会抛空指针,报错位置往往在初始化代码里,看着和资源毫无关系。

3.3 编译 SwingSet2:多包结构与运行参数

SwingSet2 的类分布在多个包里,直接列*.java会漏掉子目录,正确做法是交给 javac 自己按源码路径找依赖:

cd /d D:\jdksrc\demo\jfc\SwingSet2 :: 只给入口类,其余依赖由 -sourcepath 自动解析 javac -encoding GBK -sourcepath src -d ..\..\..\build\swingset2 src\SwingSet2.java :: 运行:classpath 同时包含编译输出与资源根 java -cp ..\..\..\build\swingset2;src SwingSet2

这个写法比通配符更可靠,因为-sourcepath会按包名映射目录,编译器能精确定位到每个被引用的类。运行期的-cp顺序有讲究:编译输出在前、资源根在后,同名资源以先命中的为准,调皮肤时如果发现改了图没生效,先查这里有没有重复资源。启动后如果界面出来但按钮图标是空白方块,基本可以断定资源根没挂上,而不是图片损坏。

想验证编译产物是否自洽,可以加一个-Xlint:all再看一遍警告。老代码里未检查的泛型、过时的 API 提示会刷屏,但真正需要注意的是「找不到符号」和「程序包不存在」,前者多半是-sourcepath少了目录,后者通常是包声明与目录层级不一致。

3.4 用 Ant 批量构建带 build.xml 的示例

jfc与javafx-samples下不少示例自带build.xml,这类项目不要手工编译,脚本里已经写好了资源拷贝和 jar 打包。先确认 Ant 可用,再带属性覆盖目标版本:

set ANT_HOME=D:\tools\apache-ant call %ANT_HOME%\bin\ant.bat -version cd /d D:\jdksrc\demo\jfc\Java2D :: -D 传属性覆盖脚本里的默认 JDK 路径,target 视脚本而定 call %ANT_HOME%\bin\ant.bat -f build.xml -Dplatforms.JDK_1.7.home=%JAVA_HOME% clean jar

-Dplatforms.JDK_1.7.home这类属性名由脚本定义,不同示例不一样,先ant -p看属性列表再传,盲传会静默失效。clean放在jar前面是为了清掉上一轮残留的 class,老脚本的增量判断不总是可靠。构建产物一般在dist或build下,jar 里是否包含资源目录,用jar tf抽查一眼最省事:

jar tf dist\Java2D.jar | findstr /i "images sounds"

没有输出说明资源没打进去,回头查build.xml里的fileset是否把资源目录排除在外。

4. JavaFX 与 JNI demo:构建脚本、64 位 dll 和签名三件事

前三类 demo 是纯 Java,剩下三类各自踩在不同的坑上:JavaFX 示例依赖 Ant 和 SDK 布局,WebStart 依赖签名,JVMTI 依赖 C 编译器和位数匹配。这一章按依赖复杂度从低到高排。

4.1 JavaFX samples 的构建入口

如果你的包里存在demo\javafx-samples-*这样的目录,它和其他示例的组织方式不同,通常每个子目录一套独立构建脚本,需要先设置 SDK 路径。构建前先看目录里的说明文件确认属性名,再执行:

cd /d D:\jdksrc\demo\javafx-samples-2.x\BouncingBalls :: 先列出脚本支持的目标与属性,避免盲传 call %ANT_HOME%\bin\ant.bat -p :: 指定 JavaFX SDK 位置后构建,属性名以 -p 输出为准 call %ANT_HOME%\bin\ant.bat -Djavafx.sdk.dir=D:\Java\javafx-sdk clean jar

-p输出的属性表是唯一可信来源,不同小版本的属性名可能是javafx.sdk.dir也可能是platforms.JDK_1.7.home,照抄别人的命令大概率无效且不报错。构建成功的标志是dist下出现 jar 与启动脚本,直接java -jar常常起不来,因为 JavaFX 运行时要靠脚本拼 classpath,按脚本里的java行手工执行一次更容易定位缺哪个 jar。

4.2 WebStart 示例:签名与启动

webstart目录下的示例靠 jnlp 描述文件启动,未签名的 jar 在受限沙箱里运行,涉及文件读写的示例会直接抛安全异常。本地验证流程是先生成自签名证书,再签 jar,最后用javaws打开 jnlp:

:: 生成自签名密钥库,dname 里不要填任何真实单位信息 keytool -genkeypair -keystore demos.jks -alias demo -keyalg RSA -keysize 2048 ^ -validity 3650 -storepass changeit -keypass changeit ^ -dname "CN=Local Demo, OU=Test, O=Local, L=Local, ST=Local, C=CN" :: 对示例 jar 签名,jar 名按实际产物替换 jarsigner -keystore demos.jks -storepass changeit -keypass changeit dist\Notepad.jar demo :: 用 JDK 7 自带的 javaws 启动 jnlp "%JAVA_HOME%\bin\javaws.exe" dist\notepad.jnlp

-validity 3650给足有效期,本地验证不至于中途过期。签名后第一次启动会弹证书确认,选择信任并运行即可。如果 jnlp 里写死了旧代码库地址,先把codebase改成file:本地路径,否则启动器会去拉不存在的远程 jar,表现是一直停在加载界面。

4.3 JVMTI 示例:编译 64 位 dll

jvmti下的示例需要一个与 JVM 位数一致的动态库,32 位 dll 放进 64 位 JVM 会报Can't load IA 32-bit .dll on a AMD 64-bit platform。编译必须在 x64 本机工具命令行里进行:

:: 在 x64 Native Tools 命令行中执行,确保 cl 输出 64 位目标 cl /LD /I"%JAVA_HOME%\include" /I"%JAVA_HOME%\include\win32" ^ versionCheck.c /Fe:versionCheck.dll :: 运行示例,-agentpath 指向刚编译出的 dll java -agentpath:.\versionCheck.dll -version

/LD生成动态库,两个/I分别指向 JNI 通用头与平台头,缺一个就会报找不到jni.h或jni_md.h。/Fe指定输出文件名,省略会按源码名生成。运行参数-agentpath给绝对路径更保险,相对路径在切换工作目录后容易失效。加载成功时会在标准错误里看到 agent 打印的信息,加载失败则抛Agent JAR not found or no Agent-Class或UnsatisfiedLinkError,两者对应的问题完全不同,先看异常类型再查。

5. 避坑与排查:五个报错现象、原因和改法

这个包的坑集中在编码、版本、位数、资源路径四类,报错信息大多指向表象。下面五条是我在实际拆解中反复遇到的,按「现象 → 原因 → 解决」写清楚,遇到同类问题可以直接对号。

5.1 现象:UnsupportedClassVersionError 版本号不匹配

运行示例时报Unsupported major.minor version 51.0。原因是包内部分预编译 jar 是 Java 7 目标字节码,而本机java命令指向了 JDK 8 以上的运行时,或者反过来,用 JDK 7 去跑更高版本编译的 jar。

解决方式是先java -version确认运行时,再用同一套 JDK 重新编译源码,不要混用系统里多个 JDK。命令行里显式写全路径比依赖 PATH 更可靠,比如直接C:\Java\jdk1.7.0_05\bin\java -cp ...。如果必须在新 JDK 上跑,只能重编译源码,改字节码版本没有实际意义。

5.2 现象:javac 报「编码 GBK 的不可映射字符」

编译时刷出一片警告,个别文件直接失败,提示某行存在不可映射字符。原因是源码混合了 GBK 与 UTF-8,或者文件里存在 GBK 无法表示的符号。

解决办法是按文件实际编码分别处理:先用-encoding GBK整体编译,失败的文件单独确认编码后转成 UTF-8 再统一用-encoding UTF-8。转换前先备份,转码工具选能保留换行符的,避免整个文件变成一行。运行期如果控制台输出乱码,那是终端代码页问题,chcp 936切回 GBK 代码页对照一下即可,和编译编码不是一回事。

5.3 现象:UnsatisfiedLinkError 提示 32 位 dll

加载 native 库时报Can't load IA 32-bit .dll on a AMD 64-bit platform。原因有两个:包里的预编译 dll 是 32 位版本,或者编译时用的cl来自 32 位工具链。

解决方式是确认java -version输出 64-Bit 后,在 x64 本机工具命令行里重新编译 dll,并用dumpbin /headers versionCheck.dll | findstr machine检查目标机类型,输出应为 x64。两个环节任意一个用了 32 位工具,结果都是同一个报错,所以位数验证要做两次而不是一次。

5.4 现象:界面起来但图片全是空白

Swing 示例能启动,按钮和面板上的图标显示为空白方块或缺失。原因是资源目录没有进入 classpath 根,getResource返回 null,代码里没做判空就静默跳过绘制。

解决办法是把资源根与编译输出一起挂到-cp,并用jar tf确认打包后的 jar 里确实存在对应路径的资源文件。调试时可以在启动参数里加一段打印,把getClass().getResource("/resources/...")的结果输出出来,路径前导斜杠的有无、大小写差异都会导致命中失败,Windows 文件系统不区分大小写,打包到其他环境才暴露。

5.5 现象:applet 示例打开一片空白

用浏览器打开applets下的 html 页面,区域空白,控制台无报错。原因是现代浏览器已经移除 Java 插件支持,页面里的applet标签不会被任何运行时接管。

这类示例的正确用法是只读源码,看它怎么组织绘制循环、怎么处理鼠标事件,不要指望运行。需要看效果的话,把绘制逻辑抽出来改成一个JFrame承载的JPanel,几十行就能跑起来,比折腾插件环境省时间。同理,plugin目录下的示例也只作参考。

6. 把 demo 拆成最小工程:一份验证清单和我的固定动作

拆解到后面会发现,真正有价值的不是把示例原样跑起来,而是把它压成一个能塞进现有项目的最小结构。我通常会挑 Notepad 或sample下的单文件示例,删到只剩入口类、一个面板类、一个资源目录,然后补一份最小构建脚本,确认三件事:编译无警告、资源能加载、打包后java -jar能启动。这三步走完,示例才算真正可用。

下面这份清单是我每次拆新示例时固定过一遍的,按顺序执行,出问题能快速定位到环节:

检查项命令或动作通过标准
运行时位数java -version输出含 64-Bit
源码编码javac -encoding显式指定无不可映射字符
依赖解析javac -sourcepath只给入口类无「找不到符号」
资源加载运行期-cp含资源根图标与配置正常
打包完整性jar tf抽查资源路径资源文件在 jar 内
native 位数dumpbin /headers目标机类型为 x64

有一个细节值得单独说:打包成可执行 jar 时,Main-Class要写在 manifest 里,资源路径要保持与源码里getResource的相对路径一致,不能因为换了目录就改前导斜杠。我习惯在jar命令后立刻执行一次java -jar冒烟,而不是等到交付前才试,因为 classpath 从目录切到 jar 之后,资源定位方式会变,问题往往在这一刻才冒出来。

还有一个容易忽略的点是字符集。老示例里有硬编码的字体名和字号,在中文字体环境下布局会偏移,按钮文字被截断。处理方法不是改代码里的常量,而是在启动时读取系统字体替换,把字体决策集中到一个配置类里,后面换环境只改一处。

从那以后我每次拆这类历史示例包,都强制先跑一遍位数与编码确认,再动编译命令,宁可多花五分钟,也不要在UnsatisfiedLinkError和乱码之间来回猜。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进