最近公司項(xiàng)目開發(fā)中遇到的一個(gè)問(wèn)題,整理一下,和大家分享。
驗(yàn)證碼無(wú)法顯示的問(wèn)題,驗(yàn)證碼的代碼就是google上查找到的最常見(jiàn)的代碼,服務(wù)器采用resin部署于linux或unix。不是常見(jiàn)的out.clear()問(wèn)題,這次的問(wèn)題發(fā)現(xiàn)在一個(gè)我壓根就沒(méi)有想到的地方,profile DISPLAY 環(huán)境變量。
1) 問(wèn)題描述:
登錄頁(yè)面等有驗(yàn)證瑪顯示的頁(yè)面,通常可以正確顯示驗(yàn)證碼圖片,但是在某些情況下發(fā)現(xiàn)驗(yàn)證碼圖片無(wú)法顯示,并且目前只發(fā)生在linux/unix平臺(tái),windows下正常.而且和resin/jdk版本無(wú)關(guān).
bug的直接表現(xiàn)是表現(xiàn)為ie下是紅叉,firefox下無(wú)實(shí)現(xiàn).將驗(yàn)證碼圖片的地址在ie輸入框中輸入,則頁(yè)面報(bào)錯(cuò):
代碼
- 500 Servlet Exception
- java.lang.NoClassDefFoundError
- at java.lang.Class.forName0(Native Method)
- at java.lang.Class.forName(Class.java:164)
- at java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(GraphicsEnvironment.java:68)
- at java.awt.image.BufferedImage.createGraphics(BufferedImage.java:1141)
- at com.asiainfo.aimc.wmail.action.CreateImageServlet.doGet(CreateImageServlet.java:104)
這里的java.lang.NoClassDefFoundError 極其誤導(dǎo)人,一直以為是CLASSPATH或者jar包的問(wèn)題,所以反復(fù)檢查resin和jdk版本。
始終無(wú)法找到問(wèn)題,只好嘗試追查jdk源碼,看到底發(fā)生了什么。
2) jdk源碼追查
調(diào)用的servlet:
BufferedImage bi = new BufferedImage(...)
Graphics2D g = bi.createGraphics();
查jdk: BufferedImage.createGraphics():
GraphicsEnvironment env =
GraphicsEnvironment.getLocalGraphicsEnvironment();
再查GraphicsEnvironment.getLocalGraphicsEnvironment:
String nm = (String) java.security.AccessController.doPrivileged
(new sun.security.action.GetPropertyAction
("java.awt.graphicsenv", null));
......
localEnv = GraphicsEnvironment) Class.forName(nm).newInstance();
......
問(wèn)題應(yīng)該和nm有關(guān),這里明顯是一個(gè)類似工廠模式的設(shè)計(jì),"java.awt.graphicsenv"到nm 然后Class.forName() 生成GraphicsEnvironment對(duì)象。
由于代碼在jdk中,不方便修改,因此單獨(dú)將這些代碼提出來(lái)到簡(jiǎn)單的測(cè)試類 Test.java:
3) 測(cè)試代碼分析
代碼
- public class Test {
- public static void main(String[] args) {
- String nm = (String) java.security.AccessController.doPrivileged
- (new sun.security.action.GetPropertyAction
- ("java.awt.graphicsenv", null));
-
- System.out.println(nm);
-
- try {
- Class.forName(nm).newInstance();
- } catch (Throwable e) {
- System.out.println("error=" + e.getClass().getName());
-
- e.printStackTrace();
- }
- }
- }
在windows平臺(tái)下運(yùn)行,結(jié)果正常,打印:
sun.awt.Win32GraphicsEnvironment
將代碼放到出問(wèn)題的resin安裝所在的linux平臺(tái),手工編譯運(yùn)行:
javac Test.java
java -cp . Test
報(bào)錯(cuò),打印為:
代碼
- sun.awt.X11GraphicsEnvironment
- Throwable=java.lang.InternalError
- java.lang.InternalError: Can't connect to X11 window server using '10.3.18.16' as the value of the DISPLAY variable.
- at sun.awt.X11GraphicsEnvironment.initDisplay(Native Method)
- at sun.awt.X11GraphicsEnvironment.access$000(X11GraphicsEnvironment.java:53)
- at sun.awt.X11GraphicsEnvironment$1.run(X11GraphicsEnvironment.java:142)
- at java.security.AccessController.doPrivileged(Native Method)
- at sun.awt.X11GraphicsEnvironment.<clinit>(X11GraphicsEnvironment.java:131)
- at java.lang.Class.forName0(Native Method)
- at java.lang.Class.forName(Class.java:164)
- at Test.main(Test.java:13)
從錯(cuò)誤信息" Can't connect to X11 window server using '10.3.18.16' as the value of the DISPLAY variable."來(lái)看,和DISPLAY環(huán)境變量有關(guān)
執(zhí)行unset再運(yùn)行可以發(fā)現(xiàn)問(wèn)題消失:
$> unset DISPLAY
$> java -cp . Test
sun.awt.X11GraphicsEnvironment
$>
在此情況下(unset DISPLAY )下重新啟動(dòng)resin,發(fā)現(xiàn)驗(yàn)證碼可以正常顯示。
4) 解決的方法:
必須保證resin運(yùn)行時(shí)DISPLAY
環(huán)境變量沒(méi)有設(shè)置,如果resin運(yùn)行的環(huán)境有其他要求必須使用DISPLAY,則可以在運(yùn)行resin前使用unset清除.
建議的簡(jiǎn)單而有效的方法是直接修改resin/bin/httpd.sh文件,在第二行(具體行數(shù)無(wú)所謂,但必須在最后一行前)插入:
#! /bin/sh
unset DISPLAY
#....
5)疑惑
1. Can't connect to X11 window server using '10.3.18.16' as the value of the DISPLAY variable
為什么要去連X11 window server ?不懂
2. 從Test.java運(yùn)行看拋出的是Error :
java.lang.InternalError,但是頁(yè)面上顯示的是java.lang.NoClassDefFoundError,看了看源代碼也沒(méi)
有發(fā)現(xiàn)先catch 后throws的錯(cuò)誤處理,不清楚這里的具體處