Tomcat源码解析(一):开坑!手把手教你读Tomcat源码。

目录

前言

一、环境搭建:建议拿来主义

二、从时序图,看tomcat启动

1.时序图

a).Bootstrap 的main 函数

b).Bootstrap 的init()

c).Bootstrap.load()

d).catalina.load()

e).Server.init()

2.思考

1.tomcat的类加载过程是什么样

2.Degister如何解析server.xml文件


前言

       Tomcat的使用已经十分方便,大部分人工作中可能都不会有二次开发它的必要了。那么我们为什么还需要阅读它的源码呢?

       往往熟练的使用,仅仅能让你成为一个熟练的代码民工。阅读源码,会让你在这一领域拥有更多的认知积累,逐渐成为一个拥有整套完整思维模式和优秀认知事物能力的人。仅仅停留在见过,会用,确从没想过代码背后蕴含的思想,更少有人研究过源码,进而体会大师们在某些问题解决上秉承的思想和思维的风格。

     


一、环境搭建:建议拿来主义

如果使用eclipse,可以直接从官网下载并导入源码,ant编译后运行。习惯了idea+maven的童鞋就难受了。需要手动添加pom.xml文件。tomcat10以上的版本,代码仓库的jar无法满足需求,还需要手动从tomcat的lib文件下去添加依赖。有兴趣的童鞋自行尝试。失败了也没关系,毕竟咱们还有plan B。

懒人推荐:直接引入已经别人调试成功的源码。跳过繁琐配置。根据我多年读代码的经验,每多一步繁琐的操作,就会磨灭一点你看源码的热情。撸起袖子,直接开始,手撕源码。

推荐一个源码下载地址:成功环境  (不是我搭建的,我也是直接导入)

二、从时序图,看tomcat启动

1.时序图

了解一个陌生事物,最简单的方式就是看别人的总结,so,我们先看一下Tomcat启动的时序图:

好家伙,这么多概念。没有关系,不必理会这些容器的定义,现在就犹如探索一堆黑盒子。我们要做的,仅仅是从代码里探索,了解这些容器什么时候被创建,是如何分工,处理请求,返回响应等等逻辑。最后再由我们自己给出这些容器的定义,与书本上的内容对比。有点啰嗦,上代码:

a).Bootstrap 的main 函数


    public static void main(String args[]) {
        // 这里加锁 应该是为了防止多次初始化吧
        synchronized (daemonLock) {
            if (daemon == null) {
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try {
                    // 1、初始化类加载器,并初始化calatina
                    bootstrap.init();
                } catch (Throwable t) {
                    //省略非关键代码。。。。。
                }
                daemon = bootstrap;
            } else {
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
            }
        }

        try {
            String command = "start";
            // 默认command=start,如果args有参数,必须把start放在最后
            // 如 -config xxx start
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            if (command.equals("start")) {
                // 以下三个方法都是反射调用calatina的方法
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
            } else {
                //其他分支代码均省略。。。。。。。。。。。
            }
        } catch (Throwable t) {
            //省略非关键代码。。。。。
        }
    }

 

 可以看到,main作为入口,完成两件事:

1.初始化类加载器,并初始化calatina (后一章会详细介绍tomcat的类加载机制)。

2.调用了setAwait load start 方法。

b).Bootstrap 的init()

初始化类加载器代码先跳过。在init时,创建了时序图中第二个对象,catalina,并将sharedLoader 设置为父类加载器。那么catalina的load过程是在什么时候被调用。别急,沿着代码继续看。

public void init() throws Exception {

        // 初始化三个classLoader:
        // commonLoader、catalinaLoader、sharedLoader
        initClassLoaders();
        // 设置当前线程的ContextClassLoader为catalinaLoader
        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);

        // Load our startup class and call its process() method
        // 用 catalinaLoader加载并实例化Catalina
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();
        // 调用 catalina 的方法setParentClassLoader,
        // 设置catalina的parentClassLoader 为 sharedLoader
        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;
    }

 回到a中,此时我们init代码已经执行完毕,接下来将执行setAwait,load,start 的方法 

c).Bootstrap.load()


    private void load(String[] arguments) throws Exception {

        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        method.invoke(catalinaDaemon, param);
    }

最后一句,通过反射的方式,使catalina动态的执行了load()方法。留个疑问:此处为什么使用反射,直接使用catalina.load()执行呢?

d).catalina.load()

 代码看到这里,与我们所见的时序图完全一致。catalina 的load方法中,做了server.xml的解析,这里不详细说了(另开一章Digester)。解析完成后会生成一个StandardServer ,后面的getServer()返回就是这个对象。

public void load() {
        //省略非关键代码。。。。。。。。。。。。。
        // Before digester - it may be needed
        initNaming();
        // Parse main server.xml
        // 解析server.xml 
        parseServerXml(true);
        Server s = getServer();
        if (s == null) {
            return;
        }
        // 设置Server的Catalina、CatalinaHome、CatalinaBase
        getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

        // Stream redirection
        initStreams();


        // Start the new server
        try {
            getServer().init();
        } catch (LifecycleException e) {
            //省略非关键代码。。。。。。。。。。。。。
        }

        
    }

 致此,我相信还是比较简单的。(因为难得部分都被我跳过了)

代码中server的init(),我们可以看一下这个类图:

init是调用父类方法,这里是典型的模板模式。子类实现的是initInternal()方法。这样做的好处就是再init中定义了一个算法的骨架,而将一些步骤的具体实现延迟到子类中。

e).Server.init()

先看看骨架,公共代码部分,主要是维护生命周期的状态:

public final synchronized void init() throws LifecycleException {
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }

        try {
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            initInternal();
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        } catch (Throwable t) {
            handleSubClassException(t, "lifecycleBase.initFail", toString());
        }
    }

 接下来看看子类的具体实现,注意代码最后一句,是一个for循环来创建多个service

 protected void initInternal() throws LifecycleException {

        super.initInternal();

        // Initialize utility executor
        reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
        register(utilityExecutor, "type=UtilityExecutor");

        // Register global String cache
        // Note although the cache is global, if there are multiple Servers
        // present in the JVM (may happen when embedding) then the same cache
        // will be registered under multiple names
        onameStringCache = register(new StringCache(), "type=StringCache");

        // Register the MBeanFactory
        MBeanFactory factory = new MBeanFactory();
        factory.setContainer(this);
        onameMBeanFactory = register(factory, "type=MBeanFactory");

        // Register the naming resources
        globalNamingResources.init();

        // Populate the extension validator with JARs from common and shared
        // class loaders
        if (getCatalina() != null) {
            ClassLoader cl = getCatalina().getParentClassLoader();
            // Walk the class loader hierarchy. Stop at the system class loader.
            // This will add the shared (if present) and common class loaders
            while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
                if (cl instanceof URLClassLoader) {
                    URL[] urls = ((URLClassLoader) cl).getURLs();
                    for (URL url : urls) {
                        if (url.getProtocol().equals("file")) {
                            try {
                                File f = new File (url.toURI());
                                if (f.isFile() &&
                                        f.getName().endsWith(".jar")) {
                                    ExtensionValidator.addSystemResource(f);
                                }
                            } catch (URISyntaxException | IOException e) {
                                // Ignore
                            }
                        }
                    }
                }
                cl = cl.getParent();
            }
        }
        // Initialize our defined Services
        for (Service service : services) {
            service.init();
        }
    }

2.思考

代码看到这里,我们对bootstrap启动,到创建service的过程有了个大致了解。同时留下两个坑:

1.tomcat的类加载过程是什么样

2.Degister如何解析server.xml文件

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Tomcat源码解析是对Tomcat服务器的源代码进行分析和解的过程。通过对Tomcat源码的研究,可以深入了解Tomcat的整体架构、连接器的内部结构、容器分析以及Tomcat的启动流程等方面的内容。 Tomcat的整体架构包括配置文件server.xml的分析和连接器的内部结构。配置文件server.xml是Tomcat的主要配置文件,通过对其进行分析可以了解Tomcat的各个组件和配置项的作用。连接器是Tomcat的核心组件之一,负责处理客户端请求并将其转发给相应的容器进行处理。 Tomcat的启动流程是通过实现Lifecycle接口的各个组件来完成的。在启动过程中,Tomcat会按照一定的顺序初始化和启动各个组件,确保它们能够正常工作。具体的启动流程可以通过阅源码中的相关方法和注释来了解。 Tomcat底层使用了Netty来实现IO相关的操作,但与Netty有所区别,因为Tomcat对部分处理进行了封装。通过对Tomcat源码的学习,可以了解Tomcat底层的实现逻辑、各个组件的配合方式以及各种设计模式的交互。 如果你对Tomcat源码解析感兴趣,可以参考提供的源码和相关文章进行深入研究。通过深入研究Tomcat源码,你可以更好地理解Tomcat的工作原理和内部机制。 #### 引用[.reference_title] - *1* [Tomcat源码分析](https://blog.csdn.net/sun_code/article/details/123554480)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [tomcat线程模型-源码解析](https://blog.csdn.net/qq_16498553/article/details/126080174)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值