源码获取

克隆项目:git clone https://github.com/apache/tomcat.git

cd tomcat

切换到9.0.x分支:git checkout -b 9.0.x origin/9.0.x

准备工作

tomcat改造成maven项目。

在源码根目录创建pom.xml文件,初始内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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>tomcat9</artifactId>
<version>9.0.x</version>

<name>tomcat9</name>
<description>Apache Tomcat 9.0.x</description>

<dependencies>
</dependencies>

<build>
<finalName>tomcat9</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

使用IDEA导入

必要设置

选择File->Project Structure

设置Project:设置JDK

设置IDEA的Project

设置Modules:标记Sources(源码包目录)和Tests(单元测试目录)。

设置IDEA的Modules

build(编译)源码

选择:Build->Build Project。(或者点击工具栏上的绿色小锤;Mac的快捷键是command+F9

接下来会看到报很多很多的错误,一个一个解决就好了。基本解决思路是:“缺啥补啥”。

解决报错

报错:程序包aQute.bnd.annotation.spi不存在

错误:

1
2
/tomcat/java/org/apache/juli/logging/LogFactory.java:24:32
java: 程序包aQute.bnd.annotation.spi不存在

解决:在pom.xml中添加以下依赖

1
2
3
4
5
6
<dependency>
<groupId>biz.aQute.bnd</groupId>
<artifactId>biz.aQute.bndlib</artifactId>
<version>5.2.0</version>
<scope>provided</scope>
</dependency>

报错:程序包org.apache.tools.ant不存在

错误:

1
2
/tomcat/java/org/apache/catalina/ant/JKStatusUpdateTask.java:22:28
java: 程序包org.apache.tools.ant不存在

解决:在pom.xml中添加以下依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.10.5</version>
</dependency>

报错:程序包javax.wsdl不存在

错误:

1
2
/tomcat/java/org/apache/naming/factory/webservices/ServiceRefFactory.java:37:18
java: 程序包javax.wsdl不存在

解决:在pom.xml中添加以下依赖

1
2
3
4
5
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.3</version>
</dependency>

报错:程序包javax.xml.rpc不存在

错误:

1
2
/tomcat/java/org/apache/naming/factory/webservices/ServiceRefFactory.java:44:21
java: 程序包javax.xml.rpc不存在

解决:在pom.xml中添加以下依赖

1
2
3
4
5
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>

报错:程序包org.eclipse.jdt.core.compiler不存在

错误:

1
2
/tomcat/java/org/apache/jasper/compiler/JDTCompiler.java:40:37
java: 程序包org.eclipse.jdt.core.compiler不存在

解决:在pom.xml中添加以下依赖

1
2
3
4
5
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.6.1</version>
</dependency>

重新编译后会发现以下问题:

1
2
3
4
/tomcat/java/org/apache/jasper/compiler/JDTCompiler.java:299:76
java: 找不到符号
符号: 变量 VERSION_9
位置: 类 org.eclipse.jdt.internal.compiler.impl.CompilerOptions

解决:更换ecjmaven依赖为以下

1
2
3
4
5
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>ecj</artifactId>
<version>3.26.0</version>
</dependency>

接下来是和test有关的报错。有两种处理方式,比较简单粗暴的是直接删除/tomcat/test整个目录;当然也可以按照下文的步骤一个个地去解决。

报错:程序包org.junit不存在

错误:

1
2
/tomcat/test/util/TestCookieFilter.java:19:17
java: 程序包org.junit不存在

解决:在pom.xml中添加以下依赖

1
2
3
4
5
6
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>

报错:程序包org.easymock不存在

错误:

1
2
/tomcat/test/org/apache/catalina/filters/TestRestCsrfPreventionFilter.java:32:20
java: 程序包org.easymock不存在

解决:在pom.xml中添加以下依赖

1
2
3
4
5
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>4.3</version>
</dependency>

报错:程序包com.unboundid.ldap.listener不存在

错误:

1
2
/tomcat/test/org/apache/catalina/realm/TestJNDIRealmIntegration.java:37:35
java: 程序包com.unboundid.ldap.listener不存在

解决:在pom.xml中添加以下依赖

1
2
3
4
5
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>6.0.0</version>
</dependency>

报错:程序包trailers不存在

错误:

1
2
/tomcat/test/org/apache/coyote/http2/TestStream.java:33:16
java: 程序包trailers不存在

解决:复制/tomcat/webapps/examples/WEB-INF/classes/trailers整个目录至/test目录下

报错:找不到符号:变量CookieFilter

错误:

1
2
3
4
/tomcat/test/util/TestCookieFilter.java:29:36
java: 找不到符号
符号: 变量 CookieFilter
位置: 类 util.TestCookieFilter

解决:复制/tomcat/webapps/examples/WEB-INF/classes/util/CookieFilter.java/test/util目录下。

IDEA可能会有缓存,需要Rebuild Project,或者刷新一下maven

至此,我们就能正常编译tomcat源码了,下面我们尝试启动一下tomcat

尝试启动

运行org.apache.catalina.startup.Bootstrap#main方法。

控制台中有以下报错信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java.lang.ClassNotFoundException: listeners.ContextListener
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1407)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1215)
at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:538)
...

java.lang.ClassNotFoundException: listeners.SessionListener
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1407)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1215)
at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:538)
...

java.lang.ClassNotFoundException: async.AsyncStockContextListener
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1407)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1215)
at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:538)
...

java.lang.ClassNotFoundException: websocket.drawboard.DrawboardContextListener
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1407)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1215)
at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:538)
...

方法一:直接删除/tomcat/webapps/examples整个目录。

方法二:复制缺少的.java文件至/java目录下。

  1. 复制/tomcat/webapps/examples/WEB-INF/classes/listeners整个目录至/java目录下。
  2. 复制/tomcat/webapps/examples/WEB-INF/classes/async/AsyncStockContextListener.java/tomcat/webapps/examples/WEB-INF/classes/async/Stockticker.java文件至/java/async目录下 (新建async包)。
  3. 复制/tomcat/webapps/examples/WEB-INF/classes/websocket/drawboard整个目录至/java/websocket目录下。

Rebuild Project后重新运行Bootstrap#main方法,控制台中又出现以下报错信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
八月 02, 2021 11:45:51 下午 org.apache.catalina.core.StandardContext filterStart
严重: 启动过滤器异常
java.lang.ClassNotFoundException: compressionFilters.CompressionFilter
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1407)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1215)
at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:538)
at org.apache.catalina.core.DefaultInstanceManager.loadClassMaybePrivileged(DefaultInstanceManager.java:519)
...

八月 02, 2021 11:45:51 下午 org.apache.catalina.core.StandardContext filterStart
严重: 启动过滤器异常
java.lang.ClassNotFoundException: filters.ExampleFilter
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1407)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1215)
at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:538)
at org.apache.catalina.core.DefaultInstanceManager.loadClassMaybePrivileged(DefaultInstanceManager.java:519)
...
  1. 复制/tomcat/webapps/examples/WEB-INF/classes/compressionFilters整个目录至/java目录下。
  2. 复制/tomcat/webapps/examples/WEB-INF/classes/filters整个目录至/java目录下。

Rebuild Project后重新运行Bootstrap#main方法,控制台中已无报错信息。

在浏览器中访问:http://localhost:8080,页面显示以下报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
org.apache.jasper.JasperException: org.apache.jasper.JasperException: æ— æ³•ä¸ºJSP编译类
org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:589)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:425)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:379)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:327)
javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
...

java.lang.NullPointerException
at org.apache.jasper.compiler.Validator$ValidateVisitor.<init>(Validator.java:527)
at org.apache.jasper.compiler.Validator.validateExDirectives(Validator.java:1869)
at org.apache.jasper.compiler.Compiler.generateJava(Compiler.java:224)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:391)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:367)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:351)
...

原因是未初始化JSP解析器。

解决:编辑org.apache.catalina.startup.ContextConfig#configureStart方法,在webConfig();方法调用处前面添加以下代码:

1
context.addServletContainerInitializer(new JasperInitializer(), null);

设置JSP解析器

重新运行Bootstrap#main方法后浏览器中访问http://localhost:8080,成功展示tomcat首页。

tomcat首页

解决控制台日志中文乱码问题:

正常启动tomcat后发现控制台打印的日志存在类似以下乱码的情况:

1
2
3
4
5
6
7
8
9
八月 02, 2021 8:28:43 下午 org.apache.jasper.servlet.TldScanner scanJars
信息: 至少有一个JAR被扫描用于TLD但尚未包含TLD。 为此记录器启用调试日志记录,以获取已扫描但未在其中找到TLD的完整JAR列表。 在扫描期间跳过不需要的JAR可以缩短启动时间和JSP编译时间。

...

八月 02, 2021 8:28:43 下午 org.apache.coyote.AbstractProtocol start
信息: 开始协议处理句柄["http-nio-8080"]
八月 02, 2021 8:28:43 下午 org.apache.catalina.startup.Catalina start
信息: [1086]毫秒后服务器启动

查看乱码打印日志的类后发现,其调用的是org.apache.tomcat.util.res.StringManager#getString(java.lang.String)方法或org.apache.jasper.compiler.Localizer#getMessage(java.lang.String)方法获取的日志内容。根本原因是在调用java.util.ResourceBundle#getString方法时未指定编码字符集为”UTF-8“,进行强制类型转换将java.lang.Object强转成java.lang.String导致。

解决方案:

修改以下两个方法中的代码

  • org.apache.jasper.compiler.Localizer#getMessage(java.lang.String)

1
errMsg = bundle.getString(errCode);

改为:

1
errMsg = new String(bundle.getString(errCode).getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
  • org.apache.tomcat.util.res.StringManager#getString(java.lang.String)

str = bundle.getString(key);

改为:

1
str = new String(bundle.getString(key).getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);

至此,我们就完美地在IDEA中构建并启动了Tomcat9.0.x源码。

参考: