tomcat学习之二:tomcat的结构

本文介绍了Tomcat的结构,从server.xml文件解析出server、service、connector、Engine、Host、Context、Wrapper等组件,详细阐述了它们的功能和相互关系。在源码层面,探讨了Server、Service、Container等核心组件的实现,揭示了Tomcat的工作原理。通过阅读,读者将能深入理解Tomcat的内部架构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、从server.xml看tomcat的结构

      tomcat的结构可以从server.xml文件看出一二,例如server包含service,而service包含engine,engine中包含host。 去除监听器、用户登录等配置,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">
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
		<Context path = "/word" docBase = "G:\java\web\hello" />
      </Host>
    </Engine>
  </Service>
</Server>
转换成图如下图所示:

        各个结构含义如下:

              server:代表一个tomcat实例,同时所有的配置都应该在server标签之间,经过解析后,server标签会生成对应的org.apache.catalina.Server对象。

              service:代表一个服务,同样解析后会生成对应的org.apache.catalina.Service。

              connector:连接器,作用是监听端口,接收socket请求,并交由容器处理。较老版本的tomcat会生成org.apache.catalina.Connector对象,这是一个接口,新版本的会生成对应的org.apache.catalina.connector.Connector对象,这是一个类。如上图,一个Service是可以有多个connector的,只要监听的端口不同。

        容器:

              Engine:引擎,一个引擎可以包含多个Host,解析后会生成对应的org.apache.catalina.Engine对象。

              Host:主机,例如访问的localhost就是tomcat的默认配置。解析后会生成对应的org.apache.catalina.Host对象。

              Context:应用上下文,具体部署的服务。解析后会生成对应的org.apache.catalina.Conext对象。

        其他组件:

              Valve:阈,在容器处理请求前,会由一个叫做Pipeline的组件调用Valve,容器可以有多个Valve,在tomcat4.x和5.x,是使用Valve数组的形式来循环遍历调用的,但tomcat7之后是基于链的形式调用的。 解析后会生成相应的org.apache.catalina.Valve对象。

             Listener:监听器,tomcat在启动、停止等生命周期的相应操作。解析后会生成org.apache.catalina.LifecycleListener对象。

        以上就是基本可以从server.xml文件中读出的配置,但还有一些其他组件配置,例如Host中的Realm。

2、从源码看tomcat的结构

        先从 tomcat官方网址下载源码解压后将java文件夹的代码导入到IDE中,再将servlet-api的依赖加入到项目中。
        ps : 如果IDE提示无法导入,可以在IDE新建一个project,然后将java中的代码拷贝过去。
       在查看源码前,先牢记一点:
                如果遇到类XXXBase,则表示该类为XXX接口的基本实现,如果遇到类StandardXXX,则表示该类为XXX接口的标准实现。
2.1、 Server组件
       tomcat的启动类为org.apache.catalina.startup.BootStrap,该类会在init方法中启动Catalina类,会在Catalina的load()方法中解析server.xml,生成Catalina类持有的Server对象。可以认为Server代表tomcat,点开Server,可以发现Server继承了Lifecycle接口。如图:
public interface Server extends Lifecycle {
	//接口方法略
}
        编写Servlet应该知道,Servlet有初始化、销毁、服务的生命周期。在tomcat中,用org.apache.catalina.Lifecycle来表示生命周期,用org.apache.catalina.LifecycleListener接口表示生命周期监听器,用org.apache.catalina.LifecycleEvent表示生命周期的各个事件,比如启动、停止、销毁、启动前等事件。同时tomcat还添加了许多XXSuport类来简化组件处理监听器事件和生命周期事件。

       编写servlet时,servlet的初始化以及销毁将由tomcat来调用相应方法完成,具体一点应该为servlet的父容器来调用。tomcat内部就是采用这种方式来完成容器组件的初始化与销毁:子容器的销毁以及初始化交由父容器调用,这样可以保证父容器在启动前,其子容器已经启动完成,父容器在销毁前,其子容器已经销毁完成。
      
       Server的标准实现类为StandardServer,该类部分源码如下所示:
 /**
  * The set of Services associated with this Server.
  */
 private Service services[] = new Service[0];
 private final Object servicesLock = new Object();
      添加service源码:
@Override
public void addService(Service service) {

    service.setServer(this);

    synchronized (servicesLock) {
        Service results[] = new Service[services.length + 1];
        System.arraycopy(services, 0, results, 0, services.length);
        results[services.length] = service;
        services = results;

        if (getState().isAvailable()) {
            try {
                service.start();
            } catch (LifecycleException e) {
                // Ignore
            }
        }

        // Report this property change to interested listeners
        support.firePropertyChange("service", null, service);
    }

}
     虽然初始定义services的length为1,但从addService方法可以看出,每次调用该方法都会将数组扩容到原长度 + 1,并将原数组数据复制过去。
     亦可以得出Server中可以存在多个Service。除此之外,Server还提供了findService(String)用于寻找Service。
     
     启动子容器如下:依次启动service
@Override
protected void startInternal() throws LifecycleException {

    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);

    globalNamingResources.start();
        
    // Start our defined Services
    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }
    }
}
2.2 、Service组件
     继续看Service接口以及其标准实现类StandardService,Service是一个分类,可以使得一个或多个连接器共享一个 容器处理连接器接收的请求。这是Service接口的源码文档,这儿提到了容器,在tomcat中,用org.apache.catalina.Container表示容器。果不其然,在StandardService中找到了定义容器的语句。
/**
  * The Container associated with this Service. (In the case of the
  * org.apache.catalina.startup.Embedded subclass, this holds the most
  * recently added Engine.)
  */
protected Container container = null; 
     代码表示出Service里面只能有一个容器,点开Container的继承树:

       可以发现一些熟悉的类,Engine,Host等,但实际上Service里面的容器只能是Engine,因为它的setContainer方法如下:

@Override
public void setContainer(Container container) {

    Container oldContainer = this.container;
    if ((oldContainer != null) && (oldContainer instanceof Engine))
        ((Engine) oldContainer).setService(null);
    this.container = container;
    if ((this.container != null) && (this.container instanceof Engine))
        ((Engine) this.container).setService(this);
    if (getState().isAvailable() && (this.container != null)) {
        try {
            this.container.start();
        } catch (LifecycleException e) {
            // Ignore
        }
    }
    if (getState().isAvailable() && (oldContainer != null)) {
        try {
            oldContainer.stop();
        } catch (LifecycleException e) {
            // Ignore
        }
    }

    // Report this property change to interested listeners
    support.firePropertyChange("container", oldContainer, this.container);

}
     它根本无法添加除Engine类型外的其它容器类型。
     
      同时配置文件中的connector连接器也是在这里定义的:
/**
 * The set of Connectors associated with this Service.
 */
protected Connector connectors[] = new Connector[0];
private final Object connectorsLock = new Object();
     所以到这儿Service的内部结构很清楚了:一个Engine + 多个Connector。
2.3、Engine容器
          引擎Engine接口以及其标准实现类StandardEngine,StandardEngine还继承了ContainerBase。ContainerBase类实现了基本的添加容器,移除容器功能,添加阈等弄能。其中关于子容器的定义为:
 /**
  * The child Containers belonging to this Container, keyed by name.
  */
protected HashMap<String, Container> children =
     new HashMap<String, Container>();
        但Engine中只能添加Host类型,原因在于StandardEngine覆盖了ContainerBase的addChild方法:

/**
 * Add a child Container, only if the proposed child is an implementation
 * of Host.
 *
 * @param child Child container to be added
 */
@Override
public void addChild(Container child) {

    if (!(child instanceof Host))
        throw new IllegalArgumentException
            (sm.getString("standardEngine.notHost"));
    super.addChild(child);

}
2.4、Host容器
       Host组件实现类为StandardHost,server.xml中的Host标签会解析生成一个StandardHost对象,其中覆盖了addChild方法,代码如下:
@Override
public void addChild(Container child) {

    child.addLifecycleListener(new MemoryLeakTrackingListener());

    if (!(child instanceof Context))
        throw new IllegalArgumentException
            (sm.getString("standardHost.notContext"));
    super.addChild(child);

}
      所以Host的结构为:多个Context容器。除此之外,也可以改变某些属性来改变tomcat的行为,例如autoDeploy=true改为false,则tomcat不会自动部署webapps下的war,但还是会解压war包。如果更改reloadable="true",tomcat则会在源代码改变的情况下重启tomcat。
2.5、Context容器
         一个Context容器对应一个应用,Context容器的标准实现类为StandardContext类,Context容器只能添加Wrapper容器作为子容器。addChild方法代码与Host中的代码没有太大不同,所以这儿就不贴代码了。
        由于Context对应着应用,该类存在着数十个属性,所以应用中存在的配置基本都能在该类中找到对应的属性,例如:过滤器集合,编码,缓存最大大小、cookie路径以及web.xml定义的welcome files等等。与Host容器也有相同之处,例如都支持reloadable属性。
2.6、Wrapper容器
          Wrapper容器是最后一个容器组件,同时也是最小的容器组件,因为它无法添加子容器,调用它的addChild方法会抛出IllegalStateException。一个Wrapper对象是与一个Servlet对应的。它负责Servlet的初始化、停止以及销毁,所以可以认为它的子容器为Servlet,而且只能为Servlet。web.xml中配置的servlet初始化参数也会放在类中parameters属性中,配置文件中的属性你都可以在该类中一一找到。Wrapper分配一个Servlet实例的时候,会先检查servlet是否实现了SingleThreadModel,如果实现了,则多次调用allocate方法只加载该Servlet一次,多次请求返回相同的实例。除此之外Wrapper还在内部维持了一个Stack<Servlet>用于保存Servlet实例,虽然Stack继承了Vector,且Vector是线程安全的,在多线程情况下确实线程不安全的,所以可以看到allocate方法中有synchronized代码块保证同一时刻只能有一个线程能获取到Servlet实例。
@Override
public Servlet allocate() throws ServletException {

    // If we are currently unloading this servlet, throw an exception
    if (unloading) {
        throw new ServletException(sm.getString("standardWrapper.unloading", getName()));
    }

    boolean newInstance = false;
        
    // If not SingleThreadedModel, return the same instance every time
    if (!singleThreadModel) {
        // Load and initialize our instance if necessary
        if (instance == null || !instanceInitialized) {
            synchronized (this) {
                 if (instance == null) {
                    try {
                        if (log.isDebugEnabled()) {
                            log.debug("Allocating non-STM instance");
                        }

                        // Note: We don't know if the Servlet implements
                        // SingleThreadModel until we have loaded it.
                        instance = loadServlet();
                        newInstance = true;
                        if (!singleThreadModel) {
                            // For non-STM, increment here to prevent a race
                            // condition with unload. Bug 43683, test case
                            // #3
                            countAllocated.incrementAndGet();
                        }
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                    }
                }
                if (!instanceInitialized) {
                    initServlet(instance);
                }
            }
        }

        if (singleThreadModel) {
            if (newInstance) {
                // Have to do this outside of the sync above to prevent a
                // possible deadlock
                synchronized (instancePool) {
                    instancePool.push(instance);
                    nInstances++;
                }
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("  Returning non-STM instance");
            }
            // For new instances, count will have been incremented at the
            // time of creation
            if (!newInstance) {
                countAllocated.incrementAndGet();
            }
            return instance;
        }
    }

    synchronized (instancePool) {
        while (countAllocated.get() >= nInstances) {
            // Allocate a new instance if possible, or else wait
            if (nInstances < maxInstances) {
                try {
                    instancePool.push(loadServlet());
                    nInstances++;
                } catch (ServletException e) {
                    throw e;
                } catch (Throwable e) {
                    ExceptionUtils.handleThrowable(e);
                    throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                }
            } else {
                try {
                    instancePool.wait();
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("  Returning allocated STM instance");
        }
        countAllocated.incrementAndGet();
        return instancePool.pop();
    }
}
         Wrapper内部维护的Servlet实例个数是受到maxInstances变量限制的,默认是20,也就是说,如果同一时刻有超过20个以上的线程向同一个Servlet发起请求,只有最先的20个请求会被处理,其他的请求在其他请求完成处理前只能等待。

2.7、connector组件
        我认为连接器组件是tomcat最核心的组件之一。
        在How Tomcat Works书中写到tomcat4.x连接器是这样定义的:
        a)、必须实现接口prg.apache.catalina.Connector。
        b)、必须创建请求对象,请求对象必须实现org.apache.catalina.Request接口。
        c)、必须创建响应对象,请求对象必须实现org.apache.catalina.Response接口。
       然而在tomcat7中,Connector已经成为一个类,而且将接受请求,将请求交给容器处理等操作委托给了ProtocolHandler对象处理,ProtocolHandler层次结构如下图:


        按照协议分类处理,更细一点的分类又分为了IO处理和NIO处理。
        同时Request与Response类也被移到了org.apache.catalina.connector包中,且也不再是接口,而是分别继承了HttpServletRequest 与 HttpServletResponse的类。 
2.8、Pipeline组件
        这个组件的主要目的是调用容器的阈,以及对容器阈的管理和调用,标准实现类为StandardPipeline。它包括对阈的添加、移除,设置基本阈等等,同时它是和容器绑定的,打开各个容器的实现类,可以发现都有一个StandardPipeline对象,且该对象的容器为当前容器。部分变量定义如下:
/**
 * The basic Valve (if any) associated with this Pipeline.
 */
protected Valve basic = null;

/**
 * The Container with which this Pipeline is associated.
 */
protected Container container = null;

/**
 * The first valve associated with this Pipeline.
 */
protected Valve first = null;
          调用getValve()时,如果没有设置first,则会返回basic。
2.9、valve组件
          valve组件可以说是tomcat最核心的组件之一,它由pipeline组件调用。在前面connector组件小节说到了connector将接受请求,处理请求交给了ProtocolHandler处理,而ProtocolHandler在process方法中调用了CoyoteAdapter的service方法,请求的解析等都在这个方法中完成。 CoyoteAdapter是与Connector绑定的,Connector又是与Service绑定的,如果上面的service方法解析成功且没有发生错误,那么将会调用与Connector相绑定的service的容器的pipeline组件的invoke方法,而pipeline的invoke方法就是调用valve组件的invoke方法,这种调用会将request以及response对象当做参数一直传下去。 前面的4个容器对象都有相应的StandardXXXValve,而经过前面的service方法处理,可以从request对象中获取当前请求的Host、Context以及Wrapper组件,在相应的容器vavle中,父容器会对相应的子容器是否存在等判断,判断完成再调用子容器的pipeline组件。于是,正常处理一个请求时,tomcat内部结构调用如下图:

       每一步向下调用都会有判断,判断失败则请求处理中断。
       Pipeline调用Valve在tomcat4.x与tomcat5.x中如下:
public void invokeNext(Request request, Response response)
      throws IOException, ServletException {

      int subscript = stage;
      stage = stage + 1;

      // Invoke the requested Valve for the current request thread
      if (subscript < valves.length) {
          valves[subscript].invoke(request, response, this);
      } else if ((subscript == valves.length) && (basic != null)) {
           basic.invoke(request, response, this);
      } else {
           throw new ServletException
              (sm.getString("standardPipeline.noValve"));
      }

}
       而在tomcat7中则为:
@Override
public void invoke(Request request, Response response)
   throws IOException, ServletException {

   pipeline.getFirst().invoke(request, response);

}
      在这儿选取AccessLogValve的invoke代码为例:
@Override
public void invoke(Request request, Response response) throws IOException,ServletException {
    getNext().invoke(request, response);
}
     可以看到最明显的区别tomcat7将Valve调用从循环调用改为了链式调用。
     最终调用servlet的service方法并不是Wrapper对象,当请求走到Wrapper对象的Valve时,StandardWrapperValve将会调用ApplicationFilterFactory生成一个ApplicationFilterChain对象,在创建ApplicationFilterChain过程中将会为当前request对象生成一组ApplicationFilterConfig(内部有Filter变量声明),然后调用Filter,最后再由ApplicationFilterChain调用Servlet的service方法。所以Filter总是在Servlet执行前被调用。
2.10、tomcat结构图
            介绍了tomcat大多数组件,那么刚开始的tomcat结构图可以改为下图:


3、总结

        tomcat的组件除了上面提到的之外,还有负责日志的日志组件,负责加载class文件的类加载器classLoader,负责解析server.xml等配置文件的degister等组件。除此外还有解析jsp、发布webService服务、session管理,用户登录管理等。

         如果对本文有疑问或发现错误,欢迎下面留言评论指正,谢谢!φ(>ω<*) 
         最后,感谢你的阅读!
       


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值