什么?编译了三个版本Tomcat源码后,我才发现这个bug

背景

一产品是基于多模块开发的 SpringBoot 项目,发布时导出多个 war 包部署在同一个 Tomcat 。模块有五六个,发布时最大的问题就是每个包都很大,主要是各个模块的 WEB-INF/lib 下包含大量相同 jar ,因此有必要将公共包摘出来放到 Tomcat 的共享目录下。

然而,在捣鼓了两天后,我对 Tomcat 的多应用部署时共享公共包的能力产生了怀疑。理论上,同一个 Tomcat 下部署多个应用时,可以将所有共享 jar 放在 shared/lib 目录下,然后配置 shared.loader 就可以了。

实践的时候,抽取了公共包后,多个 war 部署时始终报错, WEB-INF/lib 下明明有对应的 Spring 框架包,还是报 Caused by: java.lang.NoClassDefFoundError: org/springframework/beans/factory/FactoryBean 异常,反复测试,还是无法确定哪些包应该作为公共包。

所以,决定从源头来搞明白这个类加载过程,在编译的 Tomcat 版本下断点跟踪一下到底是怎么回事儿。第一件事儿,先编译 Tomcat 源码。

Tomcat 源码编译

Tomcat 源码导入 IDEA 的过程比较简单,步骤为:

1.下载 ANT 工具,最新版本为 1.10.9,配置环境变量 ANT_HOME:

什么?编译了三个版本Tomcat源码后,我才发现这个bug

 

2.下载 tomcat 源码,根据操作系统选择 zip 或者 tar.gz 文件;

3.进入源码目录,使用 ant 命令编译源码;

什么?编译了三个版本Tomcat源码后,我才发现这个bug_第1张图片

 

此操作耗时较长,耐心等待编译完成:

什么?编译了三个版本Tomcat源码后,我才发现这个bug_第2张图片

 

4. 创建一个 catalina-home 目录,将 output/build 目录下的所有文件拷贝到 catalina-home 目录下,将其作为 Tomcat 的工作目录:

什么?编译了三个版本Tomcat源码后,我才发现这个bug_第3张图片

 

5.进入源码目录,创建 pom.xml 添加依赖:



 
    4.0.0
    org.apache.tomcat
    Tomcat8.0
    Tomcat8.0
    8.0
 
    
        Tomcat8.0
        java
        
        
            
                java
            
        
        
            
                
            
        
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.1
                
                    UTF-8
                    1.7
                    1.7
                
            
        
    
 
    
        
            junit
            junit
            4.12
            test
        
        
            org.easymock
            easymock
            3.4
        
        
            ant
            ant
            1.7.0
        
        
            wsdl4j
            wsdl4j
            1.6.2
        
        
            javax.xml
            jaxrpc
            1.1
        
        
            org.eclipse.jdt.core.compiler
            ecj
            4.5.1
        
    

6.导入 IDEA 源码,配置启动参数 -Dcatalina.home="F:\Study\TomcateStudy\2021Tomcat\catalina-home" 。

总的来说,编译源码流程比较简单,但最开始因为随便选择了最新版本,导致无法启动,这可能是官网版本发布时的缺陷吧,反正换个版本就没有问题。

选错版本的问题

首先,在 MAC 操作系统下,随便选择了最新版本 tomcat8.5.63 的 tar.gz 版本,下载后,编译时报错。先报 @Version@ 校验错误:

Error:osgi: [Tomcat8.5] Invalid value for Bundle-Version, @VERSION@ does not

修改为一个特定值后,又报下面的错误:

什么?编译了三个版本Tomcat源码后,我才发现这个bug_第4张图片

 

最初怀疑是 IDEA 配置问题,于是又在 windows 操作系统下,还是用最新版本的 tomcat8.5.63 的 zip 版本下载源码,配置参数如下:

什么?编译了三个版本Tomcat源码后,我才发现这个bug_第5张图片

 

奇怪的是,它能够编译通过、正常运行:

什么?编译了三个版本Tomcat源码后,我才发现这个bug_第6张图片

 

结论:Tomcat8.5.63 版本的 tar.gz 源码的 jdbc-pool 模块打包生成的 MANIFEST.MF 文件有问题,编译源码的时候应该避免使用该版本。本来以为哪个版本都一样,就随便选了最新版本,结果就踩坑了。

最后,更换为 Tomcat8.5.59 版本,MAC 下就能正确启动了。

什么?编译了三个版本Tomcat源码后,我才发现这个bug_第7张图片

 

控制台乱码问题

最后解决控制台乱码问题,主要涉及到两个类,它们使用了默认编码导致输出乱码的。具体操作如下,详细参考:

第一步,修改 org.apache.tomcat.util.res.StringManager 类的 getString(final String key, final Object... args) 方法,获得 value 后再转码:

try{
     value = new String(value.getBytes("ISO-8859-1"),"UTF-8");
 }catch (Exception e ){
     e.printStackTrace();
 }

第二步,修改 org.apache.jasper.compiler.Localizer 类的 getMessage(String errCode) 方法,在返回之前进行转码:

 try{
     errMsg = new String(errMsg.getBytes("ISO-8859-1"),"UTF-8");
 }catch (Exception e ){
     e.printStackTrace();
 }

再重启 Tomcat 应用,输出信息就正常了:

什么?编译了三个版本Tomcat源码后,我才发现这个bug_第8张图片

 

启示录

到现在为止,还没找到答案的几个问题是:

  1. 多应用共享 lib 的原则是什么?
  2. 哪些包应该放在 shared/lib 下?【我是将纯第三方 jar ,无相关依赖的】
  3. 为什么只 WEB-INF/lib 下只保留某个应用自身的 jar 而且包含 SpringBoot 的 starter 依赖,还是报类异常?

你可能感兴趣的:(Java,程序员,面试,java,maven,spring,linux,tomcat)