什么?編譯了三個(gè)版本Tomcat源碼后,我才發(fā)現(xiàn)這個(gè)bug
背景
一產(chǎn)品是基于多模塊開(kāi)發(fā)的 SpringBoot 項(xiàng)目,發(fā)布時(shí)導(dǎo)出多個(gè) war 包部署在同一個(gè) Tomcat 。模塊有五六個(gè),發(fā)布時(shí)最大的問(wèn)題就是每個(gè)包都很大,主要是各個(gè)模塊的 WEB-INF/lib 下包含大量相同 jar ,因此有必要將公共包摘出來(lái)放到 Tomcat 的共享目錄下。
然而,在搗鼓了兩天后,我對(duì) Tomcat 的多應(yīng)用部署時(shí)共享公共包的能力產(chǎn)生了懷疑。理論上,同一個(gè) Tomcat 下部署多個(gè)應(yīng)用時(shí),可以將所有共享 jar 放在 shared/lib 目錄下,然后配置 shared.loader 就可以了。
實(shí)踐的時(shí)候,抽取了公共包后,多個(gè) war 部署時(shí)始終報(bào)錯(cuò), WEB-INF/lib 下明明有對(duì)應(yīng)的 Spring 框架包,還是報(bào) Caused by:
java.lang.NoClassDefFoundError: org/springframework/beans/factory/FactoryBean 異常,反復(fù)測(cè)試,還是無(wú)法確定哪些包應(yīng)該作為公共包。
所以,決定從源頭來(lái)搞明白這個(gè)類(lèi)加載過(guò)程,在編譯的 Tomcat 版本下斷點(diǎn)跟蹤一下到底是怎么回事兒。第一件事兒,先編譯 Tomcat 源碼。
Tomcat 源碼編譯
Tomcat 源碼導(dǎo)入 IDEA 的過(guò)程比較簡(jiǎn)單,步驟為:
1.下載 ANT 工具,最新版本為 1.10.9,配置環(huán)境變量 ANT_HOME:

2.下載 tomcat 源碼,根據(jù)操作系統(tǒng)選擇 zip 或者 tar.gz 文件;
3.進(jìn)入源碼目錄,使用 ant 命令編譯源碼;

此操作耗時(shí)較長(zhǎng),耐心等待編譯完成:

4. 創(chuàng)建一個(gè) catalina-home 目錄,將 output/build 目錄下的所有文件拷貝到 catalina-home 目錄下,將其作為 Tomcat 的工作目錄:

5.進(jìn)入源碼目錄,創(chuàng)建 pom.xml 添加依賴(lài):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.tomcat</groupId>
<artifactId>Tomcat8.0</artifactId>
<name>Tomcat8.0</name>
<version>8.0</version>
<build>
<finalName>Tomcat8.0</finalName>
<sourceDirectory>java</sourceDirectory>
<!--<testSourceDirectory>test</testSourceDirectory>-->
<resources>
<resource>
<directory>java</directory>
</resource>
</resources>
<!--<testResources>-->
<!--<testResource>-->
<!--<directory>test</directory>-->
<!--</testResource>-->
<!--</testResources>-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.5.1</version>
</dependency>
</dependencies>
</project>
6.導(dǎo)入 IDEA 源碼,配置啟動(dòng)參數(shù) -Dcatalina.home="F:\Study\TomcateStudy\2021Tomcat\catalina-home" 。
總的來(lái)說(shuō),編譯源碼流程比較簡(jiǎn)單,但最開(kāi)始因?yàn)殡S便選擇了最新版本,導(dǎo)致無(wú)法啟動(dòng),這可能是官網(wǎng)版本發(fā)布時(shí)的缺陷吧,反正換個(gè)版本就沒(méi)有問(wèn)題。
選錯(cuò)版本的問(wèn)題
首先,在 MAC 操作系統(tǒng)下,隨便選擇了最新版本 tomcat8.5.63 的 tar.gz 版本,下載后,編譯時(shí)報(bào)錯(cuò)。先報(bào) @Version@ 校驗(yàn)錯(cuò)誤:
Error:osgi: [Tomcat8.5] Invalid value for Bundle-Version, @VERSION@ does not修改為一個(gè)特定值后,又報(bào)下面的錯(cuò)誤:

最初懷疑是 IDEA 配置問(wèn)題,于是又在 windows 操作系統(tǒng)下,還是用最新版本的 tomcat8.5.63 的 zip 版本下載源碼,配置參數(shù)如下:

奇怪的是,它能夠編譯通過(guò)、正常運(yùn)行:

結(jié)論:Tomcat8.5.63 版本的 tar.gz 源碼的 jdbc-pool 模塊打包生成的 MANIFEST.MF 文件有問(wèn)題,編譯源碼的時(shí)候應(yīng)該避免使用該版本。本來(lái)以為哪個(gè)版本都一樣,就隨便選了最新版本,結(jié)果就踩坑了。
最后,更換為 Tomcat8.5.59 版本,MAC 下就能正確啟動(dòng)了。

控制臺(tái)亂碼問(wèn)題
最后解決控制臺(tái)亂碼問(wèn)題,主要涉及到兩個(gè)類(lèi),它們使用了默認(rèn)編碼導(dǎo)致輸出亂碼的。具體操作如下,詳細(xì)參考:
第一步,修改
org.apache.tomcat.util.res.StringManager 類(lèi)的 getString(final String key, final Object... args) 方法,獲得 value 后再轉(zhuǎn)碼:
try{
value = new String(value.getBytes("ISO-8859-1"),"UTF-8");
}catch (Exception e ){
e.printStackTrace();
}第二步,修改
org.apache.jasper.compiler.Localizer 類(lèi)的 getMessage(String errCode) 方法,在返回之前進(jìn)行轉(zhuǎn)碼:
try{
errMsg = new String(errMsg.getBytes("ISO-8859-1"),"UTF-8");
}catch (Exception e ){
e.printStackTrace();
}再重啟 Tomcat 應(yīng)用,輸出信息就正常了:

啟示錄
到現(xiàn)在為止,還沒(méi)找到答案的幾個(gè)問(wèn)題是:
多應(yīng)用共享 lib 的原則是什么?
哪些包應(yīng)該放在 shared/lib 下?【我是將純第三方 jar ,無(wú)相關(guān)依賴(lài)的】
為什么只 WEB-INF/lib 下只保留某個(gè)應(yīng)用自身的 jar 而且包含 SpringBoot 的 starter 依賴(lài),還是報(bào)類(lèi)異常?

騰訊、阿里、滴滴后臺(tái)面試題匯總總結(jié) — (含答案)
面試:史上最全多線程面試題 !
最新阿里內(nèi)推Java后端面試題
JVM難學(xué)?那是因?yàn)槟銢](méi)認(rèn)真看完這篇文章

關(guān)注作者微信公眾號(hào) —《JAVA爛豬皮》
了解更多java后端架構(gòu)知識(shí)以及最新面試寶典


看完本文記得給作者點(diǎn)贊+在看哦~~~大家的支持,是作者源源不斷出文的動(dòng)力
作者:畢小寶
鏈接:https://juejin.cn/post/6925741351971586055
來(lái)源:掘金
