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

Tomcat分析三—— Container分析

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

3.1 ContainerBase的结构

​ Container是Tomcat中容器的接口,通常使用的Servlet就封装在其子接口Wrapper中 。

​ Container一共有四个子接口Engine、Host、Context、Wrapper和一个默认实现类ContainerBase。每个子接口都是一个容器,四个容器都有对应的StandardXX实现类,且这些实现类都继承COntainerBase类。

​ Container还继承Lifecycle接口,且ContainerBase间接继承LifecycleBase,所以Engine、Host、Context、Wrapper这四个子容器都符合Tomcat的生命周期管理模式。
Tomcat分析三—— Container分析

3.2 Container的四个子容器

​ Container的子容器Engine、Host、Context、Wrapper是逐层包含的关系,Engine是最顶层,每个service最多只能有一个Engine,Engine里可以有多个Host,每个Host下可以有多个Context,每个Context下可以有多个Weapper。
Tomcat分析三—— Container分析

  • Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine
  • Host:代表一个站点,也叫虚拟主机,通过配置Host就可以添加站点 /localhost
  • Context:代表一个应用程序localhost/test,对应着平时开发的一个程序,或者一个WEB-INF目录及下面的web.xml文件
  • Wrapper:每个Wrapper封装着一个Servlet

3.3 四种容器的配置方法

Engine和Host的配置

都在conf/server.xml文件中,server.xml文件是Tomcat中最重要的配置文件,大部分功能都在这配置

  • 首先定义Server,8005端口监听关闭命令"SHUTDOWN"
  • Server里定义了名为Cataline的Service
  • Service定义了两个Connector:HTTP协议和AJP协议(通过AJP协议和另一个web容器进行交互,主要用于集成)
  • Service还定义了名为Catalina的Engine
  • Engine里定义了名为localhost的Host
    • defaultHost 表示请求的域名如果在所有的Host的name和Alias中都找不到时使用的默认Host,
  • Host标签
    • name 表示定义的站点可以通过localhost访问
    • appBase 指定站点的位置,默认在webapps下,
    • unpackWARs 表示是否自动解压war文件
    • autoDeploy 表示是否自动部署(运行中新加入webapp目录的应用会自动部署并启动)
<?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" URIEncoding="UTF-8" />
    <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>

Context配置

​ Context有三种配置方法

  1. 通过文件配置,通过文件配置有五个位置可以配置

    1. conf/server.xml文件中的Context标签 只有在Tomcat重启才会重新加载

    2. conf/[enginename]/[hostname]/目录下以应用命令的xml文件

    3. 应用自己的/META-INF/context.xml文件

    4. conf/context.xml文件 配置的内容在整个Tomcat*享

    5. conf/[enginename]/[hostname]/context.xml.default文件 在对应的host*享

      前三个用于配置单独的应用,后两个配置的Context是共享的

  2. 将WAR应用直接放到Host的appBase配置的目录下,Tomcat会自动查找并添加到Host中

  3. 将应用的文件夹放到HostHost的appBase配置的目录下,Tomcat也会自动查找并添加到Host中

Wrapper配置

​ 就是在web.xml中配置的Servlet,一个Servlet对应一个Wrapper。

​ 也可以在conf/web.xml配置全局的Wrapper,处理Jsp的JspServlet就配置在这里,不需要自己配置Jsp就可以处理Jsp请求了

3.4 Container的启动

​ Container启动是通过init和start方法来完成的,这两个方法会在Tomcat启动时被Service调用。

​ Container也是按照Tomcat的生命周期来管理的,init和start方法也会调用initInternal和startInternal方法具体处理,不过Container和Tomcat整体结构启动的过程稍微有些不一样

	1. Container的四个子容器有一个共同的父类ContainerBase,定义了Container容器的initInternal和startInternal方法通用处理内容,具体容器还可以添加自己的内容
	2. 除了最顶层容器的init被Service调用的,子容器的init方法并不是在容器中逐层循环调用的,而是在执行start方法时通过状态判断还没有初始化才会调用
	3. start方法除了在父容器的startInternal方法中调用,还会在父容器的添加子容器的addChild方法中调用,主要是因为Context和Wrapper是动态添加的,

ContainerBase的initInternal

​ 该方法主要初始化ThreadPoolExecutor类型的startStopExecutor属性,用于管理启动和关闭的线程。这里并没有设置生命周期的相应状态,所以如果具体容器也没有设置相应生命周期状态,那么即使已经调用init方法进行初始化,在start进行启动前也会再次调用init方法。

​ ThreadPoolExecutor继承Executor用于管理线程,特别是Runable类型的线程。

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

ContainerBase的startInternal

​ 该方法主要做了五件事

  1. 如果有cluster和Realm则调用其start方法

    cluster用于配置集群,作用同步Session;Realm是Tomcat的安全域,同来管理资源的访问权限

  2. 调用所有子容器的start方法启动子容器

    子容器使用stratStopExecutor调用新线程启动的,这样可以用多个线程同时启动,效率更高,具体是通过for循环对每个子容器启动一个线程,并将返回的Future保存在List中,然后遍历每个Future并调用其get方法。

    遍历Future有两个作用:

      1. 其get方法是阻塞的,只有线程处理完后才会向下走,保证了管道pipeline启动前容器已经启动完成了		
    
      	2. 可以处理启动过程中遇到的异常 
    
  3. 调用管道中Value的start方法来启动管道

  4. 启动完成后将生命周期状态设置为LifecycleState.STARTING状态

  5. 启用后台线程定时处理一些事

    protected synchronized void startInternal() throws LifecycleException {
        Loader loader = getLoaderInternal();
        if ((loader != null) && (loader instanceof Lifecycle))
            ((Lifecycle) loader).start();
        getLogger();
        Manager manager = getManagerInternal();
        if ((manager != null) && (manager instanceof Lifecycle))
            ((Lifecycle) manager).start();
        Cluster cluster = getClusterInternal();
        if ((cluster != null) && (cluster instanceof Lifecycle))
            ((Lifecycle) cluster).start();
        Realm realm = getRealmInternal();
        if ((realm != null) && (realm instanceof Lifecycle))
            ((Lifecycle) realm).start();
        DirContext resources = getResourcesInternal();
        if ((resources != null) && (resources instanceof Lifecycle))
            ((Lifecycle) resources).start();
       //获取所有子容器
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<Future<Void>>();
        for (int i = 0; i < children.length; i++) {
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }
        boolean fail = false;
      //处理子容器 启动线程的Future
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Exception e) {
                fail = true;
            }
        }
      //启动管道
        if (pipeline instanceof Lifecycle)
            ((Lifecycle) pipeline).start();
      //将生命周期状态设置为LifecycleState.STARTING
        setState(LifecycleState.STARTING);
      //启动后台线程
        threadStart();
    }
    

    ​ 启动子容器的线程类型StartChild是实现了Callable的内部类,作用就是调用子容器的start方法

    private static class StartChild implements Callable<Void> {
        private Container child;
        public StartChild(Container child) {
            this.child = child;
        }
        @Override
        public Void call() throws LifecycleException {
            child.start();
            return null;
        }
    }
    

    ​ threadStart方法启动的后台线程是一个while循环,内部定期调用backgroundProcess方法做些事,间隔时间通过ContainerBase的backgroundProcessorDelay属性来设置,单位事秒,小于0不启动后台线程,其backgroundProcess方法会在父容器的后台线程中调用。

    ​ backgroundProcess方法是Container接口中的一个方法,共有三个实现,分别在ContainerBase、StandardContext和StandardWrapper中,ContainerBase提供了所有容器共同的处理过程。

    ​ StandardContext和StandardWrapper的backgroundProcess方法除了处理自己相关的业务,也调用ContainerBase中的处理。

    ​ ContainerBase的backgroundProcess方法调用了Cluster、Realm和管道的backgroundProcess方法

    ​ StandardContext的backgroundProcess方法中对Session过期和资源变化进行了处理

    ​ StandardWrapper的backgroundProcess方法对Jsp生成的Servlet定期进行检查

Engine

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

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

​ 它们分别调用ContainerBase中的相应方法,initInternal方法还调用了getRealm方法,如果没有配置Realm,则使用一个默认的NullRealm

public Realm getRealm() {
    Realm configured = super.getRealm();
    if (configured == null) {
        configured = new NullRealm();
        this.setRealm(configured);
    }
    return configured;
}

Host

​ Host的默认实现类StandardHost没有重写initInternal方法,初始化默认调用ContainerBase的initInternal方法。startInternal方法如下,检查Host的管道中有没有指定的value,没有则添加进去。

​ 检查的value的类型通过getErrorReportValveClass方法获取,它返回errorReportValveClass属性,可以配置,默认是ErrorReportValve

private String errorReportValveClass = “org.apache.catalina.valves.ErrorReportValve”

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).getDeclaredConstructor().newInstance();
              	//管道中没有ErrorReportValv则将其加入管道
                getPipeline().addValve(valve);
            }
        }
    }
    super.startInternal();
}

​ Host的启动除了startInternal方法还有HostConfig中相应的方法,HostConfig继承子LifecycleListener的监听器(Engine对应的EngineConfig监听器只是做了日志记录),在接受到Lifecycle.START_EVENT事件时会调用start方法启动,HostConfig的start方法会检查配置的Host站点的位置是否存在及是不是目录,最后调用deployApps方法部署应用。

  protected void deployApps() {
        File appBase = appBase();
        File configBase = configBase();
        String[] filteredAppPaths = filterAppPaths(appBase.list());
    		// 部署XML描述文件
        deployDescriptors(configBase, configBase.list());
    		// 部署WAR文件
        deployWARs(appBase, filteredAppPaths);
    		// 部署 文件夹
        deployDirectories(appBase, filteredAppPaths);
    }
  • XML文件指conf/[enginename]/[hostname]/*.xml文件

  • WAR文件和文件夹是Host站点目录下的,会自动找出来并部署

    部署后会将部署的Context通过StandardHost的addChild方法添加到Host里面。

    StandardHost的addChild方法会调用父类ContainerBase的addChild方法,其中会调用子类(这里指Context)的start方法来启动子容器

Context

​ Context的默认实现类StandardContext在startInteral方法中调用了在web.xml定义的Listener,还初始化了其中的Filter和load-on-startup的Servlet

protected synchronized void startInternal() throws LifecycleException {
// 触发listener
if (ok) {
    if (!listenerStart()) {
        log.error(sm.getString("standardContext.listenerFail"));
        ok = false;
    }
}
// 初始化Filter
if (ok) {
     if (!filterStart()) {
       log.error(sm.getString("standardContext.filterFail"));
       ok = false;
     }
   }
// 初始化Servlets
if (ok) {
  if (!loadOnStartup(findChildren())){
    log.error(sm.getString("standardContext.servletFail"));
    ok = false;
  }
}

​ listenerStart、filterStart和loadOnStartup方法分别调用配置在Listener的contextInitialized方法及Filter和配置了load-on-startup的Servlet的init方法

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

Wrapper

​ Wrapper的默认实现类StandardWrapper没有重写initInternal方法,初始化时会默认调用ContainerBase的initInternal方法

protected synchronized void startInternal() throws LifecycleException {
    if (this.getObjectName() != null) {
        Notification notification = new Notification("j2ee.state.starting",
                                                    this.getObjectName(),
                                                    sequenceNumber++);
        broadcaster.sendNotification(notification);
    }
    super.startInternal();
    setAvailable(0L);
    if (this.getObjectName() != null) {
        Notification notification =
            new Notification("j2ee.state.running", this.getObjectName(),
                            sequenceNumber++);
        broadcaster.sendNotification(notification);
    }
}

​ 主要做了三件事

  1. 用broadcaster发送通知,用于JMX

  2. 调用了父类ContainerBase中的startInternal方法

  3. 调用setAvaliable方法让Servlet有效

    ​ 该方法是Wrapper接口的方法,作用是设置Wrapper所包含的Servlet有效的起始时间,如果设置的是将来的事件,那么调用所对应的Servlet就会产生错误,知道过了所设置的事件后才可以正常调用。

Wrapper没有XXConfig样式的LifecycleListener监听器

相关标签: tomcat