﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-dproc2003-随笔分类-java</title><link>http://www.cnitblog.com/dproc2003/category/8516.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 30 Sep 2011 13:15:44 GMT</lastBuildDate><pubDate>Fri, 30 Sep 2011 13:15:44 GMT</pubDate><ttl>60</ttl><item><title>Retrotranslator让你用JDK1.5的特性写出的代码能在JVM1.4中运行</title><link>http://www.cnitblog.com/dproc2003/archive/2009/12/02/62970.html</link><dc:creator>Roc</dc:creator><author>Roc</author><pubDate>Wed, 02 Dec 2009 07:51:00 GMT</pubDate><guid>http://www.cnitblog.com/dproc2003/archive/2009/12/02/62970.html</guid><wfw:comment>http://www.cnitblog.com/dproc2003/comments/62970.html</wfw:comment><comments>http://www.cnitblog.com/dproc2003/archive/2009/12/02/62970.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/dproc2003/comments/commentRss/62970.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dproc2003/services/trackbacks/62970.html</trackback:ping><description><![CDATA[ JDK1.5出来多年了(2004年10月正式发行)，就连6.0正式版在 http://java.sun.com上已是赫然在目，紧跟着的各应用服务器和 Java IDE 厂商的都准备就绪. 可是相信很多开发者跟我一样却碍于公司用的是老版本的应用服务器，如WebSphere Application Server,，WebLogic等只能支持到1.4的JDK，要升级应用服务器成本和风险都有担心，所以项目中只能用1.4 的JDK，一直无法体验到 JDK 1.5 的新特性带来的便利．

有些同事机器里一直还是躺着 JDK 1.4，我可能比他们好一点就是直接装了一个 JDK 1.5，然后在 Java IDE 中设置编译器的 Compiler compliance level为 1.4（实质就是javac &#8211;target 1.4）．这样避免了用JDK1.5编译的Class 放在1.4的JVM中运行出现49.0的字节码版本太高的错误，这样做只不是50步和100步的差距，照例用不了JDK1.5 的新特性．

我一直在探索：能不能使用JDK1.5的特性，然后让生成的字节码能在1.4的JVM下运行．如是果是仅仅换掉应用服务所用的JDK恐怕是会出很多问题的．

也算是功夫不负有心人啊，时至今日终于在网上找着了一个叫做 Retrotranslator 的工具，开源的，在 SourceForge http://sourceforge.net/projects/retrotranslator，当前版本是1.2.1．这个工具正好能满足我的需求．进到那个下载页面，你只要下载 Retrotranslator-1.2.1-bin.zip，其中包含了

retrotranslator-runtime-1.1.1-bundle.jar 字节码转换之后，运行时需要这个包来支持

retrotranslator-transformer-1.2.1-bundle.jar 转换字节码用，如用命令转换或用ant来转换需要这个包

而且还有一个 backport-util-concurrent-3.0.jar 大概是用新的并发机制运行时需要依赖这个包

Retrotranslator实质上是一个基于ASM 框架的字节码转换工具，如果要看它详细的使用帮助，请见页面　http://retrotranslator.sourceforge.net/．
使用 Retrotranslator 可以让你使用的JDK1.5的特征有泛型、注解、泛型和注解的反射、枚举、自动装/拆箱、增强的循环、变参、协变式返回类型、格式化输出、静态引入、新的并发机制、增强的集合框架。（见What Java 5 features are supported?）
还能使用新的类和新的方法，如 StringBuilder 等，见What Java 5 classes and methods are supported?
使用方式：

1． 命令行下转换用JDK1.5的编译的classes文件

2． 用Ant或Maven自动化转换用JDK1.5的编译的classes文件，Retrotranslator提供了对Ant和Maven的支持。

3． 运行程序时加载类时即时转换换用JDK1.5的编译的类打成的jar包

4． 作为 IntelliJ IDEA的一个插件使用，可惜现在还没有Eclipse的插件，只能是期盼着。



下面只介绍如何用ANT方式来转换用JDK1.5的编译的classes文件，让它们能运行在1.4的JVM中。步骤如下：

1． 用1.5的JDK编译好你的类，比如编译在c:\workspace\unmi\WEB-INF\classes下。用Ant或是IDE帮你编译好。

2． 把retrotranslator-transformer-1.2.1-bundle.jar拷入到 $ANT_HOME/lib中，如果是在Eclipse中运行Ant Build文件，你需要设置 Window->Preferences->Ant-Runtime然后Add External JARSs&#8230;把retrotranslator-transformer-1.2.1-bundle.jar加进来。

3． 上面准备做好了，就要开始用 Retrotranslator来转换你的字节码了。转换部分的Ant Build的target像下面这么写：

01.
<!-- 用 Retrotranslator 把上面编译的Class文件转换成JVM1.4的Class文件-->
02.
<target name="translate"> 
03.
<taskdef name="retrotranslator"
04.
classname="net.sf.retrotranslator.transformer.RetrotranslatorTask" /> 
05.
<retrotranslator
06.
destdir="c:\workspace\unmi\WEB-INF\classes" verify="true" 
07.
srcdir="c:\workspace\unmi\WEB-INF\classes"> 
08.
<!-- 项目中用到的包或类 -->   
09.
<classpath refid="classpath"/>  
10.
 
11.
<!-- 1.4JDK的运行时包 -->
12.
<classpath location="c:/jdk1.4/jre/lib/rt.jar"/> 
13.
</retrotranslator> 
14.
</target> 

4． 注意到上面destdir和srcdir都写成了一样的，我是让转换后的类直接把原来的类覆盖了，你可已把destdir设置为别的目录。

5． 最后就是运行了，你需要事先把retrotranslator-runtime-1.1.1-bundle.jar和backport-util-concurrent-3.0.jar配置在你的classpath中，比如是WEB应用程序，把它们拷到WEB-INF\lib中就行了。

比如，你写的是一个企业应用程序或者WEB程序，你只需要在打EAR或WAR包之前作以上的转换就行了。

另外，对于在JSP中用了JDK1.5的特性，就稍稍有点麻烦了，先要用特定于应用服务器的JSP编译器编译好JSP文件，在用 Retrotranslator 转换生成的 JSP 对应的class文件。如果中途对JSP文件有改动，我们不仅要编译并转换JSP 对应的class文件，然后覆盖掉服务器上的那个JSP对应的class文件，还要覆盖服务器上的那个JSP文件。

通常我们都不会对JSP文件预编译的。

所以我们的口号是：尽量不在JSP文件中写JAVA代码，写了也不要用JDK1.5的新特性，即使有复杂的东西也可以交给自定义标签处理。

比如struts标签

1.
<logic:equal name="myBean" property="amount"  value="1">&#8230;&#8230;</logic:equal> 

不管amount是Integer还是 int 都无妨

再比如

1.
<logic:iterate name="myBeanList" id="myBean"> 
2.
<bean:write name="myBean" property="amount"/> 
3.
</logic:iterate> 

不管myBeanList是否是用的泛型，都能够正确取出数据来。如果要求严格点在给<logic:iterate标签加上一个type属性指明所存储的数据类型。

当然不是正统上的东西还是会有一些局限性的，具体参看：What are the limitations?，如果在项目中遇到问题，我想总会有别的解决问道的。

借助于 Retrotranslator，我的一个项目就是用JDK1.5的特性写的，用了泛型、增强的循环、自动装/拆箱等特新，部署在WAS5.1下运行，至今状况十分良好。

其他类似工具：

    1. Retroweaver
    2. Declawer
    3. JBossRetro

参考：
    1. Retrotranslator ReadMe.html

由于在 Eclipse 没有相应的插件，可考虑修改在 JDK 1.5 中使用 javac api 调用 Retrotranslator 打造自己的 java 编译器。 

后记：retrotranslator 的  destdir 属性可以省略，这时候就与 srcdir 同一目录，转换后的 class 覆盖原来的 class。并且 srcdir 可以指定到包目录中，如 srcdir="c:\workspace\unmi\WEB-INF\classes\com\unmi\utils"，这时候只转换 com.unmi.utils 包中的 class 文件。<img src ="http://www.cnitblog.com/dproc2003/aggbug/62970.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dproc2003/" target="_blank">Roc</a> 2009-12-02 15:51 <a href="http://www.cnitblog.com/dproc2003/archive/2009/12/02/62970.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>明明白白Unsupported major.minor version 49.0的错误</title><link>http://www.cnitblog.com/dproc2003/archive/2009/12/02/62969.html</link><dc:creator>Roc</dc:creator><author>Roc</author><pubDate>Wed, 02 Dec 2009 07:46:00 GMT</pubDate><guid>http://www.cnitblog.com/dproc2003/archive/2009/12/02/62969.html</guid><wfw:comment>http://www.cnitblog.com/dproc2003/comments/62969.html</wfw:comment><comments>http://www.cnitblog.com/dproc2003/archive/2009/12/02/62969.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/dproc2003/comments/commentRss/62969.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dproc2003/services/trackbacks/62969.html</trackback:ping><description><![CDATA[ 一：要解决的问题 

我们在尝鲜 JDK1.5 的时候，相信不少人遇到过 Unsupported major.minor version 49.0 错误，当时定会茫然不知所措。因为刚开始那会儿，网上与此相关的中文资料还不多，现在好了，网上一找就知道是如何解决，大多会告诉你要使用 JDK 1.4 重新编译。那么至于为什么，那个 major.minor 究竟为何物呢？这就是本篇来讲的内容，以使未错而先知。 

我觉得我是比较幸运的，因为在遇到那个错误之前已研读过《深入 Java 虚拟机》第二版，英文原书名为《Inside the Java Virtual Machine》( Second Edition)，看时已知晓 major.minor 藏匿于何处，但没有切身体会，待到与 Unsupported major.minor version 49.0 真正会面试，正好是给我验证了一个事实。 

首先我们要对 Unsupported major.minor version 49.0 建立的直接感觉是：JDK1.5 编译出来的类不能在 JVM 1.4 下运行，必须编译成 JVM 1.4 下能运行的类。(当然，也许你用的还是 JVM 1.3 或 JVM 1.2，那么就要编译成目标 JVM 能认可的类)。这也解决问题的方向。

 二：major.minor 栖身于何处 

何谓 major.minor，且又居身于何处呢？先感性认识并找到 major.minor 来。

 写一个 Java Hello World! 代码，然后用 JDK 1.5 的编译器编译成，HelloWorld.java 

 

用 JDK 1.5 的 javac -d .  HelloWorld.java 编译出来的字节码 HelloWorld.class 用 UltraEdit 打开来的内容如图所示： 


从上图中我们看出来了什么是 major.minor version 了，它相当于一个软件的主次版本号，只是在这里是标识的一个 Java Class 的主版本号和次版本号，同时我们看到 minor_version 为 0x0000，major_version 为 0x0031，转换为十制数分别为0 和 49，即 major.minor 就是 49.0 了。 

三：何谓 major.minor 以及何用 

Class 文件的第 5-8 字节为 minor_version 和 major_version。Java class 文件格式可能会加入新特性。class 文件格式一旦发生变化，版本号也会随之变化。对于 JVM 来说，版本号确定了特定的 class 文件格式，通常只有给定主版本号和一系列次版本号后，JVM 才能够读取 class 文件。如果 class 文件的版本号超出了 JVM 所能处理的有效范围，JVM 将不会处理该 class 文件。 

在 Sun 的 JDK 1.0.2 发布版中，JVM 实现支持从 45.0 到 45.3 的 class 文件格式。在所有 JDK 1.1 发布版中的 JVM 都能够支持版本从 45.0 到 45.65535 的 class 文件格式。在 Sun 的 1.2 版本的 SDK 中，JVM 能够支持从版本 45.0 到46.0 的 class 文件格式。 

1.0 或 1.2 版本的编译器能够产生版本号为 45.3 的 class 文件。在 Sun 的 1.2 版本 SDK 中，Javac 编译器默认产生版本号为 45.3  的 class 文件。但如果在 javac 命令行中指定了 -target 1.2 标志，1.2 版本的编译器将产生版本号为 46.0 的 class 文件。1.0 或 1.1 版本的 JVM 上不能运行使用-target 1.2 标志所产生的 class 文件。 

JVM 实现的 第二版中修改了对 class 文件主版本号和次版本号的解释。对于第二版而言，class 文件的主版本号与 Java 平台主发布版的版本号保持一致(例如：在 Java 2 平台发布版上，主版本号从 45 升至 46)，次版本号与特定主平台发布版的各个发布版相关。因此，尽管不同的 class 文件格式可以由不同的版本号表示，但版本号不一样并不代表 class 文件格式不同。版本号不同的原因可能只是因为 class 文件由不同发布版本的 java 平台产生，可能 class 文件的格式并没有改变。 

上面三段节选自《深入 Java 虚拟机》，啰嗦一堆，JDK 1.2 开启了 Java 2 的时代，但那个年代仍然离我们很远，我们当中很多少直接跳在 JDK 1.4 上的，我也差不多，只是项目要求不得不在一段时间里委屈在 JDK 1.3 上。不过大致我们可以得到的信息就是每个版本的 JDK 编译器编译出的 class 文件中都带有一个版本号，不同的 JVM 能接受一个范围 class 版本号，超出范围则要出错。不过一般都是能向后兼容的，知道 Sun 在做 Solaris 的一句口号吗？保持对先前版本的 100% 二进制兼容性，这也是对客户的投资保护。 

四：其他确定 class 的 major.minor version 办法 

1）Eclipse 中查看

       Eclipse 3.3 加入的新特征，当某个类没有关联到源代码，打开它会显示比较详细的类信息，当然还未到源码级别了，看下图是打开 2.0 spring.jar 中 ClasspathXmlApplicationContext.class 显示的信息 


2）命令 javap -verbose 

       对于编译出的 class 文件用 javap -verbose 能显示出类的 major.minor 版本，见下图： 


3)  MANIFEST 文件

       把 class 打成的 JAR 包中都会有文件 META-INF\MANIFEST，这个文件一般会有编译器的信息，下面列几个包的 META-INF\MANIFEST 文件内容大家看看

         &#183;Velocity-1.5.jar 的 META-INFO\MANIFEST 部份内容

                    Manifest-Version: 1.0

                   Ant-Version: Apache Ant 1.7.0 

                   Created-By: Apache Ant

                   Package: org.apache.velocity

                   Build-Jdk: 1.4.2_08  

                 Extension-Name: velocity

             我们看到是用 ant 打包，构建用的JDK是 1.4.2_08，用 1.4 编译的类在 1.4 JVM 中当然能运行。如果那人用 1.5 的 JDK 来编译，然后用 JDK 1.4+ANT 来打包就太无聊了。

       &#183;2.0 spring.jar 的 META-INFO\MANIFEST 部份内容

                   Manifest-Version: 1.0 

                  Ant-Version: Apache Ant 1.6.5 

                  Created-By: 1.5.0_08-b03 (Sun Microsystems Inc.) 

                  Implementation-Title: Spring Framework 

           这下要注意啦，它是用的 JDK 1.5 来编译的，那么它是否带了 -target 1.4 或 -target 1.3 来编译的呢？确实是的，可以查看类的二进制文件，这是最保险的。所在 spring-2.0.jar 也可以在 1.4 JVM 中加载执行。

       &#183;自已一个项目中用 ant 打的 jar 包的 META-INFO\MANIFEST

                   Manifest-Version: 1.0

                   Ant-Version: Apache Ant 1.7.0 

                  Created-By: 1.4.2-b28 (Sun Microsystems Inc.) 

            用的是 JDK 1.4 构建打包的。

 第一第二种办法能明确知道 major.minor version，而第三种方法应该也没问题，但是碰到变态构建就难说了，比如谁把那个 META-INFO\MANIFEST 打包后换了也未可知。直接查看类的二进制文件的方法可以万分保证，准确无误，就是工具篡改我也认了。

 五：编译器比较及症节之所在

 现在不妨从 JDK 1.1 到 JDK 1.7 编译器编译出的 class 的默认 minor.major version 吧。（又走到 Sun 的网站上翻腾出我从来都没用过的古董来） JDK 编译器版本	target 参数	十六进制 minor.major	十进制 minor.major
jdk1.1.8	不能带 target 参数	00 03   00 2D	45.3
jdk1.2.2	不带(默认为 -target 1.1)	00 03   00 2D	45.3
jdk1.2.2	-target 1.2	00 00   00 2E	46.0
jdk1.3.1_19	不带(默认为 -target 1.1)	00 03   00 2D	45.3
jdk1.3.1_19	-target 1.3	00 00   00 2F	47.0
j2sdk1.4.2_10	不带(默认为 -target 1.2)	00 00   00 2E	46.0
j2sdk1.4.2_10	-target 1.4	00 00   00 30	48.0
jdk1.5.0_11	不带(默认为 -target 1.5)	00 00   00 31	49.0
jdk1.5.0_11	-target 1.4 -source 1.4	00 00   00 30	48.0
jdk1.6.0_01	不带(默认为 -target 1.6)	00 00   00 32	50.0
jdk1.6.0_01	-target 1.5	00 00   00 31	49.0
jdk1.6.0_01	-target 1.4 -source 1.4	00 00   00 30	48.0
jdk1.7.0	不带(默认为 -target 1.6)	00 00   00 32	50.0
jdk1.7.0	-target 1.7	00 00   00 33	51.0
jdk1.7.0	-target 1.4 -source 1.4	00 00   00 30	48.0
Apache Harmony 5.0M3	不带(默认为 -target 1.2)	00 00   00 2E	46.0
Apache Harmony 5.0M3	-target 1.4	00 00   00 30	48.0


上面比较是 Windows 平台下的 JDK 编译器的情况，我们可以此作些总结： 

1) -target 1.1 时 有次版本号，target 为 1.2 及以后都只用主版本号了，次版本号为 0 

2) 从 1.1 到 1.4 语言差异比较小，所以 1.2 到 1.4 默认的 target 都不是自身相对应版本 

3) 1.5 语法变动很大，所以直接默认 target 就是 1.5。也因为如此用 1.5 的 JDK 要生成目标为 1.4 的代码，光有 -target 1.4 不够，必须同时带上 -source 1.4，指定源码的兼容性，1.6/1.7 JDk 生成目标为 1.4 的代码也如此。 

4) 1.6 编译器显得较为激进，默认参数就为 -target 1.6。因为 1.6 和 1.5 的语法无差异，所以用 -target 1.5 时无需跟着 -source 1.5。 

5) 注意 1.7 编译的默认 target 为 1.6 

6) 其他第三方的 JDK 生成的 Class 文件格式版本号同对应 Sun 版本 JDK 

7) 最后一点最重要的，某个版本的 JVM 能接受 class 文件的最大主版本号不能超过对应 JDK 带相应 target 参数编译出来的 class 文件的版本号。 

上面那句话有点长，一口气读过去不是很好理解，举个例子：1.4 的 JVM 能接受最大的 class 文件的主版本号不能超过用 1.4 JDK 带参数 -target 1.4 时编译出的 class 文件的主版本号，也就是 48。

 因为 1.5 JDK 编译时默认 target 为 1.5，出来的字节码 major.minor version 是 49.0，所以 1.4 的 JVM 是无法接受的，只有抛出错误。 

那么又为什么从 1.1 到 1.2、从 1.2 到 1.3 或者从 1.3 到 1.4 的 JDK 升级不会发生 Unsupported major.minor version 的错误呢，那是因为 1.2/1.3/1.4 都保持了很好的二进制兼容性，看看 1.2/1.3/1.4 的默认 target 分别为 1.1/1.1/1.2 就知道了，也就是默认情况下1.4 JDK 编译出的 class 文件在 JVM 1.2 下都能加载执行，何况于 JVM 1.3 呢？（当然要去除使用了新版本扩充的 API 的因素） 

六：找到问题解决的方法 

那么现在如果碰到这种问题该知道如何解决了吧，还会像我所见到有些兄弟那样，去找个 1.4 的 JDK 下载安装，然后用其重新编译所有的代码吗？其实大可不必如此费神，我们一定还记得 javac 还有个 -target 参数，对啦，可以继续使用 1.5 JDK，编译时带上参数 -target 1.4 -source 1.4 就 OK 啦，不过你一定要对哪些 API 是 1.5 JDK 加入进来的了如指掌，不能你的 class 文件拿到 JVM 1.4 下就会 method not found。目标 JVM 是 1.3 的话，编译选项就用 -target 1.3 -source 1.3 了。 

相应的如果使用 ant ，它的 javac 任务也可对应的选择 target 和 source <javac target="1.4" source="1.4" ............................/> 

如果是在开发中，可以肯定的是现在真正算得上是 JAVA IDE 对于工程也都有编译选项设置目标代码的。例如 Eclipse 的项目属性中的 Java Compiler 设置，如图 


自已设定编译选项，你会看到选择不同的 compiler compliance level 是，Generated class files compatibility 和 Source compatibility 也在变，你也可以手动调整那两项，手动设置后你就不用很在乎用的什么版本的编译器了，只要求他生成我们希望的字节码就行了，再引申一下就是即使源代码是用 VB 写的，只要能编译成 JVM 能执行的字节码都不打紧。在其他的 IDE 也能找到相应的设置对话框的。 

其他时候，你一定要知道当前的 JVM 是什么版本，能接受的字节码主版本号是多少（可对照前面那个表）。获息当前 JVM 版本有两种途径： 

第一：如果你是直接用 java 命令在控制台执行程序，可以用 java -version 查看当前的 JVM 版本，然后确定能接受的 class 文件版本

第二：如果是在容器中执行，而不能明确知道会使用哪个 JVM，那么可以在容器中执行的程序中加入代码　System.getProperty("java.runtime.version"); 或 System.getProperty("java.class.version")，获得 JVM 版本和能接受的 class 的版本号。 

最后一绝招，如果你不想针对低版本的 JVM 用 target 参数重新编译所有代码；如果你仍然想继续在代码中用新的 API 的话；更有甚者，你还用了 JDK 1.5 的新特性，譬如泛型、自动拆装箱、枚举等的话，那你用 -target 1.4 -source 1.4 就没法编译通过，不得不重新整理代码。那么告诉你最后一招，不需要再从源代码着手，直接转换你所正常编译出的字节码，继续享用那些新的特性，新的 API，那就是：请参考之前的一篇日志：Retrotranslator让你用JDK1.5的特性写出的代码能在JVM1.4中运行，我就是这么用的，做好测试就不会有问题的。 

七：再议一个实际发生的相关问题 

这是一个因为拷贝 Tomcat 而产生的 Unsupported major.minor version 49.0 错误。情景是：我本地安装的是 JDK 1.5，然后在网上找了一个 EXE 的 Tomcat 安装文件安装了并且可用。后来同事要一个 Tomcat，不想下载或安装，于是根据我以往的经验是把我的 Tomcat 整个目录拷给他应该就行了，结果是拿到他那里浏览 jsp 文件都出现 Unsupported major.minor version 49.0 错误，可以确定的是他安装的是 1.4 的 JDK，但我还是有些纳闷，先前对这个问题还颇有信心的我傻眼了。惯性思维是编译好的 class 文件拿到低版本的 JVM 会出现如是异常，可现并没有用已 JDK 1.5 编译好的类要执行啊。 

后来仔细看异常信息，终于发现了 %TOMCAT_HOME%\common\lib\tools.jar 这一眉目，因为 jsp 文件需要依赖它来编译，打来这个 tools.jar 中的一个 class 文件来看看，49.0，很快我就明白原来这个文件是在我的机器上安装 Tomcat 时由 Tomcat 安装程序从 %JDK1.5%\lib 目录拷到 Tomcat 的 lib 目录去的，造成在同事机器上编译 JSP 时是 1.4 的 JVM 配搭着 49.0 的 tools.jar，那能不出错，于是找来 1.4  JDK 的 tools.jar 替换了 Tomcat 的就 OK 啦。 

八：小结 

其实理解 major.minor 就像是我们可以这么想像，同样是微软件的程序，32 位的应用程序不能拿到 16 位系统中执行那样。 

如果我们发布前了解到目标 JVM 版本，知道怎么从 java class 文件中看出 major.minor 版本来，就不用等到服务器报出异常才着手去解决，也就能预知到可能发生的问题。 

其他时候遇到这个问题应具体解决，总之问题的根由是低版本的  JVM 无法加载高版本的 class 文件造成的，找到高版本的 class 文件处理一下就行了。<img src ="http://www.cnitblog.com/dproc2003/aggbug/62969.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dproc2003/" target="_blank">Roc</a> 2009-12-02 15:46 <a href="http://www.cnitblog.com/dproc2003/archive/2009/12/02/62969.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>