从源码分析tomcat如何调用Servlet的初始化

简介: 从源码分析tomcat如何调用Servlet的初始化

引言


上一篇博客我们将tomcat源码在本地成功运行了,所以在本篇博客中我们从源码层面分析,tomcat在启动的过程中,是如何初始化servlet容器的。我们平常都是将我们的服务部署到 tomcat中,然后修改一下配置文件,启动就可以对外提供 服务了,但是我们对于其中的一些流程并不是非常的了解,例如如何加载的web.xml等。这是我们分析servlet 和 sringMVC必不可少的过程。


注释源码地址:https://github.com/good-jack/tomcat_source/tree/master


一、代码启动tomcat


平常我们不论是Windows还是linux,我们都是通过脚本来启动tomcat,这对于我们分析源码不是很友好,所以我们 需要通过代码启动,启动代码如下:


Tomcat tomcat = new Tomcat();
        tomcat.setPort(8080);
        //new 出各层容器,并且维护各层容器的关系
        tomcat.addWebapp("/","/");
        tomcat.start();
        //阻塞监听端口
        tomcat.getServer().await();

启动代码还是非常非常简单,从代码中我们就可以看出,我们本篇博客主要分析的就是 addWebapp()方法和start()方法,通过这两个方法我们就可以找到servlet容器是在什么时候被初始化的。


二、tomcat框架



在我们进行分析上面两个方法之前,我们先总结一下tomcat的基础框架,其实从我们非常熟悉的 server.xml配置文件中就可以知道,tomcat就是一系列父子容器组成:


Server   --->  Service   -->   Connector   Engine addChild--->   context(servlet容器) ,这就是我们从配置文件中分析出来的几个容器,tomcat启动时候就是逐层启动容器。


三、创建容器(addWebapp())


3.1 方法 调用流程图


20201117164533927.png

上面的流程图就是,从源码中逐步分析出来的几个重要的方法,这对于我们分析源码非常有帮助。


3.2 源码分析


1)通过反射获得configContext监听器

方法路径:package org.apache.catalina.startup.Tomcat.addWebapp(Host host, String contextPath, String docBase);

    public Context  addWebapp(Host host, String contextPath, String docBase) {
        //通过反射获得一个监听器  ContextConfig,
        //通过反射得到的一定是LifecycleListener的一个实现类,进入getConfigClass得到实现类(org.apache.catalina.startup.ContextConfig)
        LifecycleListener listener = null;
        try {
            Class<?> clazz = Class.forName(getHost().getConfigClass());
            listener = (LifecycleListener) clazz.getConstructor().newInstance();
        } catch (ReflectiveOperationException e) {
            // Wrap in IAE since we can't easily change the method signature to
            // to throw the specific checked exceptions
            throw new IllegalArgumentException(e);
        }
        return addWebapp(host, contextPath, docBase, listener);
    }

2) 获得一个context容器(StandardContext)

在下面代码中,createContext()方法通过反射加载StandardContext容器,并且将设置监听ContextConfig, ctx.addLifecycleListener(config);

public Context addWebapp(Host host, String contextPath, String docBase,
            LifecycleListener config) {
        silence(host, contextPath);
        //获得一个context容器(StandardContext)
        Context ctx = createContext(host, contextPath);
        ctx.setPath(contextPath);
        ctx.setDocBase(docBase);
        if (addDefaultWebXmlToWebapp) {
            ctx.addLifecycleListener(getDefaultWebXmlListener());
        }
        ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));
        //把监听器添加到context中去
        ctx.addLifecycleListener(config);
        if (addDefaultWebXmlToWebapp && (config instanceof ContextConfig)) {
            // prevent it from looking ( if it finds one - it'll have dup error )
            ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
        }
        if (host == null) {
            //getHost会逐层创建容器,并维护容器父子关系
            getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }
        return ctx;
    }

3)维护各层容器


getHost()方法中得到各层容器,并且维护父亲容器关系,其中包括,server容器、Engine容器。并且将StandardContext容器通过getHost().addChild(ctx); 调用containerBase中的addChild()方法维护在 children 这个map中。

  public Host getHost() {
        //将每一层的容器都new 出来
        Engine engine = getEngine();
        if (engine.findChildren().length > 0) {
            return (Host) engine.findChildren()[0];
        }
        Host host = new StandardHost();
        host.setName(hostname);
        //维护tomcat中的父子容器
        getEngine().addChild(host);
        return host;
    }


getEngine().addChild(host); 方法选择调用父类containerBase中的addChild方法


20201117170358893.png

  @Override
    public void addChild(Container child) {
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> dp =
                new PrivilegedAddChild(child);
            AccessController.doPrivileged(dp);
        } else {
            //这里的child 参数是 context 容器
            addChildInternal(child);
        }
    }

addChildInternal()方法的 核心代码

 private void addChildInternal(Container child) {
        if( log.isDebugEnabled() )
            log.debug("Add child " + child + " " + this);
        synchronized(children) {
            if (children.get(child.getName()) != null)
                throw new IllegalArgumentException("addChild:  Child name '" +
                                                   child.getName() +
                                                   "' is not unique");
            child.setParent(this);  // May throw IAE
            children.put(child.getName(), child);
    }


四、启动容器(tomcat.start())


4.1、方法调用流程图


20201117170910239.png

4.2、源码分析


说明:StandardServer 、StandardService、StandardEngine等容器都是继承LifecycleBase


所以这里是模板模式的经典应用


1)逐层启动容器


此时的server对应的是我们前面创建的StandardServer

  public void start() throws LifecycleException {
        //防止server容器没有创建
        getServer();
        //获得connector容器,并且将得到的connector容器设置到service容器中
        getConnector();
        //这里的start的实现是在 LifecycleBase类中实现
        //LifecycleBase方法是一个模板方法,在tomcat启动流程中非常关键
        server.start();
    }

2) 进入start方法

20201117171856884.png


进入LifecycelBase中的start方法,其中核心方法是startInternal。

20201117171959709.png

从上面我们知道现在我们调用的是StandardServer容器的startInternal()方法,所以我们这里选择的是StandardServer

20201117172647157.png


方法路径:org.apache.catalina.core.StandardServer.startInternal()

protected void startInternal() throws LifecycleException {
        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);
        globalNamingResources.start();
        // Start our defined Services
        synchronized (servicesLock) {
            //启动 service容器,一个tomcat中可以配置多个service容器,每个service容器都对应这我们的一个服务应用
            for (Service service : services) {
                //对应 StandardService.startInternal()
                service.start();
            }
        }
    }


从上面代码中我们可以看出,启动server容器的时候需要启动子容器 service容器,从这里开始就是容器 逐层向向内引爆,所以接下来就是开始依次调用各层容器的star方法。在这里就不在赘述。


2)ContainerBase中的startInternal()方法 核心代码,从这开始启动StandardContext容器

 // Start our child containers, if any
        //在addWwbapp的流程中 addChild方法中加入的,所以这里需要找出来
        //这里找出来的就是 context 容器
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            //通过线程池 异步的方式启动线程池 开始启动 context容器,进入new StartChild
            results.add(startStopExecutor.submit(new StartChild(child)));
        }

new StartChild(child)) 方法开始启动StandardContext容器

    private static class StartChild implements Callable<Void> {
        private Container child;
        public StartChild(Container child) {
            this.child = child;
        }
        @Override
        public Void call() throws LifecycleException {
            //开始启动context,实际调用 StandardContext.startInternal()
            child.start();
            return null;
        }
    }


StandardContext.startInternal() 方法中的核心代码:

20201117174135658.png

   protected void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(this, type, data);
        //lifecycleListeners 在addwebapp方法的第一步中,设置的监听的 contextConfig对象
        for (LifecycleListener listener : lifecycleListeners) {
            //这里调用的是 contextConfig的lifecycleEvent()方法
            listener.lifecycleEvent(event);
        }
    }

进入到 contextConfig中的lifecycleEvent()方法


20201117174325364.png

public void lifecycleEvent(LifecycleEvent event) {
        // Identify the context we are associated with
        try {
            context = (Context) event.getLifecycle();
        } catch (ClassCastException e) {
            log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
            return;
        }
        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            //完成web.xml的内容解析
            configureStart();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();
        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            // Restore docBase for management tools
            if (originalDocBase != null) {
                context.setDocBase(originalDocBase);
            }
        } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
            configureStop();
        } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
            init();
        } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
            destroy();
        }
    }

在上面方法中,完成对web.xml的加载和解析,同时加载xml中配置的servlet并且封装成wrapper对象。


3)、启动servlet容器,StandardContext.startInternal()  中的 loadOnStartup(findChildren())方法

public boolean loadOnStartup(Container children[]) {
        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (Container child : children) {
            //这里的 Wrapper就是 我们前面封装的 servlet
            Wrapper wrapper = (Wrapper) child;
            int loadOnStartup = wrapper.getLoadOnStartup();
            if (loadOnStartup < 0) {
                continue;
            }
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> list = map.get(key);
            if (list == null) {
                list = new ArrayList<>();
                map.put(key, list);
            }
            list.add(wrapper);
        }
        // Load the collected "load on startup" servlets
        for (ArrayList<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    //通过 load 方法  最终会调用 servlet的init方法
                    wrapper.load();
                } catch (ServletException e) {
                    getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                          getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                    // NOTE: load errors (including a servlet that throws
                    // UnavailableException from the init() method) are NOT
                    // fatal to application startup
                    // unless failCtxIfServletStartFails="true" is specified
                    if(getComputedFailCtxIfServletStartFails()) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

在上面方法中,完成对web.xml的加载和解析,同时加载xml中配置的servlet并且封装成wrapper对象。


3)、启动servlet容器,StandardContext.startInternal()  中的 loadOnStartup(findChildren())方法

public boolean loadOnStartup(Container children[]) {
        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (Container child : children) {
            //这里的 Wrapper就是 我们前面封装的 servlet
            Wrapper wrapper = (Wrapper) child;
            int loadOnStartup = wrapper.getLoadOnStartup();
            if (loadOnStartup < 0) {
                continue;
            }
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> list = map.get(key);
            if (list == null) {
                list = new ArrayList<>();
                map.put(key, list);
            }
            list.add(wrapper);
        }
        // Load the collected "load on startup" servlets
        for (ArrayList<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    //通过 load 方法  最终会调用 servlet的init方法
                    wrapper.load();
                } catch (ServletException e) {
                    getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                          getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                    // NOTE: load errors (including a servlet that throws
                    // UnavailableException from the init() method) are NOT
                    // fatal to application startup
                    // unless failCtxIfServletStartFails="true" is specified
                    if(getComputedFailCtxIfServletStartFails()) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

通过 load 方法  最终会调用 servlet的init方法。


五、总结


上面内容就是整个tomcat是如何调用servlet初始化方法的流程,整个流程小编的理解,如果有错误,欢迎指正,小编已经在源码中重要部分进行了注释,所以如果有需要的各位读者,可以下载我的注释 源码,注释源码地址:


https://github.com/good-jack/tomcat_source/tree/master

目录
相关文章
|
7天前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
26 1
|
2月前
|
监控 网络协议 应用服务中间件
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
本文详细解析了Tomcat架构中复杂的`Connector`组件。作为客户端与服务器间沟通的桥梁,`Connector`负责接收请求、封装为`Request`和`Response`对象,并传递给`Container`处理。文章通过四个关键问题逐步剖析了`Connector`的工作原理,并深入探讨了其构造方法、`init()`与`start()`方法。通过分析`ProtocolHandler`、`Endpoint`等核心组件,揭示了`Connector`初始化及启动的全过程。本文适合希望深入了解Tomcat内部机制的读者。欢迎关注并点赞,持续更新中。如有问题,可搜索【码上遇见你】交流。
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
|
2月前
|
人工智能 前端开发 Java
【Tomcat源码分析】启动过程深度解析 (二)
本文深入探讨了Tomcat启动Web应用的过程,重点解析了其加载ServletContextListener及Servlet的机制。文章从Bootstrap反射调用Catalina的start方法开始,逐步介绍了StandardServer、StandardService、StandardEngine、StandardHost、StandardContext和StandardWrapper的启动流程。每个组件通过Lifecycle接口协调启动,子容器逐层启动,直至整个服务器完全启动。此外,还详细分析了Pipeline及其Valve组件的作用,展示了Tomcat内部组件间的协作机制。
【Tomcat源码分析】启动过程深度解析 (二)
|
2月前
|
前端开发 Java 应用服务中间件
【Tomcat源码分析 】"深入探索:Tomcat 类加载机制揭秘"
本文详细介绍了Java类加载机制及其在Tomcat中的应用。首先回顾了Java默认的类加载器,包括启动类加载器、扩展类加载器和应用程序类加载器,并解释了双亲委派模型的工作原理及其重要性。接着,文章分析了Tomcat为何不能使用默认类加载机制,因为它需要解决多个应用程序共存时的类库版本冲突、资源共享、类库隔离及JSP文件热更新等问题。最后,详细展示了Tomcat独特的类加载器设计,包括Common、Catalina、Shared、WebApp和Jsp类加载器,确保了系统的稳定性和安全性。通过这种设计,Tomcat实现了不同应用程序间的类库隔离与共享,同时支持JSP文件的热插拔。
【Tomcat源码分析 】"深入探索:Tomcat 类加载机制揭秘"
|
2月前
|
设计模式 应用服务中间件 容器
【Tomcat源码分析】Pipeline 与 Valve 的秘密花园
本文深入剖析了Tomcat中的Pipeline和Valve组件。Valve作为请求处理链中的核心组件,通过接口定义了关键方法;ValveBase为其基类,提供了通用实现。Pipeline则作为Valve容器,通过首尾相连的Valve链完成业务处理。StandardPipeline实现了Pipeline接口,提供了详细的Valve管理逻辑。通过对代码的详细分析,揭示了模板方法模式和责任链模式的应用,展示了系统的扩展性和模块化设计。
【Tomcat源码分析】Pipeline 与 Valve 的秘密花园
|
2月前
|
Java 应用服务中间件 Maven
JavaWeb基础5——HTTP,Tomcat&Servlet
JavaWeb技术栈、HTTP、get和post区别、响应状态码、请求响应格数据式、IDEA使用Tomcat、报错解决、Servlet的体系结构、IDEA使用模板创建Servlet
JavaWeb基础5——HTTP,Tomcat&Servlet
|
2月前
|
设计模式 人工智能 安全
【Tomcat源码分析】生命周期机制 Lifecycle
Tomcat内部通过各种组件协同工作,构建了一个复杂的Web服务器架构。其中,`Lifecycle`机制作为核心,管理组件从创建到销毁的整个生命周期。本文详细解析了Lifecycle的工作原理及其方法,如初始化、启动、停止和销毁等关键步骤,并展示了LifecycleBase类如何通过状态机和模板模式实现这一过程。通过深入理解Lifecycle,我们可以更好地掌握组件生命周期管理,提升系统设计能力。欢迎关注【码上遇见你】获取更多信息,或搜索【AI贝塔】体验免费的Chat GPT。希望本章内容对你有所帮助。
|
3月前
|
网络协议 Java 应用服务中间件
Tomcat源码分析 (一)----- 手撕Java Web服务器需要准备哪些工作
本文探讨了后端开发中Web服务器的重要性,特别是Tomcat框架的地位与作用。通过解析Tomcat的内部机制,文章引导读者理解其复杂性,并提出了一种实践方式——手工构建简易Web服务器,以此加深对Web服务器运作原理的认识。文章还详细介绍了HTTP协议的工作流程,包括请求与响应的具体格式,并通过Socket编程在Java中的应用实例,展示了客户端与服务器间的数据交换过程。最后,通过一个简单的Java Web服务器实现案例,说明了如何处理HTTP请求及响应,强调虽然构建基本的Web服务器相对直接,但诸如Tomcat这样的成熟框架提供了更为丰富和必要的功能。
|
5月前
|
前端开发 应用服务中间件