Tomcat6+proxool中文不能正常显示问题
汉字的显示处理是一个用java进行web应用开发很基础而又经常出来烦我们的问题,它永远有新的花样来摧残我们脆弱的神经。
近日给一个项目升级环境,由Tomcat4.1.31升级到Tomcat5.5或6.0,JDK也由1.4升级到1.6,经过一些简单修改一切都还算顺利(就是改了改代码中使用JDK6新增的保留字的问题,tomcat的兼容性当时看还是不错的,很顺利)。但在后来测试中发现我们用的proxool连接池的管理页面不能正常显示了,出现类似以下错误:
java.io.CharConversionException: Not an ISO 8859-1 character: 十
javax.servlet.ServletOutputStream.print(ServletOutputStream.java:89)
org.logicalcobwebs.proxool.admin.servlet.AdminServlet.printDefinitionEntry(AdminServlet.java:515)
org.logicalcobwebs.proxool.admin.servlet.AdminServlet.doSnapshot(AdminServlet.java:273)
org.logicalcobwebs.proxool.admin.servlet.AdminServlet.doStats(AdminServlet.java:145)
org.logicalcobwebs.proxool.admin.servlet.AdminServlet.doGet(AdminServlet.java:129)
javax.servlet.http.HttpServlet.service(HttpServlet.java:690)
javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
我参考了一下正常的情况,这里的“十”应该是当前的月份“十一月”三个字的第一个字。也就是说是当前输出不认识汉字,只能输出ISO 8859-1编码的字符(或者可以按某种规则被正常解析成该编码的字符,这种规则我们在后面可以看到)。
首先要明确,在原来的Tomcat4+JDK1.4的环境是没有这个问题的,那么问题就是新换的环境上了。
分析一下org.logicalcobwebs.proxool.admin.servlet.AdminServlet这个类不难看出这里看到的无法正常转换的汉字来自于以下这条语句:
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
这里使用了“MMM”来取本地化格式的月份,当然在中文系统中就得到了“xx月”字样了。虽然这样的格式会给代码的移植带来不必要的麻烦,而且实际输出的类似“20-十一月-2007 08:00:00”这种格式看起来也是不伦不类(所以本文提供的修改方案中建议把这种改掉),但这并不是本文讨论的问题的本源,不支持中文输出才是关键。
那我们再来看下异常栈,异常是在javax.servlet.ServletOutputStream类(抽象类javax.servlet.ServletOutputStream继承了java.io.OutputStream)的print(String s)方法中抛出来的,通过反编译工具可以很方便地看到这个类的原码(对于tomcat4一般这个类在common/lib/servlet.jar包中,tomcat6在servlet-api.jar包中)。我们可以看到print(String s)方法的以下代码段。
public void print(String s) throws IOException {
if (s==null) s="null";
int len = s.length();
for (int i = 0; i < len; i++) {
char c = s.charAt (i);
//
// XXX NOTE: This is clearly incorrect for many strings,
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
//
if ((c & 0xff00) != 0) { // high order byte must be zero
String errMsg = lStrings.getString("err.not_iso8859_1");
Object[] errArgs = new Object[1];
errArgs[0] = new Character(c);
errMsg = MessageFormat.format(errMsg, errArgs);
throw new CharConversionException(errMsg);
}
write (c);
}
}
这个if ((c & 0xff00) != 0)就是抛出异常的起因之一了,汉字一定是过不了这一关的。经比较tomcat4与tomcat6带的这个类的算法没区别,但为什么在tomcat4会没有抛异常出来呢?看来一定是tomcat4在这里做了什么手脚。即然javax.servlet.ServletOutputStream是个抽象类,我们实现运行的类一定不会是它了,而是它的子类,这个子类一定是最终被tomcat实现了。经过分析tomcat代码最终找到了真正的实现类。tomcat4使用了org.apache.coyote.tomcat4.CoyoteOutputStream继承ServletOutputStream类,而tomcat6使用了org.apache.catalina.connector.CoyoteOutputStream继承ServletOutputStream类。从名字我们就能看出,前者十之八九是由后者演变而来的(coyote包中的一些类从tomcat5开始就不在org.apache.coyote.tomcatx包中了,而被放到了org.apache.catalina.connector包中)。
分析org.apache.coyote.tomcat4.CoyoteOutputStream类,我们可以看到一个方法如下:
public void print(String s) throws IOException
{
ob.write(s);
}
而在org.apache.catalina.connector.CoyoteOutputStream类中没有的这个方法。
覆盖了这个方法,也就意味着那个恼人的“if ((c & 0xff00) != 0)”就不会被执行到了。也就是说tomcat6新的CoyoteOutputStream类去掉这个了方法,这才是在tomcat4中可以但换成tomcat6就出问题的根本原因。当然tomcat6不再覆盖print(String s)方法也是有道理的,就是让我们使用Writer而不再使用OutputStream来输出HTML或XML之类的内容。
这样看来使用OutputStream的print(String s)方法输出是不应该再用了,那如何输出呢?
我们先来分析一下,OutputStream是输出二进制流了,也就是处理byte流的,而汉字明显是要用char类型来存储处理的,那么自然要用对应的Writer来进行输出操作了。幸好javax.servlet.http.HttpServletResponse类早就支持Writer输出了,但新问题是在tomcat的不同版本中的实现(如果有的话)会不会像OutputStream那样有差异进而导致此种问题或其它问题呢?这个答案还是要分析了不同版本的tomcat的代码才能晓得了。
首先,容易知道使用response.getWriter()得到的是一个java.io.PrintWriter类。经分析,在tomcat4中最终使用的是org.apache.catalina.connector.ResponseWriter类,而在tomcat6中最终使用的是org.apache.catalina.connector.CoyoteWriter类。两个类的getWriter()方法分别如下:
//tomcat4,org.apache.catalina.connector.ResponseWriter
public PrintWriter getWriter() throws IOException
{
if(writer != null)
return writer;
if(stream != null)
{
throw new IllegalStateException(sm.getString("responseBase.getWriter.ise"));
} else
{
ResponseStream newStream = (ResponseStream)createOutputStream();
newStream.setCommit(false);
OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding());
writer = new ResponseWriter(osr, newStream);
stream = newStream;
return writer;
}
}
//tomcat6,org.apache.catalina.connector.CoyoteWriter
public PrintWriter getWriter()
throws IOException {
if (usingOutputStream)
throw new IllegalStateException
(sm.getString("coyoteResponse.getWriter.ise"));
if (Globals.STRICT_SERVLET_COMPLIANCE) {
/*
* If the response's character encoding has not been specified as
* described in <code>getCharacterEncoding</code> (i.e., the method
* just returns the default value <code>ISO-8859-1</code>),
* <code>getWriter</code> updates it to <code>ISO-8859-1</code>
* (with the effect that a subsequent call to getContentType() will
* include a charset=ISO-8859-1 component which will also be
* reflected in the Content-Type response header, thereby satisfying
* the Servlet spec requirement that containers must communicate the
* character encoding used for the servlet response's writer to the
* client).
*/
setCharacterEncoding(getCharacterEncoding());
}
usingWriter = true;
outputBuffer.checkConverter();
if (writer == null) {
writer = new CoyoteWriter(outputBuffer);
}
return writer;
}
对比后我们看到,二者虽然代码差异很大,其中tomcat6的实现多了outputBuffer.checkConverter();这一行。其实这里只是进行从char到byte的转换,而对于汉字只要字符编码设置无误就不存在问题的(tomcat在这里有较复杂的处理,目的在于兼容JDK1.1,而不是使用nio)。字符编码的设置方法是类似“response.setContentType("text/html;charset=GBK");”这样的代码行。
问题都排除了,那么得出的结论就是二者的Writer输出是一致的,完全可以使用Writer来代替StreamOutput进行输出了。
既然找到问题产生的根本原因和处理方法,那么下一步就是着手解决它了。我们基本的改进方案是只改进org.logicalcobwebs.proxool.admin.servlet.AdminServlet类,核心是让不再使用OutputStream输出HTML而是用Writer。
具体步骤大致如下:
1、改掉憋脚的日期格式,由
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
替换为:
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
2、在doGet方法的“response.setHeader("Pragma", "no-cache");”一行后加上以下两行:
response.setLocale(new Locale((String)System.getProperties().get("user.language"), (String)System.getProperties().get("user.country")));
response.setContentType("text/html;charset=" + System.getProperties().get("file.encoding"));
要注意的是这里的字符集设置是依赖于当前操作系统环境的,也就是说,当前操作系统设的是GBK,这里才会被设置为GBK。如果认为没必要的话那么直接写死成"text/html;charset=GBK"即可,locale可直接使用Locale.SIMPLIFIED_CHINESE即可;
3、将所有private void doXxx(StreamOutput out, ...)方法的第一个参数的类型都换成java.io.PrintWriter。再把doGet方法中的一些response.getOutputStream()语句换成response.getWriter(),一切搞定;
4、还要将doGet方法中几处response.getStreamOutput()都替换为response.getWriter();
5、编译后将生成的新的org.logicalcobwebs.proxool.admin.servlet.AdminServlet类替换掉proxool.jar包中原有的,大功告成。
总结一下,这个看似简单的汉字显示问题,其背景还是有些复杂的。尤其是对一个J2EE服务器软件(当然tomcat比较weblogic之类的真正的J2EE服务器还是要简单很多的)进行代码分析还是颇耗精力的。尽管付出了一些辛苦,但得到一个让我们满意又有小小激动的结果,那就是,在java中通常我们可以完美地从本质上处理一些问题,无论是对于服务器还是第三方插件。可见,开源带来的可用代码虽然可谓浩如烟海,但只要我们是个训练有素的水手,再架上一叶可顺风而破浪的轻舟,自然可畅游其间,快乐地探寻那些静静地等待着的彼岸。
分享到:
相关推荐
修复proxool-0.9.1.jar显示中文报错(java.io.CharConversionException: Not an ISO 8859-1 character:)的bug。
proxool, 纠正了snapshot中文显示的错误
修复proxool-0.9.1.jar显示中文报错(java.io.CharConversionException: Not an ISO 8859-1 character:)的bug。
具体是对org.logicalcobwebs.proxool.admin.servlet.AdminServlet类。发现果然没有做response的字符设置。 在doGet(HttpServletRequest request, HttpServletResponse response)方法里加入了一句response....
标题中的"proxool-0.9.1.jar"和"proxool-cglib.jar"是两个重要的Java档案文件,它们是Proxool项目的组件。Proxool是一个开源的数据库连接池解决方案,它允许开发者在Java应用程序中高效地管理数据库连接。数据库连接...
Proxool 是一个轻量级、高效的开源 Java 数据库连接池,它提供了一种灵活的方式来管理和控制数据库连接。在这个主题中,我们将深入理解 Proxool 的核心概念、配置选项以及如何在实际项目中应用它。 首先,了解 ...
proxool数据库连接池
Proxool是一个开源的数据库连接池实现,它提供了一种灵活的方式来管理和控制数据库连接。在使用Proxool时,理解其配置参数是至关重要的,这些参数可以调整连接池的行为,以适应不同应用场景的需求。以下是一些主要的...
Oracle Proxool是一款开源的数据库连接池,它提供了一种高效、灵活的方式来管理数据库连接,以提高Java应用程序的性能和可扩展性。在Java应用程序中,数据库连接的创建和关闭是一个耗时的操作,通过使用连接池,我们...
标题中的"proxool-0.9.1-修正版 houseKeepingSleepTime 不能赋值"涉及到的是Proxool的一个特定配置参数及其修复的问题。 Proxool是一个Java数据库连接池实现,它提供了一种灵活、高效的方式来管理数据库连接,以...
总之,Proxool为Java项目提供了一种简单而有效的数据库连接池解决方案,通过合理配置和使用,可以显著提升应用程序的数据库操作性能,同时降低资源消耗。在非Web环境中,开发者可以直接在应用程序中集成Proxool,以...
1. **添加依赖**:在项目中引入Proxool的JAR文件(即proxool-0.9.1.jar),通常将其放置在项目的类路径中或Maven/Gradle的依赖库中。 2. **配置Proxool**:在项目的配置文件(如hibernate.cfg.xml)中,我们需要...
而"proxool-cglib.jar"则可能包含了CGLIB(Code Generation Library),这是一个强大的代码生成库,常用于Java的动态代理和类增强,Proxool可能使用CGLIB来实现对数据库驱动的动态代理,以便监控和控制数据库操作。...
在Proxool中,CGLIB可能被用来创建代理类,以便在不修改原始代码的情况下扩展或监控数据库连接的行为。 2. **proxool-0.9.1.jar**:这是Proxool的主要核心库文件,包含了所有处理数据库连接池功能的类和接口。版本...
Proxool的这个版本可能已经针对这个问题进行了优化,能够正确处理中文字符,确保在连接池中的数据传输和显示无误。 Proxool的工作原理包括以下几个关键点: 1. **连接池管理**:Proxool维护一个数据库连接池,预先...
整合`Proxool`和`Mybatis`的目的是为了让`Mybatis`在没有`Spring`的情况下也能利用`Proxool`提供的连接池服务,从而提升数据访问的效率。 以下是整合`Proxool`与`Mybatis`的基本步骤: 1. 添加依赖:在项目的`pom....
Proxool是一个开源的数据库连接池实现,它提供了一种高效、灵活的方式来管理和控制数据库连接。在Java应用程序中,使用连接池可以显著提高性能,尤其是在高并发环境下,通过复用已存在的数据库连接,避免了频繁创建...
1. **引入依赖**:在项目中使用Spring Proxool,首先需要在项目的类路径下添加对应的jar包,或者在Maven或Gradle的依赖管理中引入Proxool和Spring的相关依赖。 2. **配置Proxool属性**:在Spring的配置文件(如`...
将这两个库添加到项目的类路径中,确保Proxool能够正常工作。 接下来是Spring的配置。Spring通过`beans.xml`文件来管理bean的定义和依赖关系。我们需要在`beans.xml`中定义一个数据源bean,如下所示: ```xml ...
Proxool是一个开源的、轻量级的Java数据库连接池实现,它提供了一种高效、灵活的方式来管理数据库连接。在某些场景下,为了保护敏感信息,如数据库的用户名和密码,我们需要对这些数据进行加密处理。"proxool连接池...