服务器之家:专注于服务器技术及软件下载分享
分类导航

云服务器|WEB服务器|FTP服务器|邮件服务器|虚拟主机|服务器安全|DNS服务器|服务器知识|Nginx|IIS|Tomcat|

服务器之家 - 服务器技术 - Tomcat - 详解从源码分析tomcat如何调用Servlet的初始化

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

2021-09-23 16:54g-Jack Tomcat

这篇文章主要介绍了详解从源码分析tomcat如何调用Servlet的初始化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

引言

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

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

一、代码启动tomcat

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

?
1
2
3
4
5
6
7
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 方法 调用流程图

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

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

3.2 源码分析

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

方法路径:package org.apache.catalina.startup.tomcat.addwebapp(host host, string contextpath, string docbase);

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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中。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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方法

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

?
1
2
3
4
5
6
7
8
9
10
11
@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()方法的 核心代码

?
1
2
3
4
5
6
7
8
9
10
11
12
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、方法调用流程图

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

4.2、源码分析

说明:standardserver 、standardservice、standardengine等容器都是继承lifecyclebase

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

1)逐层启动容器

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

?
1
2
3
4
5
6
7
8
9
public void start() throws lifecycleexception {
      //防止server容器没有创建
      getserver();
      //获得connector容器,并且将得到的connector容器设置到service容器中
      getconnector();
      //这里的start的实现是在 lifecyclebase类中实现
      //lifecyclebase方法是一个模板方法,在tomcat启动流程中非常关键
      server.start();
  }

2) 进入start方法

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

进入lifecycelbase中的start方法,其中核心方法是startinternal。

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

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

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

方法路径:org.apache.catalina.core.standardserver.startinternal()

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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容器

?
1
2
3
4
5
6
7
8
9
// 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容器

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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() 方法中的核心代码:

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

?
1
2
3
4
5
6
7
8
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()方法

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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())方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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

到此这篇关于详解从源码分析tomcat如何调用servlet的初始化的文章就介绍到这了,更多相关tomcat调用servlet初始化内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/hao134838/article/details/109746151

延伸 · 阅读

精彩推荐
  • TomcatTomcat服务器的安全设置

    Tomcat服务器的安全设置

    tomcat是一个开源Web服务器,基于Tomcat的Web运行效率高,可以在一般的硬件平台上流畅运行,因此,颇受Web站长的青睐。不过,在默认配置下其存在一定的安...

    IT专家网9002021-08-03
  • TomcatEclipse创建tomcat实现过程原理详解

    Eclipse创建tomcat实现过程原理详解

    这篇文章主要介绍了Eclipse创建tomcat实现过程原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以...

    海绵般汲取11642021-09-16
  • Tomcat解决Tomcat的maxPostSize属性的配置需要注意的问题

    解决Tomcat的maxPostSize属性的配置需要注意的问题

    这篇文章主要介绍了解决Tomcat的maxPostSize属性的配置需要注意的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋...

    life is wonderful12012021-09-13
  • Tomcat如何查看tomcat的控制台输出的方法

    如何查看tomcat的控制台输出的方法

    这篇文章主要介绍了如何查看tomcat的控制台输出的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    CaiCaiNeo11932021-08-29
  • TomcatTomcat安装配置方法图文教程

    Tomcat安装配置方法图文教程

    这篇文章主要为大家详细介绍了Tomcat安装配置方法图文教程,java环境变量如何配置,Eclipse安装配置方法图文教程 ,为大家分享了三个教程,感兴趣的小伙...

    Tomcat教程网13292021-08-11
  • TomcatTomcat CentOS安装实现过程图解

    Tomcat CentOS安装实现过程图解

    这篇文章主要介绍了Tomcat CentOS安装实现过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考...

    agrin5842021-09-15
  • TomcatTomcat整体结构简单介绍

    Tomcat整体结构简单介绍

    这篇文章主要介绍了Tomcat整体结构简单介绍,Tomcat的本质是一个Servlet容器。一个Servlet能做的事情是:处理请求资源,并为客户端填充response对象,需要的朋友...

    叫我田露也行12302021-09-07
  • Tomcat一次tomcat源码启动控制台中文乱码的调试过程记录

    一次tomcat源码启动控制台中文乱码的调试过程记录

    平时在使用tomcat做一些服务的时候经常遇到各种乱码问题,下面这篇文章主要给大家介绍了一次tomcat源码启动控制台中文乱码的调试过程,需要的朋友可以...

    zhoutaoping199211852021-09-24