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的分层设计与组件生命周期管理还是很值得学习的。

posted @ 2024-01-20 18:58  柠檬水请加冰  阅读(37)  评论(0编辑  收藏  举报