关于Tomcat源码启动的乱码问题的解决

前提

image-20210529181350326.png
今天在学习Tomcat的时候,使用源码的方式启动Tomcat,但是控制台打印出来的信息是乱码,.作为一个Java开发,对于乱码应该是不陌生的,所以一开始我就去和往常一样修改配置文件,但是发现不管是GBK还是UTF-8,控制台打印的信息都是乱码,所以我就去网上寻找相关的经验和博客,在一番寻找之后,我找到了一个还不错的解决方法,但是依旧有一些问题,最终经历一番寻找终于解决了问题

第一次解决尝试

记一次tomcat源码启动控制台中文乱码问题调试过程

一开始我跟着博主的第二个方法,去修改了两个类中的方法

  • 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);
复制代码

image-20210529181625683.png

image-20210529181701706.png

然后再进行重启之后发现,控制台的信息的确变得正确了

image-20210529181807568.png

第二次解决尝试

但是在之后打开Tomcat工程自带的host-manager项目和manager项目时,页面再一次出现了乱码

image-20210529181929292.png

image-20210529181944067.png

随后为了搞清楚为什么会出现乱码问题,就开始了漫长的调试过程

为什么会有乱码问题?

  • 首先我在乱码的控制台找到一个乱码输出的类,然后点进去,找到输出的部分

image-20210529184415558.png

  • 再次进入调用的这个getString方法

image-20210529184538132.png

发现来到了之前博主修改的那个方法,然后根据逻辑,再继续跟踪字符串的来源

image-20210529184725296.png

然后继续点击下一层源码

image-20210529184814379.png

从这里可以发现调用的是bundlegetString方法,那么让我们接着往里走

image-20210529184911385.png
可以看到这里进入了jdk的源码部分,仔细观察左上角,顶级目录是rt.jar,这个就是由Bootstrap ClassLoader加载的源码部分,接着再往里走

image-20210529185105986.png

可以发现是一个本类的getObject方法,其中关键是handleGetObject方法,那么接着往里面走

image-20210529185425121.png

可以发现具体调用的是PropertyResourceBundlehandleGetObject方法,而这个方法的关键就是最后一个返回的lookup.get(key),那么这个lookup又是什么呢?

image-20210529185615047.png

通过定义和debug出来的内容,不难猜出里面存放的就是要获取的字符串信息,可以看到在这里我们这个map中存放的信息就已经是乱码的了,所以当我们根据key获取字符串信息时获取到的本身就是一个乱码字符串

image-20210529190733219.png

而之前我们尝试的方法,就是在从中拿到乱码信息之后,对字符串使用UTF-8格式进行重新编码,我猜测,之所以控制台信息不乱码但是host-manager和manager依旧乱码,应该是在其他地方还有使用这个StringManager,但是没有进行手动的重新编码,所以我就想能不能让这个looup中存放的字符串本身就是正确的,不是乱码,而这个信息又是由ResourceBoundle进行加载的,随后又是一番debug,发现加载的过程中依赖一个control

image-20210529193927181.png

image-20210529193952378.png

image-20210529194017390.png

image-20210529194032944.png

可以看到,我们的bundle最后是由control的newBundle方法创建的

image-20210529194138073.png

image-20210529194215869.png

image-20210529194236968.png

没错,现在答案终于揭晓了,因为在创建bundle的过程中,源码中使用的时默认的InputStream,而InputStream默认是以ISO-8859-1格式读取的,我们的文件又是以UTF-8格式保存,所以会出现乱码问题,那么知道了问题所在,就好解决了,一开始我想着用包装器改变读取的格式

InputStreamReader isr = new InputStreamReader(stream, "UTF-8");

但是,后知后觉的发现这是jdk源码中的部分,是不允许修改的(之前追踪了这么久实在是有些糊涂了),那么是不是意味着我们就没有其他办法了呢?并不是,仔细看ResourceBundle的构造器部分

image-20210529194746953.png

没错,除了一个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工具

image-20210529192206124.png

但是全部转换很麻烦,文件也很多,我想不会有人使用这个方法的

第二个办法就是在创建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;
    }
}
复制代码

image-20210529202131474.png

让我们测试一下结果

image-20210529210732516.png

image-20210529210743382.png

image-20210529210757356.png

第三个办法就是使用相应的IDE,在Eclipse中,对于.properties文件的处理时,会将超出ISO-8859-1范围的字符自动转化为\uXXXX格式,所以在使用Eclipse的时候,你不需要进行任何的设置,就可以没有任何乱码的启动Tomcat!!!

image-20210529192610723.png

image-20210529192638554.png

可以看到,我现在是将之前的语句全部注释,让我们跑起来看看效果

image-20210529192712616.png

image-20210529192751377.png

image-20210529192813350.png

参考链接

java – How to use UTF-8 in resource properties with ResourceBundle – Stack Overflow

记一次tomcat源码启动控制台中文乱码问题调试过程_zhoutaoping1992的博客-CSDN博客

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享