Tomcat源码分析----Container初始化与加载

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 在本章节中,以下几个问题会被回答:web容器和servlet容器的区别是什么;在springMVC中的web.xml是什么时候加载到tomcat中的;tomcat是怎么加载我们的web服务的;tomcat是怎么实现的热部署;

在本章节中,以下几个问题会被回答:

 
  • web容器和servlet容器的区别是什么;
  • 在springMVC中的web.xml是什么时候加载到tomcat中的;
  • tomcat是怎么加载我们的web服务的;
  • tomcat是怎么实现的热部署;

1 Container基本结构

从上文中有讲到,Connector和Container的初始化工作是由Digester来解析conf/server.xml来完成的。而在server.xml中已经告诉我们了Container的基本结构。那么我们先来看看server.xml文件:

<?xml version='1.0' encoding='utf-8'?>

<Server port="8005" shutdown="SHUTDOWN">
    ……
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>

如果有接触过xml文件,那么我们可以很清晰的看到,在这个xml文件中,Server下包含了Service,Service下包含了Connector和Engine,Engin下包含了Host。是时候放出一张图了。
screenshot

2 Container初始化

2.1 Container组件构成

继续看看Container容器的结构图,这个图很大众化,是个Container容器介绍的文章都会有:
screenshot

通过代码,我们会知道Container是一个接口,Container分成4个级别的容器,而且这四个级别容器的关系为父子关系。真正的顶层容器是Engine。Container作为容器,存在几个概念上的级别:

  • Engine 表示一个Servlet引擎,它可以包含一个或多个子容器,比如Host或者Context容器;
  • Host 表示一台虚拟的主机,它可以包含一系列Context容器;
  • Context 表示一个唯一的ServletContext,一个 Context 对应一个 Web 工程,它可以包含一个 或多个Wrapper容器;
  • Wrapper 表示一个独立的Servlet定义,即Wrapper本质就是对Servlet进行了一层包装。

通过这个图我们知道了Container的结构,那么Container的初始化工作是怎么完成的呢?

继续回到Catalina类中,在load方法中调用了createStartDigester方法。

protected Digester createStartDigester() {
    ……

    ////如果遇到”Server“元素起始符;则创建"org.apache.catalina.core.StandardServer"的一个实例对象,并压入堆栈;如果"Server"元素的"className"属性存在,那么用这个属性的值所指定的class来创建实例对象,并压入堆栈。
    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
    
    //从server.xml读取"Server"元素的所有{属性:值}配对,用对应的Setter方法将属性值设置到堆栈顶层元素(Server)。
    digester.addSetProperties("Server");
    
    //遇到"Server"结束符时,调用“次顶层元素(Catalina)”的"setServer"方法,
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");
    ……
    //遇到标签使用规则
    digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
    ……
}

从简化的源码中可以看到,digester对server.xml设置的标签动作有5种调用:

  • addObjectCreate:遇到起始标签的元素,初始化一个实例对象入栈
  • addSetProperties:遇到某个属性名,使用setter来赋值
  • addSetNext:遇到结束标签的元素,调用相应的方法
  • addRule:调用rule的begin 、body、end、finish方法来解析xml,入栈和出栈给对象赋值
  • addRuleSet:调用addRuleInstances来解析xml标签

从这些规则和xml中可以看到,Calatina的Server对象是StandardServer。
StandardService包含了多个Connector(xml中有2个connector)和一个StandardEngine Container。
StandardEngine包含了一个Host Container

2.2 Context容器加载web服务与热部署

从confg/server.xml中我们可以看到Server的容器的初始化只有Engine和Host,那么Context是什么时候初始化的呢,是怎么加载我们的web application,怎么实现的热部署呢?
先说结论,tomcat的Engine会启动一个线程,该线程每10s会发送一个发送一个事件,监听到该事件的部署配置类会自动去扫描webapp文件夹下的war包,将其加载成一个Context,即启动一个web服务。

OK,回过头看conf/server.xml和createStartDigester,添加了HostRuleSet,进入在HostRuleSet类中,可以看到这么一行代码:

digester.addRule(prefix + "Host",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.HostConfig",
                          "hostConfigClass"));

继续进入LifecycleListenerRule类可以发现,在监听事件中增加了HostConfig类的对象,也就是说StandardHost中新增了一个HostConfig监听器。

再回过头来进入StandardEngine的starInternal方法supper.startInternal(父类ContainerBase)中有这行代码:

threadStart();

进入后发现开启了一个线程,调用ContainerBackgroundProcessor这个的run方法,而这个run方法可以看到

protected class ContainerBackgroundProcessor implements Runnable {
    
    @Override
    public void run() {
        ……
        try {
            while (!threadDone) {
                try {
                    Thread.sleep(backgroundProcessorDelay * 1000L);//在StandardEngine中构造方法设置默认backgroundProcessorDelay=10,即10s调用一次
                } catch (InterruptedException e) {
                    // Ignore
                }
                if (!threadDone) {
                    ……
                    processChildren(parent, cl);
                }
            }
        } 
        ……
    }
}

也就是说每该线程每10s会调用一次processChildren,继续跟踪该方法,会看到调用其子容器Engine、Host、Context、Wrapper各容器组件及与它们相关的其它组件的backgroundProcess方法。

@Override
public void backgroundProcess() {
    if (loader != null) {
        try {
            loader.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);                
        }
    }
    ……
    fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}

这个方法中比较重要的两个
loader.backgroundProcess():调用了载入器的WebappLoader的backgroundProcess方法,进入这个方法可以看到:

public void backgroundProcess() {
    if (reloadable && modified()) {
        try {
            Thread.currentThread().setContextClassLoader
                (WebappLoader.class.getClassLoader());
            if (container instanceof StandardContext) {
                ((StandardContext) container).reload();
            }
        } finally {
            if (container.getLoader() != null) {
                Thread.currentThread().setContextClassLoader
                    (container.getLoader().getClassLoader());
            }
        }
    } else {
        closeJARs(false);
    }
}

看判断条件reloadable和modified(),reloadable即为是否开启热部署,而modified()则是当前文件是否有修改的判断,当开启了热部署且有修改就会调用Context的reload方法进行重加载,实现web服务的热部署

fireLifecycleEvent:对容器的监听对象发送Lifecycle.PERIODIC_EVENT事件,调用LifecycleListener的lifecycleEvent。

public void fireLifecycleEvent(String type, Object data) {

    LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
    LifecycleListener interested[] = listeners;
    for (int i = 0; i < interested.length; i++)
        interested[i].lifecycleEvent(event);

}

好的,前面说到StandardHost通server.xml配置了HostConfig监听器,那么进入HostConfig查看对该事件的响应方法lifecycleEvent

public void lifecycleEvent(LifecycleEvent event) {

    // Identify the host we are associated with
    try {
        host = (Host) event.getLifecycle();
        if (host instanceof StandardHost) {
            setCopyXML(((StandardHost) host).isCopyXML());
            setDeployXML(((StandardHost) host).isDeployXML());
            setUnpackWARs(((StandardHost) host).isUnpackWARs());
            setContextClass(((StandardHost) host).getContextClass());
        }
    } catch (ClassCastException e) {
        log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
        return;
    }

    // 看事件与其对应的方法调用
    if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
        check();
    } else if (event.getType().equals(Lifecycle.START_EVENT)) {
        start();
    } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
        stop();
    }
}

可以看到Lifecycle.PERIODIC_EVENT事件会调用其check方法。

protected void check() {
    if (host.getAutoDeploy()) {//这个条件对应这server.xml的Host配置的autoDeploy="true"
        DeployedApplication[] apps =
            deployed.values().toArray(new DeployedApplication[0]);
        for (int i = 0; i < apps.length; i++) {
            if (!isServiced(apps[i].name))
                //资源查找
                checkResources(apps[i], false);
        }
        if (host.getUndeployOldVersions()) {
            checkUndeploy();
        }
        //部署
        deployApps();
    }
}

很显然,如果server.xml的Host配置了能够自动部署,那么会调用deployApps方法。也就是说tomcat每10s会调用一次deployApps,完web application的部署

protected void deployApps() {

    File appBase = appBase();
    File configBase = configBase();
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // Deploy XML descriptors from configBase
    deployDescriptors(configBase, configBase.list());
    // Deploy WARs
    deployWARs(appBase, filteredAppPaths);
    // Deploy expanded folders
    deployDirectories(appBase, filteredAppPaths);
}

可以看到可以通过xml,war包等直接部署!

protected void deployDescriptor(ContextName cn, File contextXml) {
    ……
    Context context = null;
    ……
    Class<?> clazz = Class.forName(host.getConfigClass());//默认值:ContextConfig
    LifecycleListener listener =
        (LifecycleListener) clazz.newInstance();
    context.addLifecycleListener(listener);
    ……
    host.addChild(context);
    ……
}

而部署的过程,其实就是创建了Context对象,并添加到Host中。
此外从HostConfig部署Contex的方法中可以看到,有3中方式部署war包:

  • 1 在server.xml的Host标签中声明Context标签
  • 2 将war包放入webapps中
  • 3 context.xml配置方式

至此,我们已经知道了Engine、Host、Context的加载了,同时也知道了tomcat是怎么加载我们的web服务,是怎么实现的热部署。那么接下来就剩下最后一个Wrapper的加载了。
很捉急,在server.xml中没有关于Wrapper的初始化加载,那么在哪里呢?
同样回到,上面的deployApps()方法中,在其三种部署方式中都有一节代码

Class<?> clazz = Class.forName(host.getConfigClass());//默认值:ContextConfig
    LifecycleListener listener =
        (LifecycleListener) clazz.newInstance();
    context.addLifecycleListener(listener);

这段代码的作用是给Context容器添加了ContextConfig监听器。而在Context的startInternal方法中,发送了监听事件:

fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

ContextConfig监听到该事件,调用configureStart方法,在该方法中调用webConfig(),webConfig完成web.xml解析,生成servlet、filter等信息,并配置加载Wrapper。
通过对ContextConfig的分析可以知道,Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。

好了,到了现在,从Engine---Host---Contex----Wrapper这个链路上的容器初始化已经完成。接下来看看Connector的初始化与启动过程。

系列文章直达:

初始化与启动:https://yq.aliyun.com/articles/20169?spm=0.0.0.0.4yGfpo
容器:https://yq.aliyun.com/articles/20172?spm=0.0.0.0.2uPEZi
连接器:https://yq.aliyun.com/articles/20175?spm=0.0.0.0.2uPEZi
一个http请求的经历:https://yq.aliyun.com/articles/20177?spm=0.0.0.0.2uPEZi
重要的设计模式:https://yq.aliyun.com/articles/20179?spm=0.0.0.0.2uPEZi

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