6

面试官:来说说Tomcat的启动过程是什么样子的

 3 years ago
source link: http://www.justdojava.com/2021/01/02/Tomcat/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

阿粉最近在疯狂的研究各种用的工具里面的源码实现,之前给大家都专门的去扣了一下 JDK 里面自带的exe程序,这次阿粉开始更加无聊,直接开始搞Tomcat。

1.Tomcat分析

阿粉知道作为一个 Java 资深开发人员,对 Tomcat 那是再熟悉不过了,bin目录、conf目录、webapps目录,对这些目录熟悉的简直不能再熟悉了。一言不合就是一个shutdown.sh,或者来个shutdown.bat,但是你知道你的启动startup.bat,和startup.sh他们的启动过程是什么过程么?接下来我们就开始进入分析吧。

2.Tomcat的整体结构图

这个整体结构图可不是大家想的目录结构图,目录结构图阿粉就不给大家展示了,自己去打开你的 Tomcat,里面就有你想看到目录结构图,那么整体结构图是什么样子的呢?

1.jpg

给大家解释一下这个图的意思,

  • Server:整个服务器。

  • Service:具体的服务。

  • Connector:提供Socket和request,response的连接。

  • Container:用于封装和管理Servlet,以及具体的处理请求。

这个图就把里面的包含关系说的是明明白白了,为什么这么说呢?因为一个Server中可以存在多个服务,也就是多个Service,而一个Service中可以存在多个Connector,但是只能存在一个Container,是不是就非常的清晰了呢?

3.Tomcat的启动过程

接下来我们就来看看源码里面的启动过程吧,Bootstrap类中的启动过程。

这个类的位置是在tomcat的catalina的包里面,大家看一下主方法也就是所谓的main方法,

  public static void main(String[] args) {
        //对象初始化
        if (daemon == null) {
            Bootstrap bootstrap = new Bootstrap();

            try {
                bootstrap.init();
            } catch (Throwable var3) {
                handleThrowable(var3);
                var3.printStackTrace();
                return;
            }

            daemon = bootstrap;
        } else {
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                //加载
                daemon.load(args);
                //启动
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                //停止
                daemon.stop();
            } else if (command.equals("start")) {
                daemon.setAwait(true);
                //加载并且启动
                daemon.load(args);
                daemon.start();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null == daemon.getServer()) {
                    System.exit(1);
                }

                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable var4) {
            Throwable t = var4;
            if (var4 instanceof InvocationTargetException && var4.getCause() != null) {
                t = var4.getCause();
            }

            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }

    }

main方法里面的存在也很简单,先进行init的操作,然后再执行start,也就是说,启动过程中,首先要进行初始化,然后接下来再进行启动,最后阶段在来个stop,这样才算完整嘛。

  • load方法: 其实说白了load方法就是根据server.xml文件创建Server并且调用Server的init方法进行初始化。

  • start方法:start方法很直白,启动服务器。

  • stop方法:stop方法同样,停止服务器。

在这里的start方法和stop方法调用的分别就是调用了Server内部的start和stop方法,而这三个方法都是按照图中的层级结构来的,先从Server的load,start,stop,然后Server的start再调用Service的start,而Service的start调用的就是Connector和Container的start方法了,这从外到内的启动,就可以把Tomcat完整的启动起来了。

我们在接下来就继续从外到内的启动开始分析一波。

3.1 Catalina启动过程

上面的启动入口我们已经成功找到了,那么我们就继续来,对象初始化完成后,执行了init的方法

  Bootstrap bootstrap = new Bootstrap();

            try {
                bootstrap.init();
            } catch (Throwable var3) {

就是上面的这个,如果参数为空了,那么就开始调用start了,那么start方法是什么呢?

 public void start() throws Exception {
        if (this.catalinaDaemon == null) {
            this.init();
        }

        Method method = this.catalinaDaemon.getClass().getMethod("start", (Class[])null);
        method.invoke(this.catalinaDaemon, (Object[])null);
    }

上面的start方法就是直接使用invoke的方法映射到了catalinaDaemon,也就是到了Catalina的start的方法上,

而这个Catalina的启动无非也就是调用了同样的方法,setAwait方法,load方法,start方法,

  • setAwait方法:用于设置Server启动完成时,是否进入等待,如果是true,那就等待,如果不是false,那就不进入等待。

  • load方法:创建并且初始化Server,

  • start方法:同样是启动服务器

同样的setAwait方法比较少,阿粉就不给大家看了,无非就是个判断,而load方法一定得看,

if (!this.loaded) {
            this.loaded = true;
            long t1 = System.nanoTime();
                    try {
                        inputSource.setByteStream((InputStream)inputStream);
                        digester.push(this);
                        digester.parse(inputSource);
                        break label242;
                    } catch (SAXParseException var28) {
                        log.warn("Catalina.start using " + this.getConfigFile() + ": " + var28.getMessage());
                        return;
                    } catch (Exception var29) {
                        log.warn("Catalina.start using " + this.getConfigFile() + ": ", var29);
                    }
                } finally {
                    if (inputStream != null) {
                        try {
                            ((InputStream)inputStream).close();
                        } catch (IOException var23) {
                            ;
                        }
                    }

                }

                return;
            }
 try {
    //此处同样调用的Server的init方法,
                this.getServer().init();
            } catch (LifecycleException var24) {
                if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
                    throw new Error(var24);
                }

                log.error("Catalina.start", var24);
            }

            long t2 = System.nanoTime();
            if (log.isInfoEnabled()) {
                log.info("Initialization processed in " + (t2 - t1) / 1000000L + " ms");
            }

        }

而从这里就开始进入下一步了,Server的启动过程,因为从Catalina里面已经找到了getServer的初始化方法,接下来就是要进行Server的初始化,然后加载,然后启动的过程了。

3.2 Server的启动过程

Server是Tomcat里面的接口,而不是类,那么我们就只能去找实现它的子类来于是就找到了StandardServer extends LifecycleMBeanBase implements Server。

阿粉一看有继承,还有实现,那就先看看LifecycleMBeanBase这个被继承的类,于是再次去看了它,

public abstract class LifecycleMBeanBase extends LifecycleBase implements JmxEnabled {

嗯?还有继承?继续往下扒拉,

public abstract class LifecycleBase implements Lifecycle {

终于算是找到了,

6.jpg

7.jpg

阿粉一看这init方法和start方法又调用了initInternal()和startInternal(),找来找去又回去了,而阿粉也从这里知道了,模板方法,是有自己的子类具体实现

于是回到了StandardServer自己的init和start方法,

 protected void startInternal() throws LifecycleException {
        this.fireLifecycleEvent("configure_start", (Object)null);
        this.setState(LifecycleState.STARTING);
        this.globalNamingResources.start();
        Object var1 = this.servicesLock;
        synchronized(this.servicesLock) {
            for(int i = 0; i < this.services.length; ++i) {
                this.services[i].start();
            }

        }
    }

8.jpg

总得来说就是,StandardServer继承自LifecycleMBeanBase,而LifecycleMBeanBase继承自LifecycleBase,而LifecycleBase类中的模板方法,又让自己的子类去进行具体的实现,但是我们要知道他的Tomcat生命周期中存在这些内容才行。

图中都说了,Server里面有Service,那么一定就有,我们得去找找看,于是阿粉再次去找并且去看它到底是个什么意思,


    public void addService(Service service) {
        service.setServer(this);
        Object var2 = this.servicesLock;
        synchronized(this.servicesLock) {
            Service[] results = new Service[this.services.length + 1];
            System.arraycopy(this.services, 0, results, 0, this.services.length);
            results[this.services.length] = service;
            this.services = results;
            if (this.getState().isAvailable()) {
                try {
                    service.start();
                } catch (LifecycleException var6) {
                    ;
                }
            }

            this.support.firePropertyChange("service", (Object)null, service);
        }
    }

位置是在Server的接口中出现了增加和删除Service的方法,Server的init方法和start方法循环去调用每个Service的init方法和start方法。

接下来我们看看Service的具体实现,找到StandardService:

  protected void initInternal() throws LifecycleException {
        super.initInternal();
        if (this.engine != null) {
            this.engine.init();
        }

        Executor[] arr$ = this.findExecutors();
        int len$ = arr$.length;

        int len$;
        for(len$ = 0; len$ < len$; ++len$) {
            Executor executor = arr$[len$];
            if (executor instanceof JmxEnabled) {
                ((JmxEnabled)executor).setDomain(this.getDomain());
            }

            executor.init();
        }

        this.mapperListener.init();
        Object var11 = this.connectorsLock;
        synchronized(this.connectorsLock) {
            Connector[] arr$ = this.connectors;
            len$ = arr$.length;

            for(int i$ = 0; i$ < len$; ++i$) {
                Connector connector = arr$[i$];

                try {
                    connector.init();
                } catch (Exception var9) {
                    String message = sm.getString("standardService.connector.initFailed", new Object[]{connector});
                    log.error(message, var9);
                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
                        throw new LifecycleException(message);
                    }
                }
            }

        }
    }

而在方法中主要调用Executor,mapperListener,executor的init方法。

connector之前已经有了,而这个mapperListener就是Mapper的监听器,用来坚挺container容器的变化。

9.jpg

文献参考

《深入剖析Tomcat》 《Tomcat架构解析》 《Servlet/JSP深入详解》


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK