Tomcat_实现内嵌代码方式启动
构建tomcat_maven项目
可以从github上直接拉取tomcat_maven项目;也可以自己下载源码构建,这里贴一下tomcat8对应的pom.xml
<?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>2.3</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.13.2</version>
<scope>test</scope>
</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</version>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
tomcat启动方式
启动方式有两种,一种是下载tomcat安装包,运行bin/start.sh独立启动;另一种是类似springboot,内嵌tomcat代码作为web容器启动
内嵌方式启动
tomcat测试代码里依赖的就是内嵌方式启动,所以看一下测试函数是如何实现的即可,具体代码位置在,test/org/apache/catalina/startup/TestTomcat.java
@Test
public void testProgrammatic() throws Exception {
Tomcat tomcat = getTomcatInstance();
// No file system docBase required
Context ctx = tomcat.addContext("", null);
Tomcat.addServlet(ctx, "myServlet", new HelloWorld());
ctx.addServletMapping("/", "myServlet");
tomcat.start();
ByteChunk res = getUrl("http://localhost:" + getPort() + "/");
assertEquals("Hello world", res.toString());
}
我们知道Tomcat是按层级构建的,server -> services -> connectors, engine -> hosts -> contexts -> wrappers ,独立启动时可以依赖解析xml实现,现在关注如何通过代码配置完成构建,这里核心类就是Tomcat了
在测试的基础类中,test/org/apache/catalina/startup/TomcatBaseTest.java ,简单看一下官方测试是如何构建Tomcat实例
tomcat = new TomcatWithFastSessionIDs();
String protocol = getProtocol();
Connector connector = new Connector(protocol);
// Listen only on localhost
connector.setAttribute("address", InetAddress.getByName("localhost").getHostAddress());
// Use random free port
connector.setPort(0);
// Mainly set to reduce timeouts during async tests
connector.setAttribute("connectionTimeout", "3000");
tomcat.getService().addConnector(connector);
tomcat.setConnector(connector);
...
内嵌构建
尝试直接调用Tomcat生命周期函数完成Tomcat类的构建与使用,这里需要提一下Lifecycle,里面定义了tomcat组件的生命周期,例如初始化,启动..
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir("xx");
tomcat.init();
tomcat.start();
tomcat.stop();
tomcat.destroy();
一月 20, 2024 6:30:03 下午 org.apache.coyote.AbstractProtocol init
信息: Initializing ProtocolHandler ["http-nio-8080"]
一月 20, 2024 6:30:03 下午 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
信息: Using a shared selector for servlet write/read
一月 20, 2024 6:30:03 下午 org.apache.catalina.core.StandardService startInternal
信息: Starting service Tomcat
一月 20, 2024 6:30:03 下午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler ["http-nio-8080"]
一月 20, 2024 6:30:03 下午 org.apache.coyote.AbstractProtocol pause
信息: Pausing ProtocolHandler ["http-nio-8080"]
一月 20, 2024 6:30:03 下午 org.apache.catalina.core.StandardService stopInternal
信息: Stopping service Tomcat
一月 20, 2024 6:30:03 下午 org.apache.coyote.AbstractProtocol stop
信息: Stopping ProtocolHandler ["http-nio-8080"]
一月 20, 2024 6:30:03 下午 org.apache.coyote.AbstractProtocol destroy
信息: Destroying ProtocolHandler ["http-nio-8080"]
代码很简单,init()方法里已经创建了tomcat的server、service和connector组件;web应用的engine,host.. 等组件还未创建,而且还需要有一个可以测试的servlet,下面的测试代码写在官方测试目录下
@Test
public void test() throws LifecycleException, InterruptedException, IOException {
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir("xx");
Context ctx = tomcat.addContext("", null);
OK ok = new OK();
Tomcat.addServlet(ctx, "ok", ok);
ctx.addServletMapping("/", "ok");
tomcat.init();
tomcat.start();
ByteChunk res = TomcatBaseTest.getUrl("http://localhost:8080/");
assertEquals("OK", res.toString());
tomcat.stop();
tomcat.destroy();
}
private static class OK extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain");
resp.getWriter().print("OK");
}
}
可以看到,这里创建了Context,同时绑定了一个返回OK
字符的Servlet;创建context时会顺带完成host <- engine <- service <- server的构建,connector还是交由init()方法创建,测试日志如下
一月 20, 2024 6:38:19 下午 org.apache.coyote.AbstractProtocol init
信息: Initializing ProtocolHandler ["http-nio-8080"]
一月 20, 2024 6:38:19 下午 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
信息: Using a shared selector for servlet write/read
一月 20, 2024 6:38:19 下午 org.apache.catalina.core.StandardService startInternal
信息: Starting service Tomcat
一月 20, 2024 6:38:19 下午 org.apache.catalina.core.StandardEngine startInternal
信息: Starting Servlet Engine: Apache Tomcat/@VERSION@
一月 20, 2024 6:38:19 下午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler ["http-nio-8080"]
一月 20, 2024 6:38:39 下午 org.apache.coyote.AbstractProtocol pause
信息: Pausing ProtocolHandler ["http-nio-8080"]
一月 20, 2024 6:38:39 下午 org.apache.catalina.core.StandardService stopInternal
信息: Stopping service Tomcat
一月 20, 2024 6:38:39 下午 org.apache.coyote.AbstractProtocol stop
信息: Stopping ProtocolHandler ["http-nio-8080"]
一月 20, 2024 6:38:39 下午 org.apache.coyote.AbstractProtocol destroy
信息: Destroying ProtocolHandler ["http-nio-8080"]
到这里,内嵌代码方式启动tomcat已经介绍完了,更多 关于springboot如何配置tomcat,会涉及到SCI接口,spring生命周期与tomcat生命周期的绑定.. 是一个更大的话题了。
思考
2024年了,看到这样“规整的模块代码”,多少有点怀旧,哈哈。从中能感受到当年的tomcat是有着很大的构想,只是时代变了,现在很少会在一个应用里集成这么多的组件。代码层面来看,tomcat的分层设计与组件生命周期管理还是很值得学习的。