浅读tomcat架构设计之tomcat容器Container(3)

  Container是tomcat容器的接口,接口位置org.apache.catalina.Container

  Container一共有4个子接口,分别是:Engine,Host,Context,Wrapper和一个默认实现类ContainerBase:

  截图看下就会清晰很多:

    不知道为啥,截图后画质那么的马赛克...    

 

 

  每个子接口都是一个容器,这四个子容器都有一个对应的Standard***实现类:

    

 

 

 

  红线标出来的Standard***对应的是具体子容器的的实现类,并且这些子容器实现类都继承自ContainerBase类,以其中一个子容器实现类为例:

  

 

 

   而ContainerBase又继承自LifecycleMBeanBase

    

 

 

   所以四个子容器都是tomcat生命周期管理模式,看完子容器和子容器的接口实现类后,给我的感觉就是无限套娃

  下面讲讲四个子容器的作用含义,它们和我们的tomcat部署项目/发布项目息息相关,具有很强的关联性

    Engine:引擎,用于管理多个站点,一个Service最多只能有一个Engine 

      Host:代表一个站点,也可以叫虚拟主机,通过配置host就可以添加站点  www.xxx.com就是Host,代表我们的站点,oa.xxx.com是我们的另外一个Host,他指向了另一个站点

    Context:代表一个应用程序,对应着我们开发的系统,或者是WEB-INF目录及下面的web.xml文件,通俗易懂点来说www.xxx.com/xxx,xxx应用就是Context

    Wrapper:每个Wrapper封装着一个Servlet  其实就是web.xml中配置的Servlet  

   4种容器的配置方法:

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

    

<Engine name="Catalina" defaultHost="localhost">

 

    

 

 

  我安装tomcat,默认Engine的设置是Catalina,默认的Host站点使用是localhost,这里可以自己设置成别的Host站点.

  Context有三种配置方法 (1)通过文件配置 (2)war包上传放到Host目录下,tomcat会自动查找自解压 (3)将应用的文件夹放在Host目录下,tomcat会自动查找并添加到Host中

  tocmat部署不想长篇大论讲了, tomcat部署参考:https://www.cnblogs.com/ysocean/p/6893446.html ,里面讲了好几种方法.

    Wrapper的配置就是我们web.xml中配置的Servlet,一个Servlet对应一个Wrapper.

  

  Container的启动是通过init和start方法来完成的,Container的四个子容器的共同父类都是ConatinerBase,ConatinerBase类定义了两个方法,分别是:startInternal和initInternal

  先聊聊ContainerBase类的这两个方法的具体实现:

    org.apache.catalina.core.ContainerBase:

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

  initInternal方法主要是先是初始化,创建对象,然后创建一个线程池来管理启动和关闭内部线程

  ThreadPoolExecutor继承自Executor用于管理线程,一直往上回溯,会发现是Executor

  

 

 

   接着看startInternal方法:

protected synchronized void startInternal() throws LifecycleException {
        this.logger = null;
        this.getLogger();
        Cluster cluster = this.getClusterInternal();
        if (cluster != null && cluster instanceof Lifecycle) {
            ((Lifecycle)cluster).start();
        }

        Realm realm = this.getRealmInternal();
        if (realm != null && realm instanceof Lifecycle) {
            ((Lifecycle)realm).start();
        }

        Container[] children = this.findChildren();
        List<Future<Void>> results = new ArrayList();

        for(int i = 0; i < children.length; ++i) {
            results.add(this.startStopExecutor.submit(new ContainerBase.StartChild(children[i])));
        }

        boolean fail = false;
        Iterator i$ = results.iterator();

        while(i$.hasNext()) {
            Future result = (Future)i$.next();

            try {
                result.get();
            } catch (Exception var9) {
                log.error(sm.getString("containerBase.threadedStartFailed"), var9);
                fail = true;
            }
        }

        if (fail) {
            throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"));
        } else {
            if (this.pipeline instanceof Lifecycle) {
                ((Lifecycle)this.pipeline).start();
            }

            this.setState(LifecycleState.STARTING);
            this.threadStart();
        }
    }

    startInternal方法很长,拆分下代码讲解:

   

    Cluster cluster = this.getClusterInternal();
        if (cluster != null && cluster instanceof Lifecycle) {
            ((Lifecycle)cluster).start();
        }

        Realm realm = this.getRealmInternal();
        if (realm != null && realm instanceof Lifecycle) {
            ((Lifecycle)realm).start();
        }

 

  如果cluster和realm不为null,并且cluster/realm引用指向LIfecycle,就调用start方法

    继续往下拆分看:

        Container[] children = this.findChildren();
        List<Future<Void>> results = new ArrayList();

        for(int i = 0; i < children.length; ++i) {
            results.add(this.startStopExecutor.submit(new ContainerBase.StartChild(children[i])));
        }

  这部分代码是调用所有子容器的start方法来启动子容器,跟进ContainerBase.StartChild函数看看:

    

 

 

  继续往下拆分代码:

    

      if (fail) {
            throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"));
        } else {
            if (this.pipeline instanceof Lifecycle) {
                ((Lifecycle)this.pipeline).start();
            }

            this.setState(LifecycleState.STARTING);
            this.threadStart();
        }

  这段代码大致就是调用管道中start方法,启动完成后将生命周期设置成LifecycleState.STARTING,最后启动后台线程:

  

 

 

   Engine:

    前面我们讲了,四大子容器的实现类是Standard***类.那么Engine的默认实现类是StandardEngine类:

    org.apache.catalina.core.StandardEngine:

   如果使用Engine就会调用Engine默认实现类StandardEngine的initInternal和startInternal方法:

   我们来看看:

    

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

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

        super.startInternal();
    }

 

 代码不是很多,发现他是调用他父类的方法,他的父类是ContainerBase,我们一开始就分析过ContainerBase类的initInternal和startInternal方法了.

   

  Host:

  Host接口的默认实现类是org.apache.catalina.core.StandardHost:

  跟进类,发现StandardHost没有重写父类的initInternal,只有startInternal方法被重写:

    如果他没有,初始化会默认会调用它父类的initInternal

  startInternal方法代码如下:

    

     protected synchronized void startInternal() throws LifecycleException {
        String errorValve = this.getErrorReportValveClass();
        if (errorValve != null && !errorValve.equals("")) {
            try {
                boolean found = false;
                Valve[] valves = this.getPipeline().getValves();
                Valve[] arr$ = valves;
                int len$ = valves.length;

                for(int i$ = 0; i$ < len$; ++i$) {
                    Valve valve = arr$[i$];
                    if (errorValve.equals(valve.getClass().getName())) {
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    Valve valve = (Valve)Class.forName(errorValve).newInstance();
                    this.getPipeline().addValve(valve);
                }
            } catch (Throwable var8) {
                ExceptionUtils.handleThrowable(var8);
                log.error(sm.getString("standardHost.invalidErrorReportValveClass", new Object[]{errorValve}), var8);
            }
        }

        super.startInternal();
    }

 

  代码也是好多一大块.  

    拆开一部分:

    截图的是重点,主要做的就是获取管道的值,然后判断管理里面是否有指定的Valve

    

 

 

  如果没找到管道值,就调用addValve方法,加入到管道中,代码如下:

  

      if (!found) {
                    Valve valve = (Valve)Class.forName(errorValve).newInstance();
                    this.getPipeline().addValve(valve);
                }

  这是个重点,对我们后续讲解tomcat 内存马提供很大的帮助,valve的内存马注入是一定要调用addValue的,否则匹配不到!

  Host的启动除了调用StandardHost的startInternal方法,还会调用HostConfig中的一些方法:

    对应位置:org.apache.catalina.startup.HostConfig

    这个类主要作用就是Host站点的配置

    比较重要的就是部署应用了,deployApps方法,代码如下

  protected void deployApps() {
        File appBase = this.host.getAppBaseFile();
        File configBase = this.host.getConfigBaseFile();
        String[] filteredAppPaths = this.filterAppPaths(appBase.list());
        this.deployDescriptors(configBase, configBase.list());
        this.deployWARs(appBase, filteredAppPaths);
        this.deployDirectories(appBase, filteredAppPaths);
    }

  上面介绍了三种方法来部署tomcat项目,

   (1)通过xml:

    

 

 

  (2):war包部署

  

 

 

 (3)文件夹部署:

   

 

   Context:

  Context的默认实现类是org.apache.catalina.core.StandardContext:

  startInternal方法如下:

    拆取比较重要部分,方法内容实在是太多了:    

      if (ok && !this.listenerStart()) {
                log.error("Error listenerStart");
                ok = false;
            }

            if (ok) {
                this.checkConstraintsForUncoveredMethods(this.findConstraints());
            }

            try {
                Manager manager = this.getManager();
                if (manager != null && manager instanceof Lifecycle) {
                    ((Lifecycle)manager).start();
                }
            } catch (Exception var19) {
                log.error("Error manager.start()", var19);
                ok = false;
            }

            if (ok && !this.filterStart()) {
                log.error("Error filterStart");
                ok = false;
            }

            if (ok && !this.loadOnStartup(this.findChildren())) {
                log.error("Error loadOnStartup");
                ok = false;
            }

  主要含义就是调用了web.xml中定义的Listener,另外还初始化了filterStart和loadOnStartup

  filterStart:org.apache.catalina.core.StandardContext:

  

 

 

  使用filterConfigs存储键值对,前面简单看了hostConfig,filterConfig同理:

 

 

 

  他的实例类是:org.apache.catalina.core.ApplicationFilterConfig,等我讲Filter内存马的时候再详细解释它,这个先放一边.

 

  

 

  Wrapper:

 

    Wrapper是封装Servlet的包装器,Wrapper的默认实现类,没有重写initInternal,他默认调用父类的initInternal方法,startInternal方法代码如下:

 

    org.apache.catalina.core.StandardWrapper:

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

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

    }

 

  代码很简单,(1)使用broadcaster发送消息 (2)调用父类的startInternal (3)调用setAvailate方法,设置Servlet的有效时间

 

   

  

 

        

  

  

 

  

posted @ 2021-07-09 10:01  飘渺红尘✨  阅读(285)  评论(0编辑  收藏  举报
Title