前提
今天在学习Tomcat的时候,使用源码的方式启动Tomcat,但是控制台打印出来的信息是乱码,.作为一个Java开发,对于乱码应该是不陌生的,所以一开始我就去和往常一样修改配置文件,但是发现不管是GBK还是UTF-8,控制台打印的信息都是乱码,所以我就去网上寻找相关的经验和博客,在一番寻找之后,我找到了一个还不错的解决方法,但是依旧有一些问题,最终经历一番寻找终于解决了问题
第一次解决尝试
一开始我跟着博主的第二个方法,去修改了两个类中的方法
org.apache.tomcat.util.res.StringManager
类中的getString(final String key, final Object... args)
org.apache.jasper.compiler.Localizer
类的getMessage(String errCode)
在这两个类中相应的方法中添加了这么一句
value = new String(value.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
复制代码
然后再进行重启之后发现,控制台的信息的确变得正确了
第二次解决尝试
但是在之后打开Tomcat工程自带的host-manager项目和manager项目时,页面再一次出现了乱码
随后为了搞清楚为什么会出现乱码问题,就开始了漫长的调试过程
为什么会有乱码问题?
- 首先我在乱码的控制台找到一个乱码输出的类,然后点进去,找到输出的部分
- 再次进入调用的这个
getString
方法
发现来到了之前博主修改的那个方法,然后根据逻辑,再继续跟踪字符串的来源
然后继续点击下一层源码
从这里可以发现调用的是bundle
的getString
方法,那么让我们接着往里走
可以看到这里进入了jdk的源码部分,仔细观察左上角,顶级目录是rt.jar,这个就是由Bootstrap ClassLoader加载的源码部分,接着再往里走
可以发现是一个本类的getObject
方法,其中关键是handleGetObject
方法,那么接着往里面走
可以发现具体调用的是PropertyResourceBundle
的handleGetObject
方法,而这个方法的关键就是最后一个返回的lookup.get(key)
,那么这个lookup
又是什么呢?
通过定义和debug出来的内容,不难猜出里面存放的就是要获取的字符串信息,可以看到在这里我们这个map中存放的信息就已经是乱码的了,所以当我们根据key获取字符串信息时获取到的本身就是一个乱码字符串
而之前我们尝试的方法,就是在从中拿到乱码信息之后,对字符串使用UTF-8格式进行重新编码,我猜测,之所以控制台信息不乱码但是host-manager和manager依旧乱码,应该是在其他地方还有使用这个StringManager,但是没有进行手动的重新编码,所以我就想能不能让这个looup中存放的字符串本身就是正确的,不是乱码,而这个信息又是由ResourceBoundle进行加载的,随后又是一番debug,发现加载的过程中依赖一个control
可以看到,我们的bundle最后是由control的newBundle方法创建的
没错,现在答案终于揭晓了,因为在创建bundle的过程中,源码中使用的时默认的InputStream,而InputStream默认是以ISO-8859-1格式读取的,我们的文件又是以UTF-8格式保存,所以会出现乱码问题,那么知道了问题所在,就好解决了,一开始我想着用包装器改变读取的格式
InputStreamReader isr = new InputStreamReader(stream, "UTF-8");
但是,后知后觉的发现这是jdk源码中的部分,是不允许修改的(之前追踪了这么久实在是有些糊涂了),那么是不是意味着我们就没有其他办法了呢?并不是,仔细看ResourceBundle的构造器部分
没错,除了一个baseName的构造器之外,还有可以传入一个Control的构造器,又由于bundle的创建时调用Control的newBundle方法,所以我们只需要自己继承这个Control类然后重写newBundle方法,在重写的newBundle方法中,对InputStream使用InputStreamReader进行包装就可以了
我就又在网上搜索了相关的信息,在StackOverflow上找到了这么一篇回答
java – How to use UTF-8 in resource properties with ResourceBundle – Stack Overflow
这个人的回答给出了非常完整和详细的解释,并且也附上了完整的解决方案,原来读取乱码的原因是InputStream读取的时候默认是使用的ISO-8859-1格式,但是由于我使用的IDE是IDEA,同时默认的编码格式是UTF-8,所以在读取的时候就会由于格式不一致而显示乱码,问题找到了,那么该如何解决呢?
第一个办法就是将保存的文件中,超出ISO-8859-1编码范围的字符转为\uXXXX的格式,你可以使用jdk自带的native2ascii.exe工具
但是全部转换很麻烦,文件也很多,我想不会有人使用这个方法的
第二个办法就是在创建ResourceBundle的时候传入一个自定义的UTF8Control
让我们看一下博主提供给我们的现成的一个UTF8Control类
package org.apache.tomcat.util.res;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
public class UTF8Control extends ResourceBundle.Control {
//这里重写父类的newBundle方法
public ResourceBundle newBundle
(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
// The below is a copy of the default implementation.
//这里时默认的实现,c-v一下就好
String bundleName = toBundleName(baseName, locale);
String resourceName = toResourceName(bundleName, "properties");
ResourceBundle bundle = null;
InputStream stream = null;
if (reload) {
URL url = loader.getResource(resourceName);
if (url != null) {
URLConnection connection = url.openConnection();
if (connection != null) {
connection.setUseCaches(false);
stream = connection.getInputStream();
}
}
} else {
stream = loader.getResourceAsStream(resourceName);
}
if (stream != null) {
try {
// Only this line is changed to make it to read properties files as UTF-8.
//这里就是一个关键的地方,将原来的is使用UTF-8格式读取的InputStremReader包装起来
//这里就会默认按照UTF-8格式读取!!!
bundle = new PropertyResourceBundle(new InputStreamReader(stream, StandardCharsets.UTF_8));
} finally {
stream.close();
}
}
return bundle;
}
}
复制代码
让我们测试一下结果
第三个办法就是使用相应的IDE,在Eclipse中,对于.properties文件的处理时,会将超出ISO-8859-1范围的字符自动转化为\uXXXX格式,所以在使用Eclipse的时候,你不需要进行任何的设置,就可以没有任何乱码的启动Tomcat!!!
可以看到,我现在是将之前的语句全部注释,让我们跑起来看看效果
参考链接
java – How to use UTF-8 in resource properties with ResourceBundle – Stack Overflow