由浅入深--探究Tomcat9.0之--入门篇3

再次强调一下,Tomcat系列全部文章基于9.0.12版本。

                           入门篇3 Tomcat的启动(一)

二:start()方法

    在BootStrap类中的核心代码是这几行。

String command = "start";

else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
            }

  

  看到这里我很纳闷,为什么要setAwait呢?各位观众老爷应该也很疑惑把,没事,我们跟踪代码去一探究竟。

  反射就是调用了Catalina的setAwait方法来给catalinaDaemon对象的await属性赋值,因为在Catalina对象初始化的时候默认await属性是false,这里设置成true,。如果大家对这里初始化的时候知道类名,方法名,这里为什么要用反射来执行有疑问,请看上一篇的Question3和Answers3的解答。

  public void setAwait(boolean await)
        throws Exception {
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Boolean.TYPE;
        Object paramValues[] = new Object[1];
        paramValues[0] = Boolean.valueOf(await);
        Method method =
            catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
        method.invoke(catalinaDaemon, paramValues);
    }

  那么为什么要设置成true呢?这个属性有什么用呢?别着急,我们搜索一下这个属性在哪里用到了,tomcat的解耦做的非常好,同一个属性一般只应用到这个类里面。所以我们直接Ctrl+F搜索。发现在Catalina的strat方法中有这么一段

   if (await) {
            await();
            stop();
    }

 如果是true,则执行await方法。继续跟踪await方法

 public void await() {
        getServer().await();
    }

发现这里执行的是server对象的awiat方法。我们点进去发现await是一个接口,找到其实现类。这里讲一个小技巧。源码看多了大家就会发现,像这种接口一般都会有一个标准或者默认的实现类,一般名字都叫做Standardxxx或者xxxBase。果不其然,我们在StandardServer类中找到await方法,大概看一下,发现这里就是new ServerSocket的地方,应该就是启动端口的那部分代码。跟踪到这里我们暂时先放下这一块的代码。

总结一下就是await的初始化的时候是false,需要在启动的时候设置标志位为true才可以正常启动端口来监听请求。

我们现在回到Bootstrap里面,第二步是daemon.load(args)这部分。

扫描二维码关注公众号,回复: 4897723 查看本文章

我们进入load方法,和上面一样,都是反射执行Catalina的load方法。

这个方法有很多注意的地方,可能有点长,但是还是很有必要来细细分析的。

 public void load() {
        if (loaded) {
            return;
        }
        loaded = true;
        long t1 = System.nanoTime();
        initDirs();
        // Before digester - it may be needed
        initNaming();
        // Create and execute our Digester
        Digester digester = createStartDigester();
        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            try {
                file = configFile();
                inputStream = new FileInputStream(file);
                inputSource = new InputSource(file.toURI().toURL().toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail", file), e);
                }
            }
            if (inputStream == null) {
                try {
                    inputStream = getClass().getClassLoader()
                        .getResourceAsStream(getConfigFile());
                    inputSource = new InputSource
                        (getClass().getClassLoader()
                         .getResource(getConfigFile()).toString());
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("catalina.configFail",
                                getConfigFile()), e);
                    }
                }
            }
            // This should be included in catalina.jar
            // Alternative: don't bother with xml, just create it manually.
            if (inputStream == null) {
                try {
                    inputStream = getClass().getClassLoader()
                            .getResourceAsStream("server-embed.xml");
                    inputSource = new InputSource
                    (getClass().getClassLoader()
                            .getResource("server-embed.xml").toString());
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("catalina.configFail",
                                "server-embed.xml"), e);
                    }
                }
            }
            if (inputStream == null || inputSource == null) {
                if  (file == null) {
                    log.warn(sm.getString("catalina.configFail",
                            getConfigFile() + "] or [server-embed.xml]"));
                } else {
                    log.warn(sm.getString("catalina.configFail",
                            file.getAbsolutePath()));
                    if (file.exists() && !file.canRead()) {
                        log.warn("Permissions incorrect, read permission is not allowed on the file.");
                    }
                }
                return;
            }
            try {
                inputSource.setByteStream(inputStream);
                digester.push(this);
                digester.parse(inputSource);
            } catch (SAXParseException spe) {
                log.warn("Catalina.start using " + getConfigFile() + ": " +
                        spe.getMessage());
                return;
            } catch (Exception e) {
                log.warn("Catalina.start using " + getConfigFile() + ": " , e);
                return;
            }
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
        getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
        // Stream redirection
        initStreams();
        // Start the new server
        try {
            getServer().init();
        } catch (LifecycleException e) {
            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
                throw new java.lang.Error(e);
            } else {
                log.error("Catalina.start", e);
            }
        }
        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
        }
    }

1. initDirs(),名称是初始化文件夹,就是初始化catalina-home/temp这个文件夹。大家导入源码的时候会新建这个文件夹,不然会打印错误日志,其实就是这里在控制的。

2.initNaming()这一部分没什么好说的,就是使用System.property方法将原来jdk的系统属性替换成tomcat自定义的系统属性。

3.createStartDigster这个方法比较特殊,乍一看有点蒙,digester这个词是消化器的意思,那么tomcat在消化什么呢?后来上网一搜,发现digester这个东西是和dom4j一样,都是解析xml文件用的,dom4j是将文件全部读取到内存中来解析的,效率比较高,digester是一边解析,一边读取的,效率较低,但是节约内存。而且被apache收购,tomcat在digester的基础上又封装了一层的东西。总的来说,digester就是一个解析xml文件的东西。点进去一看,果不其然,和我们的server.xml文件目录一样。那到底digester用什么规则来解析呢?这个方法的作用就是这个,将一些规则和类添加进去初始化。

  digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");
        digester.addSetProperties("Server");
        digester.addSetNext("Server",
                            "setServer",
                            "org.apache.catalina.Server");

        digester.addObjectCreate("Server/GlobalNamingResources",
                                 "org.apache.catalina.deploy.NamingResourcesImpl");
        digester.addSetProperties("Server/GlobalNamingResources");
        digester.addSetNext("Server/GlobalNamingResources",
                            "setGlobalNamingResources",
                            "org.apache.catalina.deploy.NamingResourcesImpl");

 然后就是读取文件,对文件输入输出流的一些操作,真正的解析是在digester.push() 和digester.parse()这两个地方。

解析完成之后,我们tomcat中定义的一些组件和server.xml文件中定义的组件关联起来啦。

接下来还有一块的的核心代码就是getServer.init(),这个方法点进去之后发现是Lifecycle这个接口,这是管理生命周期的接口,所有组件都实现了start方法和init方法,确切的说实现的是startInternal方法和initInternal方法,这都是设计模式的体现,我会在设计模式专题中详细讲解这一部分。我们重点先回到init方法里面。记住,去找实现类,StandardServer的initInternal方法。我们来看一下。

    protected void initInternal() throws LifecycleException {
        super.initInternal();
       //这里和jvm有关,就是添加一个全局的string缓存标识,如果有多个的话就直接从缓存中读取
        onameStringCache = register(new StringCache(), "type=StringCache");
        // 工厂模式的体现,engine包裹的内容都可以叫做container,用工厂模式来统一添加
        MBeanFactory factory = new MBeanFactory();
        //所有的子集的添加都是添加container
        factory.setContainer(this);
        onameMBeanFactory = register(factory, "type=MBeanFactory");
        //初始化全局命名资源
        globalNamingResources.init();
        //这里放在文字中讲解,比较重要
        if (getCatalina() != null) {
            ClassLoader cl = getCatalina().getParentClassLoader();
            while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
                if (cl instanceof URLClassLoader) {
                    URL[] urls = ((URLClassLoader) cl).getURLs();
                    for (URL url : urls) {
                        if (url.getProtocol().equals("file")) {
                            try {
                                File f = new File (url.toURI());
                                if (f.isFile() &&
                                        f.getName().endsWith(".jar")) {
                                    ExtensionValidator.addSystemResource(f);
                                }
                            } catch (URISyntaxException e) {
                                // Ignore
                            } catch (IOException e) {
                                // Ignore
                            }
                        }
                    }
                }
                cl = cl.getParent();
            }
        }
        // 这里也放在文字中讲解
        for (int i = 0; i < services.length; i++) {
            services[i].init();
        }
    }

需要详细讲解的地方,首先获取catalina的类加载器,然后判断父加载器是不是系统级别的类加载器,如果你认真读过第二篇的话会知道catalina的类加载器的上层是common类加载器,这里其实就是判断是不是common类加载器,如果是的话就去目录里面将所有的tomcat的内置jar包加载。

下面的service方法都一样,这里需要回顾一下tomcat的组件结构,如果不熟悉的童鞋请回顾一下第一篇,接下来所有的init顺序都是和第一篇中介绍的吻合。

接下来我们来到第三步,进入start方法。

其实start方法要说的倒不是很多了,和上面的init的模式基本一致。

      public void start() {
        if (getServer() == null) {
            load();
        }
        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }
        long t1 = System.nanoTime();
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }
        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        }
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }
        if (await) {
            await();
            stop();
        }
    }

这里我们注意一下 long t1,long t2这两个变量。long t2下面是不是看到了一串熟悉的日志~,看到这里是不是有一些成就感呢~嘿嘿嘿。

核心代码是getServer().start方法这里。

也是按照tomcat的层次模型一层一层逐一start的。这里面注意一下connector的startInternal方法比较复杂。需要先初始化protocolHandler这个类,然后又需要先初始化endPoint,这个对象是一个抽象类,走到这里的时候我们看一下endPoint这个对象的实现方式。

是不是看到了我们经常说的nio,bio这些io模型呢?这里是tomcat9.0的版本,已经没有bio的实现了,我们tomcat的默认实现是nio的方式。apr模式大家可能不了解,我大致介绍一下,apr就是直接调用native本地方法库来解决高并发问题的一种方式,但是这种方式目前还不是很成熟,效率其实也一般,最重要的是使用起来比较繁琐,不是修改一下xml文件就可以的。还需要下载一些依赖。

至于protocolHandler,这种xxxHandler我们一般叫做分发器,叫适配器设计模式,这里的作用是负责找到适配的io模型然后分发。

好啦,启动流程大致就到这里啦。下面该是手写实现篇咯。大家对这种讲解有什么意见或者建议呢,还请大家留下宝贵的意见。

猜你喜欢

转载自blog.csdn.net/qq_27631217/article/details/83478962
今日推荐