zoukankan      html  css  js  c++  java
  • tomcat8.5.57源码阅读笔记4

    Bootstrap#start()

    daemon = bootstrap, 所以调用的还是 org.apache.catalina.startup.Bootstrap#start()

    public void start() throws Exception {
        if (catalinaDaemon == null) {
            init();
        }
    
        Method method = catalinaDaemon.getClass().getMethod("start", (Class[]) null);
        // 反射调用 Catalina#start()
        method.invoke(catalinaDaemon, (Object[]) null);
    }

    反射调用了 Catalina#start() 方法, 进行启动工作.

    Catalina#start()

    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();
    
        // Start the new server
        try {
            //StandardServer#start(), 最终调用的是 StandardServer#startInternal()
            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");
        }
    
        // Register shutdown hook
        // 注册回调方法, 用于安全关闭服务
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);
    
            // If JULI is being used, disable JULI's shutdown hook since
            // shutdown hooks run in parallel and log messages may be lost
            // if JULI's hook completes before the CatalinaShutdownHook()
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }
    
        // Bootstrap中会设置await为true,其目的在于让tomcat在shutdown端口阻塞监听关闭命令
        if (await) {
            await();
            stop();
        }
    }

    这里继续调用了 Server 的 start 方法. 实际调用的是 org.apache.catalina.util.LifecycleBase#start(),

    然后此方法中调用了抽象方法 startInternal(), 也就是 StandardServer#startInternal()

    StandardServer#startInternal()

    protected void startInternal() throws LifecycleException {
        // 事件通知 CONFIGURE_START_EVENT -> configure_start
        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        // 修改tomcat状态为 STARTING, 这一步也会进行事件通知 START_EVENT -> start
        setState(LifecycleState.STARTING);
    
        //NamingResourcesImpl#startInternal()   全局资源
        //在server.xml中配置了该变量, pathname="conf/tomcat-users.xml"
        globalNamingResources.start();
    
        // Start our defined Services
        synchronized (servicesLock) {
            for (Service service : services) {
                // StandardService.startInternal()
                service.start();
            }
        }
    }

    service.start() 最终调用的是 StandardService.startInternal()

    StandardService.startInternal()

    protected void startInternal() throws LifecycleException {
    
        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        setState(LifecycleState.STARTING);
    
        // Start our defined Container first
        if (engine != null) {
            synchronized (engine) {
                //StandardEngine#start() 最终调用 StandardEngine#startInternal()
                //这一句代码, 就将 Host, Context, Wrapper 的 start 执行到了
                //host是通过 server.xml 解析得到
                //context是通过扫描文件的方式解析得到, 有三种方式: xml配置方式, war包方式, 文件夹方式
                //wrapper也是通过扫描jar包的方式得到
                engine.start();
            }
        }
    
        // 启动Executor线程池, 默认情况下, 是空, 通过修改 server.xml中的tomcatThreadPool来改变
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }
    
        // 启动 MapperListener, 进行注册功能
        // 最终会调用 MapperListener#startInternal() 方法
        //* 这个方法主要干了两件事情
        //* 1. 将 MapperListener 注册到容器和子容器(Host, Context, Wrapper)的 listeners 和 lifecycleListeners 中
        //* 2. 注册 host, context, wrapper
        mapperListener.start();
    
        // Start our defined Connectors second
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                try {
                    // If it has already failed, don't try and start it
                    if (connector.getState() != LifecycleState.FAILED) {
                        //最终调用 Connector.startInternal()
                        connector.start();
                    }
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
        }
    }

    这里和初始化的时候很像, 初始化的时候, 是对他们分别初始化, 而这里, 是对他们分别启动

    1. engine.start() : 启动 engine, 里面会启动 Host, Context, Wrapper. 最终会调用 StandardEngine#startInternal() 

    2. executor.start() : 启动线程池, 默认情况下, 这个线程池为空.

    3. mapperListener.start() : 将MapperListener注册到 Host,Context,Wrapper 的监听器中, 然后对 Host, Context, Wrapper 进行映射

    4. connector.start() : 启动连接器

    StandardEngine#startInternal() 

      

    protected synchronized void startInternal() throws LifecycleException {
    
        // Log our server identification information
        if(log.isInfoEnabled())
            log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
    
        // Standard container startup
        //通过解析 server.xml 文件, 可以拿到 engine下面有一个 host, 也就是说, StandardEngine.children有一个host
        super.startInternal();
    }

    super.startInternal() 调用的是父类 org.apache.catalina.core.ContainerBase#startInternal() 方法:

    protected synchronized void startInternal() throws LifecycleException {
    
        // Start our subordinate components, if any
        logger = null;
        getLogger();
        //集群客户端
        Cluster cluster = getClusterInternal();
        if (cluster instanceof Lifecycle) {
            //如果有集群的话, 则会启动集群
            ((Lifecycle) cluster).start();
        }
        //
        Realm realm = getRealmInternal();
        if (realm instanceof Lifecycle) {
            ((Lifecycle) realm).start();
        }
    
        // Start our child containers, if any
        // 把子容器的启动步骤放在线程中处理,默认情况下线程池只有一个线程处理任务队列
        //StandardEngine 调用的时候, 这里拿到的 children 有一个值: StandardHost[localhost]
        // <Engine name="Catalina" defaultHost="localhost">
        //      <Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true" startStopThreads="1">
        // </engine>
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            //StartChild 的 call方法, 调用的是 child.start() 方法, 最终会调用 StandardHost.startInternal
            results.add(startStopExecutor.submit(new StartChild(child)));
        }
    
        MultiThrowable multiThrowable = null;
    
        // 阻塞当前线程,直到子容器start完成
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Throwable e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                if (multiThrowable == null) {
                    multiThrowable = new MultiThrowable();
                }
                multiThrowable.add(e);
            }
    
        }
        if (multiThrowable != null) {
            throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                    multiThrowable.getThrowable());
        }
    
        // Start the Valves in our pipeline (including the basic), if any
        // 启用 Pipeline --> StandardPipeline#startInternal()
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).start();
        }
    
        //StandardHost调用时, 激发 STARTING 监听器 HostConfig,最终会调用 HostConfig#start() 方法 - 这一步很关键
        setState(LifecycleState.STARTING);
    
        // Start our thread
        // 开启ContainerBackgroundProcessor线程用于调用子容器的backgroundProcess方法,
        // 默认情况下backgroundProcessorDelay=-1,不会启用该线程
        threadStart();
    }

    1. 通过Future框架异步调用 StandardHost#startInternal() 方法, 并阻塞等待所有当前 engine 下所有的 Host 启动完成

    2. setState() 时, 会激发监听器 HostConfig. 这个HostConfig 是在 解析 Server.xml 的时候, 创建并绑定的.

    StandardHost#startInternal()

    protected synchronized void startInternal() throws LifecycleException {
        // Set error report valve
        // errorValve默认使用 ErrorReportValve
        String errorValve = getErrorReportValveClass();
        if ((errorValve != null) && (!errorValve.equals(""))) {
            try {
                boolean found = false;
                // 如果所有的阀门中已经存在这个实例,则不进行处理,否则添加到 Pipeline 中
                Valve[] valves = getPipeline().getValves();
                for (Valve valve : valves) {
                    if (errorValve.equals(valve.getClass().getName())) {
                        found = true;
                        break;
                    }
                }
                // 如果未找到则添加到 Pipeline 中,注意是添加到 basic valve 的前面
                // 默认情况下,first valve 是 AccessLogValve,basic 是 StandardHostValve
                if(!found) {
                    Valve valve =
                        (Valve) Class.forName(errorValve).getConstructor().newInstance();
                    getPipeline().addValve(valve);
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString(
                        "standardHost.invalidErrorReportValveClass",
                        errorValve), t);
            }
        }
        // 调用父类 ContainerBase,完成统一的启动动作
        super.startInternal();
    }

    这里的 startInternal() 就是上面的 org.apache.catalina.core.ContainerBase#startInternal() 方法.

    但是此时, host 的 children 是空的, 所以里面并没有能够调用 StandardContext#startInternal()

    HostConfig#lifecycleEvent()

    HostConfig 实现了 LifecycleListener 接口. 所以实际上, 他是一个监听器. 

    setState() 的时候, 会激发 HostConfig的监听器方法 lifecycleEvent(), 

    public void lifecycleEvent(LifecycleEvent event) {
    
        // Identify the host we are associated with
        try {
            host = (Host) event.getLifecycle();
            if (host instanceof StandardHost) {
                setCopyXML(((StandardHost) host).isCopyXML());
                setDeployXML(((StandardHost) host).isDeployXML());
                setUnpackWARs(((StandardHost) host).isUnpackWARs());
                setContextClass(((StandardHost) host).getContextClass());
            }
        } catch (ClassCastException e) {
            log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
            return;
        }
    
        // Process the event that has occurred
        //判断事件是否由 Host 发出,并且为 HostConfig 设置属性
        if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
            check();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();
        } else if (event.getType().equals(Lifecycle.START_EVENT)) {
            start();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }
    }

    根据当前事件的状态, 会调用这里的 start() 方法. 此start 方法中, 会执行 部署 webapp 的方法 : org.apache.catalina.startup.HostConfig#deployApps()

    protected void deployApps() {
        File appBase = host.getAppBaseFile();
        File configBase = host.getConfigBaseFile();
        // 过滤出 webapp 要部署应用的目录
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // Deploy XML descriptors from configBase
        // 1. xml部署 - 不推荐这么使用
        // <host><context docBase="D://abc/eee" path="sdm" reloadable="true"></context></host>
        // 部署 xml 描述文件
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs
        // 2. war包部署
        // 解压 war 包,但是这里还不会去启动应用
        deployWARs(appBase, filteredAppPaths);
        // Deploy expanded folders
        // 3. 目录部署
        // 处理已经存在的目录,前面解压的 war 包不会再行处理
        deployDirectories(appBase, filteredAppPaths);
    }

    源码里面放的几个 Context , 都是目录结构的, 所以会走 deployDirectories() 方法

    protected void deployDirectories(File appBase, String[] files) {
    
        if (files == null)
            return;
    
        ExecutorService es = host.getStartStopExecutor();
        List<Future<?>> results = new ArrayList<>();
    
        for (String file : files) {
    
            if (file.equalsIgnoreCase("META-INF"))
                continue;
            if (file.equalsIgnoreCase("WEB-INF"))
                continue;
            File dir = new File(appBase, file);
            if (dir.isDirectory()) {
                ContextName cn = new ContextName(file, false);
    
                if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
                    continue;
    
                results.add(es.submit(new DeployDirectory(this, cn, dir)));
            }
        }
    
        for (Future<?> result : results) {
            try {
                result.get();
            } catch (Exception e) {
                log.error(sm.getString(
                        "hostConfig.deployDir.threaded.error"), e);
            }
        }
    }

    1. 通过调试, 能看到:

    这里的每一个文件夹, 转换之后, 就是一个 Context .

      

     2. 这里又出现了 Future . 这次提交的是 DeployDirectory, 是 HostConfig 的一个内部类. 看一下他的 run 方法:

    private static class DeployDirectory implements Runnable {
    
        private HostConfig config;
        private ContextName cn;
        private File dir;
    
        public DeployDirectory(HostConfig config, ContextName cn, File dir) {
            this.config = config;
            this.cn = cn;
            this.dir = dir;
        }
    
        @Override
        public void run() {
            config.deployDirectory(cn, dir);
        }
    }

    实际调用的, 还是 HostConfig 的方法:

    protected void deployDirectory(ContextName cn, File dir) {
        long startTime = 0;
        // Deploy the application in this directory
        if( log.isInfoEnabled() ) {
            startTime = System.currentTimeMillis();
            log.info(sm.getString("hostConfig.deployDir",
                    dir.getAbsolutePath()));
        }
    
        Context context = null;
        File xml = new File(dir, Constants.ApplicationContextXml);
        File xmlCopy = new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml");
    
        DeployedApplication deployedApp;
        boolean copyThisXml = isCopyXML();
        boolean deployThisXML = isDeployThisXML(dir, cn);
        try {
            if (deployThisXML && xml.exists()) {
                synchronized (digesterLock) {
                    try {
                        //StandardContext
                        context = (Context) digester.parse(xml);
                    } catch (Exception e) {
                       ......
                    } finally {
                        digester.reset();
                        if (context == null) {
                            context = new FailedContext();
                        }
                    }
                }
    
                if (copyThisXml == false && context instanceof StandardContext) {
                    // Host is using default value. Context may override it.
                    copyThisXml = ((StandardContext) context).getCopyXML();
                }
    
                if (copyThisXml) {
                    Files.copy(xml.toPath(), xmlCopy.toPath());
                    context.setConfigFile(xmlCopy.toURI().toURL());
                } else {
                    context.setConfigFile(xml.toURI().toURL());
                }
            } else if (!deployThisXML && xml.exists()) {
                // Block deployment as META-INF/context.xml may contain security
                // configuration necessary for a secure deployment.
                log.error(sm.getString("hostConfig.deployDescriptor.blocked",
                        cn.getPath(), xml, xmlCopy));
                context = new FailedContext();
            } else {
                context = (Context) Class.forName(contextClass).getConstructor().newInstance();
            }
    
            // 实例化 ContextConfig,作为 LifecycleListener 添加到 Context 容器中,这和 StandardHost 的套路一样,都是使用 XXXConfig
            Class<?> clazz = Class.forName(host.getConfigClass());
            LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
            context.addLifecycleListener(listener);
    
            context.setName(cn.getName());
            context.setPath(cn.getPath());
            context.setWebappVersion(cn.getVersion());
            context.setDocBase(cn.getBaseName());
            // 实例化 StandardContext 之后,为 Host 添加子节点
            // 这里调用的是 StandardHost#addChild()
            host.addChild(context);
        } catch (Throwable t) {
            ......
        } finally {
            ......
        }
        ......
    }

    这里出现了 host.addChild(context) 方法, 需要回到 StandardHost 类中去看

    //org.apache.catalina.core.StandardHost#addChild
    public void addChild(Container child) {
    
        if (!(child instanceof Context))
            throw new IllegalArgumentException
                (sm.getString("standardHost.notContext"));
    
        //加入一个监听器
        child.addLifecycleListener(new MemoryLeakTrackingListener());
    
        // Avoid NPE for case where Context is defined in server.xml with only a
        // docBase
        Context context = (Context) child;
        if (context.getPath() == null) {
            ContextName cn = new ContextName(context.getDocBase(), true);
            context.setPath(cn.getPath());
        }
        super.addChild(child);
    }

    接着看父类中的 addChild()

    //org.apache.catalina.core.ContainerBase#addChild
    public void addChild(Container child) {
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> dp =
                new PrivilegedAddChild(child);
            AccessController.doPrivileged(dp);
        } else {
            addChildInternal(child);
        }
    }
    
    private void addChildInternal(Container child) {
    
        if( log.isDebugEnabled() )
            log.debug("Add child " + child + " " + this);
        synchronized(children) {
            if (children.get(child.getName()) != null)
                throw new IllegalArgumentException("addChild:  Child name '" +
                                                   child.getName() +
                                                   "' is not unique");
            child.setParent(this);  // May throw IAE
            children.put(child.getName(), child);
        }
    
        // Start child
        // Don't do this inside sync block - start can be a slow process and
        // locking the children object can cause problems elsewhere
        try {
            if ((getState().isAvailable() ||
                    LifecycleState.STARTING_PREP.equals(getState())) &&
                    startChildren) {
                //addChild的时候, 调用启动方法, 解析Server.xml时, 不会进此方法
                //StandardHost的时候, 会进此方法, 调用的是 StandardContext.start() -> StandardContext.startInternal()
                //StandardContext的时候, 会进此方法, 调用的是 StandardWrapper.start() -> StandardWrapper.startInternal()
                child.start();
            }
        } catch (LifecycleException e) {
            log.error("ContainerBase.addChild: start: ", e);
            throw new IllegalStateException("ContainerBase.addChild: start: " + e);
        } finally {
            fireContainerEvent(ADD_CHILD_EVENT, child);
        }
    }

    此时 child 是 StandardContext. 所以会调用

    org.apache.catalina.util.LifecycleBase#start() :

    public final synchronized void start() throws LifecycleException {
    
        if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
                LifecycleState.STARTED.equals(state)) {
    
            if (log.isDebugEnabled()) {
                Exception e = new LifecycleException();
                log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
            } else if (log.isInfoEnabled()) {
                log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
            }
    
            return;
        }
    
        if (state.equals(LifecycleState.NEW)) {
            //results.add(startStopExecutor.submit(new StartChild(child)));
            //异步执行 StandardHost.start() 方法时, 会走这里
            init();
        } else if (state.equals(LifecycleState.FAILED)) {
            stop();
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
                !state.equals(LifecycleState.STOPPED)) {
            invalidTransition(Lifecycle.BEFORE_START_EVENT);
        }
    
        try {
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            startInternal();
            if (state.equals(LifecycleState.FAILED)) {
                // This is a 'controlled' failure. The component put itself into the
                // FAILED state so call stop() to complete the clean-up.
                stop();
            } else if (!state.equals(LifecycleState.STARTING)) {
                // Shouldn't be necessary but acts as a check that sub-classes are
                // doing what they are supposed to.
                invalidTransition(Lifecycle.AFTER_START_EVENT);
            } else {
                setStateInternal(LifecycleState.STARTED, null, false);
            }
        } catch (Throwable t) {
            // This is an 'uncontrolled' failure so put the component into the
            // FAILED state and throw an exception.
            handleSubClassException(t, "lifecycleBase.startFail", toString());
        }
    }

    StandardContext 就是在这个方法中进行初始化, 以及启动的.

  • 相关阅读:
    ThinkPHP5专题
    php截取中文字符串
    跨域/非跨域接口专题
    JS检查输入项是否为手机号码或者固话号码的正则表达式
    TinkPHP去重统计查询
    模型类设计模式
    经典排序方法 python
    leetcode122 买卖股票的最佳时机 python
    找到链表的倒数第k个节点 python
    链表的实现、输出和反向 python
  • 原文地址:https://www.cnblogs.com/elvinle/p/13528022.html
Copyright © 2011-2022 走看看