欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

tomcat container

程序员文章站 2022-07-14 10:49:48
...

ContainerBase的结构


       Container是tomcat中容器的接口,通常使用的servlet封装在其子接口Wrapper中。Container共有4个子接口Engine、host、context、Wrapper和一个默认实现类ContainerBase,每个子接口都是一个容器,这四个子容器都有一个对应的StandardXXX实现类,并且这些实现类都继承ContainerBase类。Container还继承Lifecycle接口,ContainerBase间接继承LifecycleBase,所以Engine、Host、Context、Wrapper4个子容器都符合前面文章提的Tomcat生命周期管理模式:

tomcat container

Container的四个子容器


       Container的子容器Engine、Host\Context、Wrapper是逐层包含的关系,其中Engine是最顶层,每个Service最多只有一个Engine,Engine里面可以包含多个Host,每个Host下可以有多个Context,每个Context下可以有多个Wrapper:

tomcat container
- Engine:用来管理多个站点,一个service只有一个Engine
- Host:代表一个站点,也叫虚拟主机,通过配置Host可以添加站点
- Context:代表应用程序,对应平时开发的程序
- Wrapper:每个Wrapper封装着一个Servlet


       Context和Host的区别是Context表示一个应用,webapps下的每个目录都代表一个应用,而这个webapps是一个站点。

4个容器的配置方法


       Engine和Host的配置都在conf/server.xml中,server.xml文件是 tomcat最重要的配置文件,tomcat大部分功能都可以在这个文件配置:

<?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">
   </Host>
   </Engine>
</Service>
</Server>


       这里首先定义了一个Server,在8005端口监听“SHUTDOWN”关闭命令;Server中定义了名叫Catalina的service,Service中定义了两个Connector,一个是HTTP协议,一个是AJP协议(用于集成,如与Apache集成);Service中还定义了一个名为Catalina的Engine,Engine中定义了名为localhost的host。

       Engine和Host直接用Engine、Host标签定义到相应位置即可。Host中name代表域名,所以可以通过localhost访问,appBase属性指定站点的位置,如默认站点为webapps目录,unpackWARs表示是否自动解压war文件,autoDeploy表示是否自动部署,如果为true在tomcat运行过程中如果webapps加入新应用将自动部署并启动。Host有个Alias子标签,可以通过这个标签定义别名,如果多个域名访问同一个站点可以使用它定义:

<Host name="www.xxx.com"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
    <Alias>xxx.com</Alias>   
</Host>


       Engine在定义的时候有个defaultHost属性,它表示接收到的域名如果所有的Host的name和Alias都找不到时,使用默认Host,例如我们配置是localhost,如果把这个属性删除,那么使用127.0.0.1就访问不到了。

       Context配置有三种方式:
- 文件配置
- 将war应用直接放到Host目录下,tomcat会自动查找并添加到Host中
- 将应用文件夹直接放到Host目录中,tomcat也会自动查找


       Context通过配置文件共有五个位置可以配置:
- conf/server.xml文件的Context标签
- conf/[enginename]/[hostname]/目录下以应用命名的xml文件
- 应用自己的/META-INF/context.xml文件
- conf/context.xml文件
- conf/[enginename]/[hostname]/context.xml.default文件


       前三个用于配置单独的应用,后两个配置是context共享的,第四个是整个tomcat共享,第五个是对应站点下共享。server.xml在tomcat启动时才会重新加载,所以使用时要慎重。

       Wrapper的配置就是我们在web.xml中配置的Servlet,一个Servlet对应一个Wrapper,conf/web.xml可以配置全局的Wrapper,处理Jsp的JspServlet就配置在这里,所以不用配置Jsp就可以处理Jsp请求。

       同一service中的所有站点共享Connector,所以监听的端口一样,如果想要监听不同端口,可以通过配置不同的Service实现,service配置在conf/server.xml中。

Container启动


       Container的启动时通过init和start完成的,前面文章分析过,这两个方法在启动时会被service调用。Container也是按照tomcat生命周期管理的,init和start方法调用initInternal和startInternal来具体处理。container与tomcat整体结构启动有些区别:
- Container四个子容器有一个共同的父类ContainerBase,Container的initInternal和startInternal方法处理通用内容,具体容器可以添加自己的内容
- 顶层的容器init是被service调用的,子容器的init不是容器中逐层调用,而是在执行start是判断状态没有初始化才会调用
- start方法除了父容器的startInternal方法调用,还在父容器添加子容器的addChild方法调用,因为Context和Wrapper是动态添加的

ContainerBase


       ContainerBase的initInternal方法主要初始化ThreadPoolExecutor类型的startStopExecutor属性,用于启动和关闭线程:

protected void initInternal()
    throws LifecycleException
  {
    BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue();
    this.startStopExecutor = new ThreadPoolExecutor(getStartStopThreadsInternal(), getStartStopThreadsInternal(), 10L, TimeUnit.SECONDS, startStopQueue, new StartStopThreadFactory(getName() + "-startStop-"));

    this.startStopExecutor.allowCoreThreadTimeOut(true);
    super.initInternal();
  }


       ThreadPoolExecutor继承于Executor用于管理线程池,这里没有设置生命周期的相应状态,如果具体容器也没有设置相应的生命周期状态,那么即使调用了init方法进行初始化,在start启动前也会调用init方法:

       ContainerBase的startInternal方法主要做了五件事:
- 如果有Cluster和Realm则调用其start方法
- 调用所有子容器的start方法启动子容器
- 通过管道中的Value的start方法启动管道
- 启动完成后将生命周期状态设置为Lifecycle.STARTING状态
- 启用后台线程定时处理一些事情

protected synchronized void startInternal()
    throws LifecycleException
  {
    this.logger = null;
    getLogger();
    //如果有Cluster和Realm则启动
    Cluster cluster = getClusterInternal();
    if ((cluster != null) && ((cluster instanceof Lifecycle))) {
      ((Lifecycle)cluster).start();
    }
    Realm realm = getRealmInternal();
    if ((realm != null) && ((realm instanceof Lifecycle))) {
      ((Lifecycle)realm).start();
    }
    //获取所有子容器
    Container[] children = findChildren();
    List<Future<Void>> results = new ArrayList();
    for (int i = 0; i < children.length; i++) {
      //通过线程调用子容器的start方法
      results.add(this.startStopExecutor.submit(new StartChild(children[i])));
    }
    //处理子容器启动线程的Future
    boolean fail = false;
    for (Future<Void> result : results) {
      try
      {
        result.get();
      }
      catch (Exception e)
      {
        log.error(sm.getString("containerBase.threadedStartFailed"), e);
        fail = true;
      }
    }
    if (fail) {
      throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"));
    }
    //启动管道
    if ((this.pipeline instanceof Lifecycle)) {
      ((Lifecycle)this.pipeline).start();
    }
    //设置生命周期状态
    setState(LifecycleState.STARTING);

    //启动后台线程
    threadStart();
  }


       先启动Cluster和Realm,启动是通过直接调用他们的start方法。Cluster用于配置集群,server.xml有注释的参考配置,作用是同步Session,Realm是tomcat的安全域,用于管理资源的访问权限。

       子容器通过startStopExecutor调用新线程来启动,这样可以用多个线程同时启动,效率更高,具体启动过程是通过for循环对每个子容器启动一个线程,并将返回的Future保存到List,然后遍历每个Future调用其Future方法。遍历Future主要有两个作用:1、get方法是阻塞的,只有线程处理之后才会往下走,保证管道Pipeline启动之前容器已经完成启动了;2、可以处理启动过程中遇到的异常。

       启动子容器的线程类型StartChild说是一个实现了Callable的内部类,主要作用是调用子容器的start方法:

private static class StartChild
    implements Callable<Void>
  {
    private Container child;

    public StartChild(Container child)
    {
      this.child = child;
    }

    public Void call()
      throws LifecycleException
    {
      this.child.start();
      return null;
    }
  }


       startInternal方法是定义在所有容器的父类ContainerBase中的,所以所有容器启动过程中都会调用子容器的start方法来启动子容器。

       threadStart方法启动的后台线程是一个while循环,内部定期调用backgroundProcess方法处理一些事情,包含ContainerBase、StandardContext和StandardWrapper三个实现。

Engine


       Service会调用容器最顶层的init和start方法,如果使用了Engine就会调用Engine的。Engine默认实现是StandardEngine中的initInternal和startInternal:


  protected void initInternal()
    throws LifecycleException
  {
    getRealm();
    super.initInternal();
  }

  protected synchronized void startInternal()
    throws LifecycleException
  {
    if (log.isInfoEnabled()) {
      log.info("Starting Servlet Engine: " + ServerInfo.getServerInfo());
    }
    super.startInternal();
  }
Host


       Host的默认实现类Standardhost没有重写initInternal,默认调用ContainerBase的initInternal方法,startInternal实现方法如下:

 protected synchronized void startInternal()
    throws LifecycleException
  {
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
      try
      {
        boolean found = false;
        Valve[] valves = getPipeline().getValves();
        for (Valve valve : valves) {
          if (errorValve.equals(valve.getClass().getName()))
          {
            found = true;
            break;
          }
        }
        if (!found)
        {
          Valve valve = (Valve)Class.forName(errorValve).newInstance();

          getPipeline().addValve(valve);
        }
      }
      catch (Throwable t)
      {
        ExceptionUtils.handleThrowable(t);
        log.error(sm.getString("standardHost.invalidErrorReportValveClass", new Object[] { errorValve }), t);
      }
    }
    super.startInternal();
  }


       主要功能是检查Host的管道中有没有指定的Value,如果没有则添加进去。

       Host启动除了startInternal方法,还有HostConfig中对应的方法,HostConfig继承自LifecycleListener的监听器,接收到Lifecycle.START_EVENT事件时会调用start方法启动,HostConfig的start方法会检查Host站点配置的位置是否存在以及是不是目录,最后调用deployApps部署应用(xml、war、文件夹三种方式)。

Context


       Context的默认实现类StandardContext在startInternal方法调用web.xml中定义的Listener,另外初始化Filter和load-on-startup的Servlet。listenerStart、filterStart和loadOnStartup方法分别调用配置在Listener的contextInitializerd以及Filter和配置了load-on-startup中的Servlet的init方法。

       Context和Host一样有一个LifecycleListener类型的监听器ContextConfig,其中configureStart处理CONFIGUR_START_EVENT事件,这个方法调用webConfig,webConfig里面解析web.xml文件,相应创建Wrapper并使用addChild添加到Context里面。

Wrapper


       Wrapper没有重写initInternal方法,默认调用ContainerBase的initInternal,startInternal方法如下:

 protected synchronized void startInternal()
    throws LifecycleException
  {
    if (getObjectName() != null)
    {
      Notification notification = new Notification("j2ee.state.starting", getObjectName(), this.sequenceNumber++);

      this.broadcaster.sendNotification(notification);
    }
    super.startInternal();

    setAvailable(0L);
    if (getObjectName() != null)
    {
      Notification notification = new Notification("j2ee.state.running", getObjectName(), this.sequenceNumber++);

      this.broadcaster.sendNotification(notification);
    }
  }


       主要做了一下工作:
- 用broadcaster发送通知,主要用于JMX
- 调用父类ContainerBase的startInternal方法
- 调用setAvailable方法让Servlet生效

setAvailable方法是wrapper接口中的方法,其作用是设置Wrapper所包含的Servlet有效的起止时间,如果设置的将来时间,调用对应的Servlet就会出错,直到过了所设置的时间后才能正常使用,类型为long,如果设置为Long.MAX_VALUE就一直不可用。