潜心学习 技术强身

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  14 随笔 :: 0 文章 :: 8 评论 :: 0 Trackbacks

2009年8月5日 #

转自:http://www.blogjava.net/Unmi/archive/2007/12/04/165035.html
一:要解决的问题

我们在尝鲜 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
 1package com.unmi;
 2
 3public class HelloWorld
 4{
 5    public static void main(String[] args)
 6    {
 7        System.out.println("Hello, World!");
 8    }

 9}

10
用 JDK 1.5 的 javac -d .  HelloWorld.java 编译出来的字节码 HelloWorld.class 用 UltraEdit 打开来的内容如图所示:

HelloWorldClassUnmi.jpg


从上图中我们看出来了什么是 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 显示的信息

eclipseclass1.jpg

2)命令 javap -verbose
       对于编译出的 class 文件用 javap -verbose 能显示出类的 major.minor 版本,见下图:

JavapVerboseUnmi.jpg

3)  MANIFEST 文件
      把 class 打成的 JAR 包中都会有文件 META-INF\MANIFEST,这个文件一般会有编译器的信息,下面列几个包的 META-INF\MANIFEST 文件内容大家看看
      ·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 来打包就太无聊了。
      ·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 中加载执行。
      ·自已一个项目中用 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 设置,如图

EclipseCompiler.JPG


自已设定编译选项,你会看到选择不同的 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 文件处理一下就行了。

posted @ 2009-08-05 16:27 平涛 阅读(390) | 评论 (0)编辑 收藏

2009年8月1日 #

     摘要: VO(value object) 值对象     通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要.个人觉得同DTO(数据传输对象),在web上传递。     对于数据库而言,每一个VO对象可以表示出一张表中的一行记录,此类的名称要与表的名称一致。 B...  阅读全文
posted @ 2009-08-01 11:12 平涛 阅读(511) | 评论 (1)编辑 收藏

2009年7月31日 #

       有许多标准和实践准则可适用于Java开发者,但此处要说的,是每个Java开发者需坚守的基本原则。

  一、为代码加注释。虽然每个人都知道这点,但有时却不自觉忘了履行,今天你“忘了”加注释了吗?虽然注释对程序的功能没什么“贡献”,但过一段时间,比如说两星期之后或者更长,回过头来看看自己的代码,说不定已经记不住它是干什么的了。如果这些代码是你个人的,那还算是走运了,不幸的是,当然了,大多数时候都是别人的不幸,很多时候大家都是在为公司写代码,写代码的人也许早已经离开了公司,但别忘了一句古话,有来有往嘛,为他人,也为我们自己,请为你的代码加上注释。

  二、不要让事情复杂化。程序员有时候总是对简单问题想出复杂的解决方案,比如说,在只有五个用户的程序中引入EJB、对程序实现了并不需要的框架(framework),之类的还有属性文件、面向对象解决方案、多线程等等。为什么要这样做呢?也许我们并不知道是否这样会更好,但这样做也许可以学到一些新东西,或者让自己更感兴趣一些。如果是不知道为什么这样做,建议多请教经验丰富的程序员,如果是为了个人的目的,麻烦让自己更专业一点。

  三、始终牢记——“少即是好(Less is more)并不总是对的”。代码效率虽然很重要,但在许多解决方案中,编写更少的代码并不能改善这些代码的效率,请看下面这个简单的例子:

if(newStatusCode.equals("SD") && (sellOffDate == null ||
todayDate.compareTo(sellOffDate)<0 || (lastUsedDate != null &&
todayDate.compareTo(lastUsedDate)>0)) ||
(newStatusCode.equals("OBS") && (OBSDate == null ||
todayDate.compareTo(OBSDate)<0))){
newStatusCode = "NYP"; 
}

  能看明白if条件语句是干什么的吗?能想出来是谁写的这段代码吗?如果把它分成两段独立的if语句,是不是更容易理解呢,下面是修改后的代码:

if(newStatusCode.equals("SD") && (sellOffDate == null ||
todayDate.compareTo(sellOffDate)<0 || (lastUsedDate != null &&
todayDate.compareTo(lastUsedDate)>0))){
newStatusCode = "NYP"; 
}else
if(newStatusCode.equals("OBS") && (OBSDate == null ||
todayDate.compareTo(OBSDate)<0))
{
newStatusCode = "NYP"; 
}

  是不是读起来容易多了呢,在此只是多加了一个if和两个花括号,但代码的可读性与可理解性就一下子提高了一大截。

  四、请不要硬编码。开发者经常有意“忘记”或忽略掉这点,因为有些时候开发日程逼得实在太紧。其实,多写一行定义静态变量的代码能花多少时间呢?

public class A {
public static final String S_CONSTANT_ABC = "ABC"; 
public boolean methodA(String sParam1){
if (A.S_CONSTANT_ABC.equalsIgnoreCase(sParam1)){
return true; 
}
return false; 
}
}

 

  现在,每次需要将“ABC”与其他变量进行比较时,不必记住实际代码,直接引用A.S_CONSTANT_ABC就行了,而且在今后需要进行修改时,也可在一处修改,不会翻遍整个源代码逐个修改了。

  五、不要“创造”自己的框架(framework)。确切来说,有数以千计的各种框架存在,而且大多数是开源的,这些框架都是优秀的解决方案,可用于日常程序开发中,我们只需使用这些框架的最新版本就行了,至少表面上要跟上形势吧。被大家广为接受的最为明显的一个例子就是Struts了,这个开源web框架非常适合用在基于web的应用程序中。是不是想开发出自己的Struts呢,还是省点力气吧,回头看看第二条——不要让事情复杂化。另外,如果正在开发的程序只有3个窗口,就不要使用Struts了,对这种程序来说,不需要那么多的“控制”。

  六、不要使用println及字符串连接。通常为了调试方便,开发者喜欢在可能的所有地方都加上System.out.println,也许还会提醒自己回过头来再来删除,但有些时候,经常会忘了删除或者不愿意删除它们。既然使用System.out.println是为了测试,那么测试完之后,为什么还要留着它们呢,因为在删除时,很可能会删除掉真正有用的代码,所以不能低估System.out.println危害啊,请看下面的代码:

public class BadCode {
public static void calculationWithPrint(){
double someValue = 0D; 
for (int i = 0; i <10000; i++) {
System.out.println(someValue = someValue + i); 
}
}
public static void calculationWithOutPrint(){
double someValue = 0D; 
for (int i = 0; i < 10000; i++) {
someValue = someValue + i; 
}
}
public static void main(String [] n) {
BadCode.calculationWithPrint(); 
BadCode.calculationWithOutPrint(); 
}
}

  从测试中可以发现,方法calculationWithOutPrint()执行用了0.001204秒,作为对比,方法calculationWithPrint()执行可是用了10.52秒。

  要避免浪费CPU时间,最好的方法是引入像如下的包装方法:

public class BadCode {
public static final int DEBUG_MODE = 1; 
public static final int PRODUCTION_MODE = 2; 
public static void calculationWithPrint(int logMode){
double someValue = 0D; 
for (int i = 0; i < 10000; i++) {
someValue = someValue + i; 
myPrintMethod(logMode, someValue); 
}
}
public static void myPrintMethod(int logMode, double value) {
if (logMode > BadCode.DEBUG_MODE) { return; }
System.out.println(value); 
}
public static void main(String [] n) {
BadCode.calculationWithPrint(BadCode.PRODUCTION_MODE); 
}
}

 

  另外,字符串连接也是浪费CPU时间的一个大头,请看下面的示例代码:

public static void concatenateStrings(String startingString) {
for (int i = 0; i < 20; i++) {
startingString = startingString + startingString; 
}
}
public static void concatenateStringsUsingStringBuffer(String startingString) {
StringBuffer sb = new StringBuffer(); 
sb.append(startingString); 
for (int i = 0; i < 20; i++) {
sb.append(sb.toString()); 
}
}

  在测试中可发现,使用StringBuffer的方法只用了0.01秒执行完毕,而使用连接的方法则用了0.08秒,选择显而易见了。

  七、多关注GUI(用户界面)。再三强调,GUI对商业客户来说,与程序的功能及效率同等重要,GUI是一个成功程序的最基本部分,而很多IT经理往往都没注意到GUI的重要性。在现实生活中,许多公司可能为了节省开支,没有雇用那些有着设计“用户友好”界面丰富经验的网页设计者,此时Java开发者只能依赖他们自身的HTML基本功及在此领域有限的知识,结果,很多开发出来的程序都是“计算机友好”甚于“用户友好”。很少有开发者同时精通软件开发及GUI设计,如果你在公司“不幸”被分配负责程序界面,就应该遵守下面三条原则:

  1、 不要再发明一次轮子,即不做无用功。现有的程序可能会有类似的界面需求。
  2、 先创建一个原型。这是非常重要一步,用户一般想看到他们将使用的东西,而且可以先利用这个原型征求用户的意见,再慢慢修改成用户想要的样子。
  3、 学会换位思考。换句话来说,就是从用户的角度来审查程序的需求。举例来讲,一个汇总的窗口可以跨页或者不跨页,作为一个软件开发者,可能会倾向于不跨页,因为这样简单一些。但是,从用户的角度来看,可能不希望看到上百行数据都挤在同一页上。

  八、文档需求不放松。每个商业需求都必须记录在案,这可能听上去像童话,似乎在现实生活中很难实现。而我们要做的是,不管开发时间多紧迫,不管最终期限多临近,对每个商业需求都必须记录在案。

   九、单元测试、单元测试、单元测试。关于什么是单元测试的最好方法,在此不便细说,只是强调,单元测试一定要完成,这也是编程中最基本的原则。当然了,如果有人帮你做单元测试自然是最好,如果没有,就自己来做吧,当创建一个单元测试计划时,请遵守以下三条最基本的原则:

  1、 先于编写类代码之前编写单元测试。
  2、 记录单元测试中的代码注释。
  3、 测试所有执行关键功能的公有方法,这里不是指set和get方法,除非它们是以自己独特方式执行set和get方法。

  十、质量,而不是数量。有些时候因为产品问题、期限紧迫、或一些预料之外的事情,导致常常不能按时下班,但一般而言,公司不会因为雇员经常加班而对之表扬和奖励,公司只看重高质量的工作。如果遵守了前九条原则,你会发现自己写出的代码bug少且可维护性高,无形中质量提高了一大步。

posted @ 2009-07-31 15:38 平涛 阅读(159) | 评论 (0)编辑 收藏

2009年7月30日 #

    Tomcat5 及 Tomcat6 下CP配置。
    主要是根据MLDN的一个实例进行配置的。实例中用的是Tomcat5.5,所以他的CP配置如下:
    第一:将jdbc驱动的jar包,如:MySQL、Oracle的jdbc驱动,拷贝到tomcat中lib下。
    注意:必须是tomcat中lib下,如:D:\Tomcat 6.0\lib,但不能是项目中的WEB-INF/lib下,因为这时tomcat解析不到此类目录。
    第二:在Tomcat的安装目录下conf下找到server.xml,在<host></host>之间加上如下代码:
 1<Context path="/zhinangtuan" docBase="F:\Eclipse\MyZNTProject\MyZhiNangTuanDemo\WebRoot"
 2        debug="5" reloadable="true" crossContext="true">
 3 
 4     <Logger className="org.apache.catalina.logger.FileLogger"
 5                 prefix="localhost_MysqlTest_log." suffix=".txt"
 6                 timestamp="true"/>
 7                 
 8    <Resource name="jdbc/mldn"  auth="Container" type="javax.sql.DataSource"/>
 9    
10    <ResourceParams name="jdbc/mldn">
11        <parameter>
12            <name>factory</name>
13            <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
14        </parameter>
15        <parameter>
16            <name>maxActive</name>
17            <value>100</value>
18        </parameter>
19        <parameter>
20            <name>maxIdle</name>
21            <value>30</value>
22        </parameter>
23        <parameter>
24            <name>maxWait</name>
25            <value>5000</value>
26        </parameter>
27        <parameter>
28            <name>username</name>
29            <value>root</value>
30        </parameter>
31        <parameter>
32            <name>password</name>
33            <value>mysqladmin</value>
34        </parameter>
35        <parameter>
36            <name>driverClassName</name>
37            <value>org.gjt.mm.mysql.Driver</value>
38        </parameter>
39        <parameter>
40            <name>url</name>
41            <value>jdbc:mysql://localhost:3306/mldn</value>
42        </parameter>
43      </ResourceParams>
44    </Context>
    第一部分为项目的发布路径。其他的就是数据库的配置了。
    第三:在Spring的配置文件applicationContext.xml中加上如下代码:
1<bean id="dataSource"
2        class="org.springframework.jndi.JndiObjectFactoryBean"
3        destroy-method="close">
4        <property name="jndiName">
5            <value>java:comp/env/jdbc/mldn</value>
6        </property>
7    </bean>

    而我用的Tomcat是6.0.18,在根据这个配置后就会出现javax.naming.NameNotFoundException: Name jdbc is not bound in this Context的错误。原因是,Tomcat5和Tomcat6的配置有所区别:
    第一:将jdbc驱动的jar包,拷贝到Tomcat的lib下
    第二:在tomcat中conf下的context.xml文件中<context>与</context>之间加入以下部分:
 1<Resource name="jdbc/mldn"   
 2        auth="Container"       
 3         type="javax.sql.DataSource"
 4         factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"
 5         driverClassName="com.mysql.jdbc.Driver"       
 6         url="jdbc:mysql://localhost:3306/mldn?useUnicode=true&amp;characterEncoding=utf-8"       
 7         username="root"       
 8         password="123"       
 9         maxActive="100"       
10         maxIdle="30"       
11        maxWait="10000" />
    同时在项目的web.xml中加入如下代码(网上说可以加也可以不加)
1<resource-ref>       
2     <description>DB Connection</description>       
3     <res-ref-name>jdbc/mldn</res-ref-name>       
4      <res-type>javax.sql.DataSource</res-type>       
5      <res-auth>Container</res-auth>       
6    </resource-ref>

    第三:在Spring的配置文件applicationContext.xml中加上如下代码:
1<bean id="dataSource"
2        class="org.springframework.jndi.JndiObjectFactoryBean"
3        destroy-method="close">
4        <property name="jndiName">
5            <value>java:comp/env/jdbc/mldn</value>
6        </property>
7    </bean>

    这样,Tomcat6下的CP才能正常的运行起来。
posted @ 2009-07-30 01:21 平涛 阅读(1729) | 评论 (4)编辑 收藏

2009年7月23日 #

    主要是mysql数据库乱码问题,关于网页中的乱码(JSP页面乱码,action乱码)都好解决,只要页面的编码统一就可以了。我比较喜欢用UTF-8编码,不是很喜欢GBK或GB2312编码。
      关于JSP页面及action只需添加一个EncodingFilter就可以了,代码如下:
 1package cn.zhang.myznt.filter;
 2
 3import java.io.IOException;
 4
 5import javax.servlet.Filter;
 6import javax.servlet.FilterChain;
 7import javax.servlet.FilterConfig;
 8import javax.servlet.ServletException;
 9import javax.servlet.ServletRequest;
10import javax.servlet.ServletResponse;
11
12public class EncodingFilter implements Filter {
13    private String charset = null;
14    public void destroy() {
15
16    }

17
18    public void doFilter(ServletRequest request, ServletResponse response,
19            FilterChain chain) throws IOException, ServletException {
20        request.setCharacterEncoding(this.charset);
21        chain.doFilter(request, response);
22
23    }

24
25    public void init(FilterConfig arg0) throws ServletException {
26        this.charset = arg0.getInitParameter("charset");
27    }

28
29}

30

在web.xml中注册这个Filter,注意他的位置必须放在需要调用action及jsp或其他页面声明之前
 1<filter>
 2        <filter-name>encoding</filter-name>
 3        <filter-class>cn.zhang.myznt.filter.EncodingFilter</filter-class>
 4        <init-param>
 5            <param-name>charset</param-name>
 6            <param-value>UTF-8</param-value>
 7        </init-param>
 8    </filter>
 9    <filter-mapping>
10        <filter-name>encoding</filter-name>
11        <url-pattern>/*</url-pattern>
12    </filter-mapping>

同时在连接mysql数据库的时候也要改为jdbc:mysql://localhost:3306/mldn?useUnicode=true&amp;characterEncoding=utf-8 如果连接在java代码中使用,请使用&,如果是在xml中使用请写成&amp;

    关于mysql的collation字符集和mysql的characterSet字符集问题,查了很多资料都说要设置成一样,我想那只是针对如果你选GBK作为数据库编码的时候所用,但如果选用UTF8作为数据库编码的时候就不一定正确了。
    今天根据网上的资料将所有的characterSet设置成utf8(可用mysql> SHOW VARIABLES LIKE 'character_set_%';查看),一直在测试乱码问题解决没有。在测试过程发现传递的中文都是正确的,可就是在MySQL Client中查看的是乱码,所以继续找方法解决,但其实这个时候你插进去的中文已经是正常的了,只是在MySQL Client中显示是乱码。那么为什么会这样?MySQL Client设置成UTF8的时候中文不能正常显示,此时我们应该将charact_set_client :设置成gbk,这样就可以正常显示中文了。
    可以在my.ini中

[mysql]

default-character-set=gbk


而下面的default-character-set=utf8,两个default-character-set的设置是不一样的。当然如果你选用gbk作为数据库编码,是需要设置成一样的。
posted @ 2009-07-23 17:40 平涛 阅读(513) | 评论 (0)编辑 收藏

tomcat下jsp出现getOutputStream() has already been called for this response异常的原因和解决方法

在tomcat下jsp中出现此错误一般都是在jsp中使用了输出流(如输出图片验证码,文件下载等),
没有妥善处理好的原因。
具体的原因就是
在tomcat中jsp编译成servlet之后在函数_jspService(HttpServletRequest request, HttpServletResponse response)的最后
有一段这样的代码
finally {
      if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);
    }
这里是在释放在jsp中使用的对象,会调用response.getWriter(),因为这个方法是和
response.getOutputStream()相冲突的!所以会出现以上这个异常。

然后当然是要提出解决的办法,其实挺简单的(并不是和某些朋友说的那样--
将jsp内的所有空格和回车符号所有都删除掉),

在使用完输出流以后调用以下两行代码即可:
out.clear();
out = pageContext.pushBody();

最后这里是一个输出彩色验证码例子(这样的例子几乎随处可见)
imag.jsp

<%@ page  import="java.awt.*,java.awt.image.*,java.util.*,javax.imageio.*" %>
<%@ page import="java.io.OutputStream" %>
<%!
Color getRandColor(int fc,int bc){
Random random = new Random();
if(fc>255) fc=255;
if(bc>255) bc=255;
int r=fc+random.nextInt(bc-fc);
int g=fc+random.nextInt(bc-fc);
int b=fc+random.nextInt(bc-fc);
return new Color(r,g,b);
}
%>
<%
try{
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
int width=60, height=20;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
OutputStream os=response.getOutputStream();
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200,250));
g.fillRect(0, 0, width, height);

g.setFont(new Font("Times New Roman",Font.PLAIN,18));
g.setColor(getRandColor(160,200));
for (int i=0;i<155;i++)
{
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x,y,x+xl,y+yl);
}
String sRand="";
for (int i=0;i<4;i++){
String rand=String.valueOf(random.nextInt(10));
sRand+=rand;
g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));
g.drawString(rand,13*i+6,16);
}
session.setAttribute("rand",sRand);
g.dispose();

ImageIO.write(image, "JPEG",os);
os.flush();
os.close();
os=null;
response.flushBuffer();
out.clear();
out = pageContext.pushBody();
}
catch(IllegalStateException e)
{
System.out.println(e.getMessage());
e.printStackTrace();
}%>

posted @ 2009-07-23 08:40 平涛 阅读(463) | 评论 (0)编辑 收藏

2009年7月22日 #

这个错误主要是struts-config.xml或web.xml上配置出错
仔细对比后就可以解决
struts-config.xml
<controller
  processorClass="org.springframework.web.struts.DelegatingRequestProcessor">
 </controller>

  <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
    <set-property property="contextConfigLocation" value="/WEB-INF/classes/applicationContext.xml" />
  </plug-in>

web.xml
<context-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/classes/applicationContext.xml</param-value>
  </context-param>
 
  <servlet>
   <servlet-name>context</servlet-name>
   <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
   <load-on-startup>1</load-on-startup>
  </servlet>
主要是要保证contextConfigLocation名字没有错误即可。
仔细对比 要一字不差
posted @ 2009-07-22 23:15 平涛 阅读(284) | 评论 (0)编辑 收藏

     摘要:   二、Struts2和Spring2.5的整合 (1)在web.xml中加入Struts2过滤器 <filter>   <filter-name>struts2</filter-name> ...  阅读全文
posted @ 2009-07-22 17:02 平涛 阅读(1651) | 评论 (0)编辑 收藏

2009年7月20日 #

     摘要:   一、Srping 与 Hibnernate 的整合 (4)开发        这里采用自底向上的开发模式。        (a)底层的开发        打开DB Explorer,连接上数据库,...  阅读全文
posted @ 2009-07-20 22:04 平涛 阅读(1562) | 评论 (2)编辑 收藏

         使用的版本为:Struts 2.1.6 + Spring 2.5 + Hibernate 3.2

        开发环境为:MyEclipse 7.1.1 + Jdk 1.6.0_13 + Tomcat 6.0.18 + MySQL 6.0.10 alpha

       所用表:test.employee

 

id        int(11)       NO   PRI | NULL     auto_increment

name     varchar(
255) YES        NULL                   

address   varchar(
255)   YES      NULL    

phone    varchar(
255)   YES       NULL  

       这个实例主要是实现简单的CRUD方法。在搭建环境前,需要处理一个问题:因为Spring2.5 AOP Liberaries里的asm2.2.3.jarHiberate中的生成代理用的asm.jar冲突,我们需要删除asm2.2.3.jar,不然就会发生异常:java.lang.NoClassDefFoundError: org/objectweb/asm/CodeVisitor 。具体的删除方法(避免在Tomcat中的lib下删除了,然后又重新发布项目时此jar又重新发不到lib下):在MyEclipse中【WindowàPreferencesàMyEclipse Enterprise WorkbenchàProject CapabilitiesàSpring】在spring2.5 AOP Libraries中删除asm2.2.3.jar

       在开发S2SH架构时,先整合Spring + Hibernate ,然后再整合Struts + Spring 完成S2SH架构的搭建。

一、Srping Hibnernate 的整合

       1)添加Hibernate支持

       在添加Hibernate支持之前,我们先在【MyEclipse DataBase Explorer】中设置数据库连接。所用的mysql/jdbc的jar包为mysql-connector-java-5.1.8-bin.jar。


    注意:
MySQLURL为:jdbc:mysql://localhost:3306/test(我所根据学的视频案例是连接oracle的,根据视频中所学的时候,url没有添加数据库,在测试过程当中怎么样也找到实体,原因就是因为在url中没有添加数据库的选择。)

       后面按照默认设置一直到【finish】即可。

       a)在导航中找到【MyEclipse】,然后选择【Add Hibernate Capbilities

这里可以直接选择jar包拷贝到lib下面,也可以先不拷贝,到时候MyEclipse会自动拷贝到lib下。

b)由于Hibernate要与Spring整合,所以在这一步无需再配置更详细的hibernate.cfg.xml

       在这个按列中,我们使用JDBC Driver模式。

c)不创建sessionFactory。然后【finish


2)添加Spring支持

a)在导航中找到【MyEclipse】,然后选择【Add Spring Capbilities

添加5个类库。

b)生成applicationContext.xml,建议将其放在WEB-INF下面。

c)创建sessionFactory。然后【finish

3)整合SpringHibernate

打开applicationContext,在当中增加DataSourceSessionFactory

<bean id="sessionFactory"

    class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

       <property name="configLocation"

           value="classpath:hibernate.cfg.xml">

       </property>

</bean>

先将原先自动生成的上述代码删除。

然后再Outline的导航中选择【New DataSource and SessionFactory

后面要用到Mapping Resources,所以你可以在这里随便填一个值进去。

最后添加部分属性(添加了
hibernate.show_sql)后形成代码如下:

<bean id="dataSource"

       class="org.apache.commons.dbcp.BasicDataSource">

       <property name="driverClassName"

           value="com.mysql.jdbc.Driver">

       </property>

       <property name="url" value="jdbc:mysql://localhost:3306/test"></property>

       <property name="username" value="root"></property>

       <property name="password" value="123"></property>

    </bean>

    <bean id="sessionFactory"

        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

       <property name="dataSource">

           <ref bean="dataSource" />

       </property>

       <property name="hibernateProperties">

           <props>

              <prop key="hibernate.dialect">

                  org.hibernate.dialect.MySQLDialect

              </prop>

              <prop key="hibernate.show_sql">true</prop>

           </props>

       </property>

       <property name="mappingDirectoryLocations">

           <list>

              <value>file:src</value>

           </list>

       </property>

    </bean>

自此已经完成了SpringHibernate的整合。

posted @ 2009-07-20 11:17 平涛 阅读(5609) | 评论 (1)编辑 收藏

仅列出标题  下一页