Linux/Unix + RESIN/Tomcat 验证码无法显示的问题(ZT)

最近公司项目开发中遇到的一个问题,整理一下,和大家分享。

验证码无法显示的问题,验证码的代码就是google上查找到的最常见的代码,服务器采用resin部署于linux或unix。不是常见的out.clear()问题,这次的问题发现在一个我压根就没有想到的地方,profileDISPLAY环境变量。

1)问题描述:

登录页面等有验证玛显示的页面,通常可以正确显示验证码图片,但是在某些情况下发现验证码图片无法显示,并且目前只发生在linux/unix平台,windows下正常.而且和resin/jdk版本无关.

    bug的直接表现是表现为ie下是红叉,firefox下无实现.将验证码图片的地址在ie输入框中输入,则页面报错:
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)  
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极其误导人,一直以为是CLASSPATH或者jar包的问题,所以反复检查resin和jdk版本。

始终无法找到问题,只好尝试追查jdk源码,看到底发生了什么。

2)jdk源码追查

调用的servlet:

BufferedImagebi=newBufferedImage(...)

Graphics2Dg=bi.createGraphics();

查jdk:BufferedImage.createGraphics():

GraphicsEnvironmentenv=

GraphicsEnvironment.getLocalGraphicsEnvironment();

再查GraphicsEnvironment.getLocalGraphicsEnvironment:

Stringnm=(String)java.security.AccessController.doPrivileged

(newsun.security.action.GetPropertyAction

("java.awt.graphicsenv",null));

......

localEnv=GraphicsEnvironment)Class.forName(nm).newInstance();

......

问题应该和nm有关,这里明显是一个类似工厂模式的设计,"java.awt.graphicsenv"到nm然后Class.forName()生成GraphicsEnvironment对象。

由于代码在jdk中,不方便修改,因此单独将这些代码提出来到简单的测试类Test.java:

3) 测试代码分析
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();   
        }    
    }   
}  
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平台下运行,结果正常,打印:

sun.awt.Win32GraphicsEnvironment

将代码放到出问题的resin安装所在的linux平台,手工编译运行:

javacTest.java

java-cp.Test

    报错,打印为:
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)  
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)

从错误信息"Can'tconnecttoX11windowserverusing'10.3.18.16'asthevalueoftheDISPLAYvariable."来看,和DISPLAY环境变量有关

执行unset再运行可以发现问题消失:

$>unsetDISPLAY

$>java-cp.Test

sun.awt.X11GraphicsEnvironment

$>

在此情况下(unsetDISPLAY)下重新启动resin,发现验证码可以正常显示。

4)解决的方法:

必须保证resin运行时DISPLAY环境变量没有设置,如果resin运行的环境有其他要求必须使用DISPLAY,则可以在运行resin前使用unset清除.建议的简单而有效的方法是直接修改resin/bin/httpd.sh文件,在第二行(具体行数无所谓,但必须在最后一行前)插入:

#!/bin/sh

unsetDISPLAY

#....

5)疑惑

1.Can'tconnecttoX11windowserverusing'10.3.18.16'asthevalueoftheDISPLAYvariable

为什么要去连X11windowserver?不懂

2. 从Test.java运行看抛出的是Error : java.lang.InternalError,但是页面上显示的是java.lang.NoClassDefFoundError,看了看源代码也没有发现先catch 后throws的错误处理,不清楚这里的具体处理,不方便继续追查,作罢。

方案二:

Linux环境下对java图片类的支持不够,需要添加如下配置、方可加载图片环境:(或置于/etc下profile 配置文件中 /etc/profile文件)

tomcat下bin的启动文件,startup.sh中修改添加如下: export CATALINA_OPTS="-Djava.awt.headless=true"即可。

相关推荐