tomcat源码分析(一)

摘要

在web开发过程中tomcat是一款使用率非常高的软件,tomcat作用apache顶级项目之一,采用了多种精巧的设计模式,学习优秀的代码实践有助于提升自己的代码质量。这篇文章在说明 Server 和 Service 的启动过程中,介绍了类加载器的双亲委派机制,监听器和模板方法两种设计模式,供大家一起学习。

实验步骤

tomcat 使用ant进行构建,为了便于分析使用maven管理依赖。首先使用git 拉取代码,其次导入eclipse。笔者使用jdk版本为:openjdk 11

git clone https://gitee.com/jiajinliu/tomcat70.git

Eclipse 中,找到 org.apache.catalina.startup.Bootstrap 类。 在 main 方法中设置断点。启动 Tomcat 并进入调试模式。

类加载器的使用

tomcat在启动过程中,Catalina对象其这重要的作用,Catalina负责加载service.xml文件,启动Service。下面的代码展示了Catalina对象的创建过程。

    Class<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.newInstance();

    // Set the shared extensions class loader
    if (log.isDebugEnabled())
        log.debug("Setting startup class properties");
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    method.invoke(startupInstance, paramValues);

    catalinaDaemon = startupInstance;

tomcat使用类加载器实例化Catalina对象,这样实例化的原因是什么呢? 使用不同类加载器是为了避免类冲突。下面用代码说明其中的原理。 假设我们有两个版本的同一个类 MyClass,分别位于不同的 JAR 文件中: mylib-1.0.jar 包含 MyClass 的第一个版本。 mylib-2.0.jar 包含 MyClass 的第二个版本。 我们希望在同一个 JVM 中加载这两个版本的 MyClass,并且它们互不干扰。

// MyClass 版本 1.0(位于 mylib-1.0.jar)
public class MyClass {
    public void printVersion() {
        System.out.println("MyClass version 1.0");
    }
}
// MyClass 版本 2.0(位于 mylib-2.0.jar)
public class MyClass {
    public void printVersion() {
        System.out.println("MyClass version 2.0");
    }
}

在同一个类加载器中加载两个版本的 MyClass,并尝试使用它们

import java.net.URL;
import java.net.URLClassLoader;

public class Main {
    public static void main(String[] args) throws Exception {
        // 创建一个自定义类加载器
        URL[] urls = {new URL("file:/path/to/mylib-1.0.jar"), new URL("file:/path/to/mylib-2.0.jar")};
        CustomClassLoader classLoader = new CustomClassLoader(urls);

        // 加载两个版本的 MyClass
        Class<?> myClass1 = classLoader.loadClass("com.example.mylib.MyClass");
        Class<?> myClass2 = classLoader.loadClass("com.example.mylib.MyClass");

        // 创建两个版本的 MyClass 实例
        Object instance1 = myClass1.getDeclaredConstructor().newInstance();
        Object instance2 = myClass2.getDeclaredConstructor().newInstance();

        // 调用方法
        myClass1.getMethod("printVersion").invoke(instance1);
        myClass2.getMethod("printVersion").invoke(instance2);

        // 演示类冲突
        System.out.println("instance1 instanceof MyClass: " + (instance1 instanceof com.example.mylib.MyClass));
        System.out.println("instance2 instanceof MyClass: " + (instance2 instanceof com.example.mylib.MyClass));
    }
}

完整代码见仓库:https://gitee.com/jiajinliu/analysis_web_server.git

Server 启动过程

Server 的启动过程中用到了如下设计模式:

监听器设计模式

接口Server的实现类是StandardService ,这个类使用Lifecycle 接口和 LifecycleListener 接口共同实现了观察者模式。通过这种方式,StandardService 可以在状态变化时通知所有注册的监听器,从而实现灵活的事件处理机制。

tomcat监听器

模板方法设计模式

在抽象类LifecycleBase中实现了接口Lifecycle的start方法,确保启动过程中的顺序,启动过程由一系列的方法组成,其中一些方法是抽象的(startInternal),需要由子类实现。子类可以通过实现抽象方法来扩展具体的逻辑,而不需要修改模板方法。

tomcat_模板方法

Service 启动过程

Service 的分别启动 Connector 和 Engine。在启动时使用 synchronized 加锁处理,确保在多线程环境下,StandardService 启动 Container 和 Connector 时能够安全、正确地执行。

    @Override
    protected void startInternal() throws LifecycleException {
        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        setState(LifecycleState.STARTING);

        // Start our defined Container first
        if (container != null) {
            synchronized (container) {
                container.start();
            }
        }

        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }

        // Start our defined Connectors second
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                try {
                    // If it has already failed, don't try and start it
                    if (connector.getState() != LifecycleState.FAILED) {
                        connector.start();
                    }
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
        }
    }

实验总结

通过阅读tomcat中 Server 和 Service 的启动过程,熟悉了类加载器的双亲委派机制,监听器和模板方法两种设计模式,使用synchronized锁确保在多线程环境下程序的正确运行。

Leave a Reply

Your email address will not be published. Required fields are marked *

赣ICP备2025059670号