精华内容
下载资源
问答
  • Tomcat启动源码分析

    2021-11-22 18:48:30
    6、engine启动,触发start事件,监听器触发各个组件启动 7、tomcat启动完毕 Server对象的创建 BootStrap实例化Catalina类 public static void main(String args[]) { if (daemon == null) { // Don't set daemon ...

    前言

    这篇文章参考这两篇博客:

    1、https://blog.csdn.net/fjslovejhl/article/details/21107331?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163749113016780265445730%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=163749113016780265445730&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v2~rank_v29-8-21107331.pc_v2_rank_blog_default&utm_term=tomcat&spm=1018.2226.3001.4450

    2、https://blog.csdn.net/flybone7/article/details/106175068

    还有一本书叫《深入刨析Tomcat》,非常棒的一本书,推荐阅读

    基本原理

    1.调用org.apache.catalina.startup.Bootstrap中的主函数main(),main() 中调用自身的start()方法,在start()方法中实例化org.apache.catalina.startup.Catalina类调用其start()方法,在Catalina的start()中调用load()载入tomcat文件夹目录下conf文件夹下的server.xml并创建Server类,然后调用Server的start()方法,到这里我们的tomcat就运行起来了.总的来说就是BootStrap类实例化了Catalina类,然后该Catalina实例解析server.xml里的配置启动Tomcat。

    2.Server类代表整个Tomcat服务器,这里有必要介绍一下为什么Tomcat叫Servlet容器,在Tomcat中包含四个容器,分别为Engine,Host,Context,Wrapper.Engine是Host的父容器,依次类推.四者都继承自Container接口.一般在Tomcat中一个Engine实例中包含一个Host实例,一个Host实例中包含一个Context实例,一个Context代表一个WEB程序并包含多个Wrapper实例,一个Wrapper代表一个Servlet类.

    3.一次请求的大题流程是这样的,首先由Connector获取到Http请求,封装ServletRequest和ServletResponse对象并派发给Context管道中的所有Valve,执行完所有Valve后执行基础Valve根据请求的url映射到对应的Wrapper,然后调用个Wrapper管道中的所有Valve,最后调用基础Valve在这个Wrapper类所封装的Servlet,实例化后调用其Service方法对用户的请求进行处理,最后将结果负载在response中返回给用户.

    大体步骤

    1、BootStrap启动main方法,tomcat的启动入口

    2、bootstrap启动Catalina类

    3、Catalina类创建Server实例

    4、Server实例调用Service类启动

    5、Service类让engine启动,engine是tomcat的顶级容器接口,tomcat根据lifecycle机制让顶级接口启动的同时把子接口也启动。

    6、engine启动,触发start事件,监听器触发各个组件启动

    7、tomcat启动完毕

    Server对象的创建

    BootStrap实例化Catalina类

    public static void main(String args[])
        {
            if (daemon == null)
            {
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try
                {
                    bootstrap.init();
                } catch (Throwable t)
                {
                    handleThrowable(t);
                    t.printStackTrace();
                    return;
                }
                daemon = bootstrap;
            } else
            {
                // When running as a service the call to stop will be on a new
                // thread so make sure the correct class loader is used to prevent
                // a range of class not found exceptions.
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
            }
            try
            {
                //这里的设置默认的命令为start
                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"))
                {
                    // 在这里读取命令行参数为start,然后启动。
                    daemon.setAwait(true);
                    daemon.load(args);
                    daemon.start();
                } 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 t)
            {
                // Unwrap the Exception for clearer error reporting
                if (t instanceof InvocationTargetException && t.getCause() != null)
                {
                    t = t.getCause();
                }
                handleThrowable(t);
                t.printStackTrace();
                System.exit(1);
            }
        }
    
    

    实际上main方法从这个里实例化Catalina对象

    bootstrap.init();
    

    追进去init()方法看看

    public void init() throws Exception {
    
            initClassLoaders();
    
            Thread.currentThread().setContextClassLoader(catalinaLoader);
    
            SecurityClassLoad.securityClassLoad(catalinaLoader);
    
            // Load our startup class and call its process() method
            if (log.isDebugEnabled())
                log.debug("Loading startup class");
            //下面这几行代码就是通过反射创建了Catalina的实例
            Class<?> startupClass =
                catalinaLoader.loadClass
                ("org.apache.catalina.startup.Catalina");
            Object startupInstance = startupClass.newInstance();
    
            // Set the shared extensions class loader
            if (log.isDebugEnabled())
                log.debug("Setting startup class properties");
            String methodName = "setParentClassLoader";
            Class<?> paramTypes[] = new Class[1];
            paramTypes[0] = Class.forName("java.lang.ClassLoader");
            Object paramValues[] = new Object[1];
            paramValues[0] = sharedLoader;
            Method method =
                startupInstance.getClass().getMethod(methodName, paramTypes);
            method.invoke(startupInstance, paramValues);
    		//把这个实例赋值给BootStrap的成员变量catalinaDaemon
            catalinaDaemon = startupInstance;
    
        }
    

    Debug

    init()实例化Catalina对象

    在这里插入图片描述

    Catalina类实例化Server

    在这里读取命令行参数为start,然后启动

    // 在这里读取命令行参数为start,然后启动。
                    daemon.setAwait(true); 
                    daemon.load(args);
                    daemon.start();
    

    这里的daemon实际上是一个Bootstrap的实例,由main()方法创建

    private static Bootstrap daemon = null;
    

    BootStrap中main方法这句代码启动了一个Server实例

    daemon.load(args);  //这里的daemon实际上是一个Bootstrap的实例
    

    追进去BootStrap中的load方法看看,BootStrap中的load方法调用了Catalina里面的load方法

    BootStrap的load方法:

    /**
         * Load daemon.
         */
        private void load(String[] arguments)
            throws Exception {
    
    		//catalinaDaemon是一个Catalina实例
            // Call the load() method
            String methodName = "load";
            Object param[];
            Class<?> paramTypes[];
            if (arguments==null || arguments.length==0) {
                paramTypes = null;
                param = null;
            } else {
                paramTypes = new Class[1];
                paramTypes[0] = arguments.getClass();
                param = new Object[1];
                param[0] = arguments;
            }
            Method method =
                catalinaDaemon.getClass().getMethod(methodName, paramTypes);
            if (log.isDebugEnabled())
                log.debug("Calling startup class " + method);
            //通过反射调用Catalina类的load方法
            method.invoke(catalinaDaemon, param);
    
        }
    

    再追进去,Catalina类里面的load方法

     public void load() {
    
            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 {
                	//这里追进去源码看,其实是找到配置文件server.xml
                    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);
                    //digster库解析xml是采用压栈入栈形式的
                    //把Catalina实例压栈,这时候Catalina实例在栈底
                    digester.push(this);
                    //digster实例解析server.xml文件,
                    //这里之后Catalina的私有变量server会引用一个已经被解析的Server对象
                    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
                    }
                }
            }
    		//设置这个Server实例的一些属性
            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");
            }
        }
    

    Catalina类中的私有成员变量server

    在digester.parse(inputSource)解析之后,会赋值一个StanderServer实例给server,并且在StandardServer中注册了一个StandardService
    在这里插入图片描述

    StanderServer继承LifecycleMBeanBase
    LifecycleMBeanBase继承LifecycleMBeanBase
    LifecycleMBeanBase继承LifecycleBase
    在接下来这句代码中,

    getServer().init();
    
    //实际上StanderServer是调用LifecycleBase的init()方法,这个方法会触发一个初始化事件,用于初始化其它容器
    
    public final synchronized void init() throws LifecycleException {
            if (!state.equals(LifecycleState.NEW)) {
                invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
            }
            setStateInternal(LifecycleState.INITIALIZING, null, false);
    
            try {
                initInternal();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                setStateInternal(LifecycleState.FAILED, null, false);
                throw new LifecycleException(
                        sm.getString("lifecycleBase.initFail",toString()), t);
            }
    
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        }
    

    Context创建与启动

    触发一次start事件

    到这里之后Catalina类中的load()方法已经走完,server实例也初始化完毕,回到Bootstrap的main方法的代码中

    daemon.setAwait(true);
    daemon.load(args);//这时候走完了这里
    daemon.start();
    

    首先由bootStrap中的main方法调用Catalina类里面的start()方法。

    /**
         * Start a new server instance.
         */
        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 {
            	//这里启动一个server
                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);
                }
            }
    
            if (await) {
                await();
                stop();
            }
        }
    

    接着在方法中这句代码,会调用StandardServer里的start()方法

    getServer().start();//调用StandardServer里的start()方法
    

    注意,这里调用StandardServer里的start()方法继承自lifecycleBase

    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)) {
                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.
                ExceptionUtils.handleThrowable(t);
                setStateInternal(LifecycleState.FAILED, null, false);
                throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
            }
        }
    

    上面最重要的是这两句代码

    			setStateInternal(LifecycleState.STARTING_PREP, null, false);//设置生命周期的状态
                startInternal();
    

    追进去startInternal()方法(StandardServer里面的startInternal()方法)

    @Override
        protected void startInternal() throws LifecycleException {
    		
            fireLifecycleEvent(CONFIGURE_START_EVENT, null);
            setState(LifecycleState.STARTING);
    
            globalNamingResources.start();
    
            // Start our defined Services
            synchronized (servicesLock) {
                for (int i = 0; i < services.length; i++) {
                    services[i].start();
                }
            }
        }
    

    注意:StandardServer和StandService类很容易混淆,请看清楚
    Server遍历自身的services数组中的service并启动,这里我们追进去StandardService的start()方法。(这里其实和StandardServer一样,都是继承自lifecycle的start()方法)
    在StandardService的start()方法中,又会调用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
            //这里很重要,这个方法会启动Tomcat最顶层的容器engine
            if (engine != null) {
                synchronized (engine) {
                    engine.start();
                }
            }
    
            synchronized (executors) {
                for (Executor executor: executors) {
                    executor.start();
                }
            }
    
            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.start();
                        }
                    } catch (Exception e) {
                        log.error(sm.getString(
                                "standardService.connector.startFailed",
                                connector), e);
                    }
                }
            }
        }
    

    追进去engine的start方法看看,其实engine的start方法继承自lifecycleBase,上面已经给出了代码,lifecycleBase又会调用engine的startInternal方法(继承自ContainerBase)

    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
            Container children[] = findChildren();
            List<Future<Void>> results = new ArrayList<>();
            for (int i = 0; i < children.length; i++) {
                results.add(startStopExecutor.submit(new StartChild(children[i])));
            }
    
            MultiThrowable multiThrowable = null;
    
            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
            if (pipeline instanceof Lifecycle) {
                ((Lifecycle) pipeline).start();
            }
    
    		//关键代码
            setState(LifecycleState.STARTING);
    
            // Start our thread
            threadStart();
        }
    

    engine的startInternal方法中,setState(LifecycleState.STARTING)这句代码最为关键。追进去看看

    protected synchronized void setState(LifecycleState state)
                throws LifecycleException {
            setStateInternal(state, null, true);
        }
    

    再追进去setStateInternal(state, null, true);

    private synchronized void setStateInternal(LifecycleState state,
                Object data, boolean check) throws LifecycleException {
    
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("lifecycleBase.setState", this, state));
            }
    
            if (check) {
                // Must have been triggered by one of the abstract methods (assume
                // code in this class is correct)
                // null is never a valid state
                if (state == null) {
                    invalidTransition("null");
                    // Unreachable code - here to stop eclipse complaining about
                    // a possible NPE further down the method
                    return;
                }
    
                // Any method can transition to failed
                // startInternal() permits STARTING_PREP to STARTING
                // stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
                // STOPPING
                if (!(state == LifecycleState.FAILED ||
                        (this.state == LifecycleState.STARTING_PREP &&
                                state == LifecycleState.STARTING) ||
                        (this.state == LifecycleState.STOPPING_PREP &&
                                state == LifecycleState.STOPPING) ||
                        (this.state == LifecycleState.FAILED &&
                                state == LifecycleState.STOPPING))) {
                    // No other transition permitted
                    invalidTransition(state.name());
                }
            }
    
            this.state = state;
            //这里最关键,会触发一个“start”的生命周期事件
            String lifecycleEvent = state.getLifecycleEvent();
            if (lifecycleEvent != null) {
                fireLifecycleEvent(lifecycleEvent, data);
            }
        }
    

    这里最关键,会触发一个“start”的生命周期事件

    String lifecycleEvent = state.getLifecycleEvent();
            if (lifecycleEvent != null) {
                fireLifecycleEvent(lifecycleEvent, data);
            }
        }
    

    好,到这里tomcat已经触发了一次start的生命事件,Host是context的父容器,而且host注册了一个监听器,该监听器监听到start事件后触发Context的创建和启动。这里小弟画了一个流程图,如果上面觉得有些乱可以跟着流程图追一追源码,其实也能看到每个组件都会通过LifeCycleBase来负责启动。

    在这里插入图片描述

    下面我们来看看这个监听器。

    监听器启动Context实例

    因为监听器是注册再Host容器里的,所以我们先来看看Catalina是怎样配置和创建Host类的

    //创建host对象
            digester.addObjectCreate(prefix + "Host",
                                     "org.apache.catalina.core.StandardHost",  //创建host对象的配置
                                     "className");
            digester.addSetProperties(prefix + "Host");
            digester.addRule(prefix + "Host",
                             new CopyParentClassLoaderRule());  //会将host的parentClassloader设置为engine的,engine被设置为sharedloader
            digester.addRule(prefix + "Host",   //为host设置配置的监听
                             new LifecycleListenerRule
                             ("org.apache.catalina.startup.HostConfig",   //这个算是比较重要的吧,在里面会具体的创建context啥的
                              "hostConfigClass"));
            digester.addSetNext(prefix + "Host",
                                "addChild",
                                "org.apache.catalina.Container");  // 在engine上面调用addChild方法,用于添加当前的host到engine上面去
    

    从上面就看出来了,在创建Host对象实例的时候,也会创建一个HostConfig并被这个Host实例所引用

    而在HostConfig是一个监听器,也就是说,当发生“某个事件”的时候,他会做出“相应的反应”。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;
            }
    		//这里根据我们前面的一大堆分析,这里会触发一个“start”事件
            // Process the event that has occurred
            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”事件会触发HostConfig的start()方法
                start();
            } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
                stop();
            }
        }
    

    接下来我们追进HostConfig的start()方法

    public void start() {
    
            if (log.isDebugEnabled())
                log.debug(sm.getString("hostConfig.start"));
    
            try {
                ObjectName hostON = host.getObjectName();
                oname = new ObjectName
                    (hostON.getDomain() + ":type=Deployer,host=" + host.getName());
                Registry.getRegistry(null, null).registerComponent
                    (this, oname, this.getClass().getName());
            } catch (Exception e) {
                log.error(sm.getString("hostConfig.jmx.register", oname), e);
            }
    
            if (!host.getAppBaseFile().isDirectory()) {
                log.error(sm.getString("hostConfig.appBase", host.getName(),
                        host.getAppBaseFile().getPath()));
                host.setDeployOnStartup(false);
                host.setAutoDeploy(false);
            }
    		//这里是重要代码,我们继续追进去
            if (host.getDeployOnStartup())
                deployApps();
    
        }
    

    再追进去这个deployApps()方法

    
    '''
        这里要解释一下,这个函数会找到Tomcat目录下wepapp文件夹,并把每一个应用进行部署,即每个应用对应一个Context实例
        
    '''
    protected void deployApps() {
    
            File appBase = host.getAppBaseFile();
            File configBase = host.getConfigBaseFile();
            String[] filteredAppPaths = filterAppPaths(appBase.list());
            // Deploy XML descriptors from configBase
            deployDescriptors(configBase, configBase.list());
            // Deploy WARs
        	//如果是war包的应用,走这个分支
            deployWARs(appBase, filteredAppPaths);
            // Deploy expanded folders
        	//如果是文件夹类型的应用,走这个分析
            deployDirectories(appBase, filteredAppPaths);
    
        }
    

    我们追一下文件夹类型的,也就是这个方法deployDirectories(appBase, filteredAppPaths);

    protected void deployDirectories(File appBase, String[] files) {
    
            if (files == null)
                return;
    		//这里获取一个host的执行器,会跟context的启动和关闭有关
            ExecutorService es = host.getStartStopExecutor();
            List<Future<?>> results = new ArrayList<>();
    		//遍历目录,每个逐个部署
            for (int i = 0; i < files.length; i++) {
    
                if (files[i].equalsIgnoreCase("META-INF"))
                    continue;
                if (files[i].equalsIgnoreCase("WEB-INF"))
                    continue;
                File dir = new File(appBase, files[i]);
                if (dir.isDirectory()) {
                    ContextName cn = new ContextName(files[i], 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);
                }
            }
        }
    

    这行代码向执行器提交了一个任务,实际上就是创建应用对应的context对象

    注意这个方法名字deployDirectory,和前面的deployDirectories是单数和复数的区别,不要混淆了

    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 {
                            //这句话解析了xml文件之后,context这个变量已经引用了一个Context的实例
                            //也就是说这时候Context已经创建好了
                            context = (Context) digester.parse(xml);
                        } catch (Exception e) {
                            log.error(sm.getString(
                                    "hostConfig.deployDescriptor.error",
                                    xml), e);
                            context = new FailedContext();
                        } 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();
                }
    			//这里为这个context实例开始设置一些属性
                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());
                host.addChild(context);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString("hostConfig.deployDir.error",
                        dir.getAbsolutePath()), t);
            } finally {
                创建DeployedApplication对象,表设一个部署的应用
                deployedApp = new DeployedApplication(cn.getName(),
                        xml.exists() && deployThisXML && copyThisXml);
    
                // Fake re-deploy resource to detect if a WAR is added at a later
                // point
                deployedApp.redeployResources.put(dir.getAbsolutePath() + ".war",
                        Long.valueOf(0));
                deployedApp.redeployResources.put(dir.getAbsolutePath(),
                        Long.valueOf(dir.lastModified()));
                if (deployThisXML && xml.exists()) {//如果有context的配置文件,用context的配置
                    if (copyThisXml) {
                        deployedApp.redeployResources.put(
                                xmlCopy.getAbsolutePath(),
                                Long.valueOf(xmlCopy.lastModified()));
                    } else {
                        deployedApp.redeployResources.put(
                                xml.getAbsolutePath(),
                                Long.valueOf(xml.lastModified()));
                        // Fake re-deploy resource to detect if a context.xml file is
                        // added at a later point
                        deployedApp.redeployResources.put(
                                xmlCopy.getAbsolutePath(),
                                Long.valueOf(0));
                    }
                } else {
                    // Fake re-deploy resource to detect if a context.xml file is
                    // added at a later point
                    deployedApp.redeployResources.put(
                            xmlCopy.getAbsolutePath(),
                            Long.valueOf(0));
                    if (!xml.exists()) {
                        deployedApp.redeployResources.put(
                                xml.getAbsolutePath(),
                                Long.valueOf(0));
                    }
                }
                addWatchedResources(deployedApp, dir.getAbsolutePath(), context);
                // Add the global redeploy resources (which are never deleted) at
                // the end so they don't interfere with the deletion process
                addGlobalRedeployResources(deployedApp); //添加全局配置
            }
    
            deployed.put(cn.getName(), deployedApp);//表示该应用已经部属
    
            if( log.isInfoEnabled() ) {
                log.info(sm.getString("hostConfig.deployDir.finished",
                        dir.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
            }
        }
    

    这时候Context已经创建完成啦,同理,其它组件中会注册有监听器,在触发lifecycle事件之后,会触发相应的启动方法。在组件都启动玩之后,Catalina类中的getServer().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 {
            	
                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);
                }
            }
    
            if (await) {
                //调用await函数接收连接
                //Catalina的await()方法会调用server的await()方法
                await();
                stop();
            }
        }
    
    public void await() {
            // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
            if( port == -2 ) {
                // undocumented yet - for embedding apps that are around, alive.
                return;
            }
            if( port==-1 ) {
                try {
                    awaitThread = Thread.currentThread();
                    while(!stopAwait) {
                        try {
                            Thread.sleep( 10000 );
                        } catch( InterruptedException ex ) {
                            // continue and check the flag
                        }
                    }
                } finally {
                    awaitThread = null;
                }
                return;
            }
    
            // Set up a server socket to wait on
            try {
                awaitSocket = new ServerSocket(port, 1,
                        InetAddress.getByName(address));
            } catch (IOException e) {
                log.error("StandardServer.await: create[" + address
                                   + ":" + port
                                   + "]: ", e);
                return;
            }
    
            try {
                awaitThread = Thread.currentThread();
    
                // Loop waiting for a connection and a valid command
                while (!stopAwait) {
                    ServerSocket serverSocket = awaitSocket;
                    if (serverSocket == null) {
                        break;
                    }
    
                    // Wait for the next connection
                    Socket socket = null;
                    StringBuilder command = new StringBuilder();
                    try {
                        InputStream stream;
                        long acceptStartTime = System.currentTimeMillis();
                        try {
                            socket = serverSocket.accept();
                            socket.setSoTimeout(10 * 1000);  // Ten seconds
                            stream = socket.getInputStream();
                        } catch (SocketTimeoutException ste) {
                            // This should never happen but bug 56684 suggests that
                            // it does.
                            log.warn(sm.getString("standardServer.accept.timeout",
                                    Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
                            continue;
                        } catch (AccessControlException ace) {
                            log.warn("StandardServer.accept security exception: "
                                    + ace.getMessage(), ace);
                            continue;
                        } catch (IOException e) {
                            if (stopAwait) {
                                // Wait was aborted with socket.close()
                                break;
                            }
                            log.error("StandardServer.await: accept: ", e);
                            break;
                        }
    
                        // Read a set of characters from the socket
                        int expected = 1024; // Cut off to avoid DoS attack
                        while (expected < shutdown.length()) {
                            if (random == null)
                                random = new Random();
                            expected += (random.nextInt() % 1024);
                        }
                        while (expected > 0) {
                            int ch = -1;
                            try {
                                ch = stream.read();
                            } catch (IOException e) {
                                log.warn("StandardServer.await: read: ", e);
                                ch = -1;
                            }
                            if (ch < 32)  // Control character or EOF terminates loop
                                break;
                            command.append((char) ch);
                            expected--;
                        }
                    } finally {
                        // Close the socket now that we are done with it
                        try {
                            if (socket != null) {
                                socket.close();
                            }
                        } catch (IOException e) {
                            // Ignore
                        }
                    }
    
                    // Match against our command string
                    boolean match = command.toString().equals(shutdown);
                    if (match) {
                        log.info(sm.getString("standardServer.shutdownViaPort"));
                        break;
                    } else
                        log.warn("StandardServer.await: Invalid command '"
                                + command.toString() + "' received");
                }
            } finally {
                ServerSocket serverSocket = awaitSocket;
                awaitThread = null;
                awaitSocket = null;
    
                // Close the server socket and return
                if (serverSocket != null) {
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        // Ignore
                    }
                }
            }
        }
    

    这时候Tomcat已经启动完毕了。await函数无非就是调用socket接收请求,如果收到的请求是shutdonwn命令,就回跳出while循环,回到BootStrap的main函数中,接着main函数继续执行,程序结束。

    总结

    其实总来的说,context对象的创建就是HostConfig监听到一个“Start”事件时创建的。由于本人技术有限,只是简单的追了追源码,如果有不对的地方还希望大家批评指正,谢谢。

    展开全文
  • Tomcat启动过程分析 Tomcat默认server.xml文件 <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <Listener className="org.apache.catalina.startup....

    Tomcat启动过程分析

    Tomcat默认server.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <Server port="8005" shutdown="SHUTDOWN">
      <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
      <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
      <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
      <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
      <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
      <GlobalNamingResources>
        <Resource name="UserDatabase" auth="Container"
                  type="org.apache.catalina.UserDatabase"
                  description="User database that can be updated and saved"
                  factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
                  pathname="conf/tomcat-users.xml" />
      </GlobalNamingResources>
      <Service name="Catalina">
        <Connector port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
        <Engine name="Catalina" defaultHost="localhost">
          <Realm className="org.apache.catalina.realm.LockOutRealm">  
            <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
                   resourceName="UserDatabase"/>
          </Realm>
          <Host name="localhost"  appBase="webapps"
                unpackWARs="true" autoDeploy="true">
            <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                   prefix="localhost_access_log" suffix=".txt"
                   pattern="%h %l %u %t &quot;%r&quot; %s %b" />
          </Host>
        </Engine>
      </Service>
    </Server>
    

    Tomcat只是一个工具,为我们提供一个以war包方式运行web程序的环境而已

    server.xml文件是Tomcat的核心文件之一

    1.启动Bootstrap

    volatile修饰是为了启动唯一的Bootstrap

    public final class Bootstrap {
     	//volatile修饰的静态对象
        private static volatile Bootstrap daemon = null;
    }
    

    main()

    创建Bootstrap对象,并通过初始命令"start"来启动Tomcat

    //BootStrap.main()
    if (daemon == null) {
    	Bootstrap bootstrap = new Bootstrap();
        ......
        bootstrap.init();//通过反射创建Catalina对象
    	......
    	daemon = bootstrap;        
    }
    ......
    String command = "start";
    ......
    daemon.start(); //通过反射调用Catalina的start方法
    
    //Bootstrap.start()
    public void start() throws Exception {
        if (catalinaDaemon == null) {
            init();//bootstrap的初始化
        }
        //反射执行Catalina的start方法,执行
        Method method = catalinaDaemon.getClass().getMethod("start", (Class[]) null);
        method.invoke(catalinaDaemon, (Object[]) null);
    }
    

    综上:

    1.Tomcat的启动入口为Bootstrap的Main方法

    2.在Main方法中生成Bootstrap对象,Catalina对象

    2.Catalina是Tomcat的一个命令程序,Tomcat的启动与关闭就是对bootstrap和catalina对象的创建和销毁过程

    2.启动Catalina

    在上面Bootstrap的启动过程中会生成Catalina并通过反射调用Catalina的start方法

    //Catalina.start()
    public void start() {
    	......
        if (getServer() == null) {
            load();//加载server.xml文件并创建server
        }
        //server的初始化
        getServer().start();
      	......
    }
    

    3. server.xml文件的加载与解析

    在Catalina的start()方法中通过load()方法加载server.xml文件

    //Catalina.load()
    public void load() {
     	......
        parseServerXml(true); //加载并解析server.xml文件,会创建server,service等组件
        Server s = getServer();
        if (s == null) {
            return;
        }
    	//server赋值
        getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
        ......
        // Start the new server
        try {
        	//server初始化
            getServer().init();
        } ......
    }
    

    server.xml文件的加载解析过程

    protected void parseServerXml(boolean start) {
        ......
    
        if (serverXml != null) {
            serverXml.load(this);
        } else {
            try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
                // Create and execute our Digester
                Digester digester = start ? createStartDigester() : createStopDigester();
                InputStream inputStream = resource.getInputStream(); //server.xml流文件
                InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
                inputSource.setByteStream(inputStream);
                digester.push(this);
                ......
            }
        }
    

    创建Digester,设置规则,然后进行解析

    在server.xml的解析过程中会创建server,service等组件

    protected Digester createStartDigester() {
        // Initialize the digester
        Digester digester = new Digester();
        digester.setValidating(false);
        digester.setRulesValidation(true);
        Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
        // Ignore className on all elements
        List<String> objectAttrs = new ArrayList<>();
        objectAttrs.add("className");
        fakeAttributes.put(Object.class, objectAttrs);
        // Ignore attribute added by Eclipse for its internal tracking
        List<String> contextAttrs = new ArrayList<>();
        contextAttrs.add("source");
        fakeAttributes.put(StandardContext.class, contextAttrs);
        // Ignore Connector attribute used internally but set on Server
        List<String> connectorAttrs = new ArrayList<>();
        connectorAttrs.add("portOffset");
        fakeAttributes.put(Connector.class, connectorAttrs);
        digester.setFakeAttributes(fakeAttributes);
        digester.setUseContextClassLoader(true);
    
        //创建server
        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");
    	//server生命周期监听器
        digester.addRule("Server/Listener",
                         new ListenerCreateRule(null, "className"));
        digester.addSetProperties("Server/Listener");
        digester.addSetNext("Server/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");
    	//创建service,在server组件中添加service属性
        digester.addObjectCreate("Server/Service",
                                 "org.apache.catalina.core.StandardService",
                                 "className");
        digester.addSetProperties("Server/Service");
        digester.addSetNext("Server/Service",
                            "addService",
                            "org.apache.catalina.Service");
    	//为service添加监听器
        digester.addObjectCreate("Server/Service/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Service/Listener");
        digester.addSetNext("Server/Service/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");
    
        //Executor,共享executor的级别为service,默认情况下不开启
        digester.addObjectCreate("Server/Service/Executor",
                                 "org.apache.catalina.core.StandardThreadExecutor",
                                 "className");
        digester.addSetProperties("Server/Service/Executor");
    
        digester.addSetNext("Server/Service/Executor",
                            "addExecutor",
                            "org.apache.catalina.Executor");
    
        digester.addRule("Server/Service/Connector",
                         new ConnectorCreateRule());
        digester.addSetProperties("Server/Service/Connector",
                                  new String[]{"executor", "sslImplementationName", "protocol"});
        digester.addSetNext("Server/Service/Connector",
                            "addConnector",
                            "org.apache.catalina.connector.Connector");
    
        digester.addRule("Server/Service/Connector", new AddPortOffsetRule());
    
        digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
                                 "org.apache.tomcat.util.net.SSLHostConfig");
        digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
        digester.addSetNext("Server/Service/Connector/SSLHostConfig",
                            "addSslHostConfig",
                            "org.apache.tomcat.util.net.SSLHostConfig");
    
        digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                         new CertificateCreateRule());
        digester.addSetProperties("Server/Service/Connector/SSLHostConfig/Certificate", new String[]{"type"});
        digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
                            "addCertificate",
                            "org.apache.tomcat.util.net.SSLHostConfigCertificate");
    
        digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                                 "org.apache.tomcat.util.net.openssl.OpenSSLConf");
        digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
        digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                            "setOpenSslConf",
                            "org.apache.tomcat.util.net.openssl.OpenSSLConf");
    
        digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                                 "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
        digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
        digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                            "addCmd",
                            "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
    
        digester.addObjectCreate("Server/Service/Connector/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Service/Connector/Listener");
        digester.addSetNext("Server/Service/Connector/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");
    
        digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
        digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
                            "addUpgradeProtocol",
                            "org.apache.coyote.UpgradeProtocol");
    
        // Add RuleSets for nested elements
        digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
        digester.addRuleSet(new EngineRuleSet("Server/Service/"));
        digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
        digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
        addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
        digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
    
        // When the 'engine' is found, set the parentClassLoader.
        digester.addRule("Server/Service/Engine",
                         new SetParentClassLoaderRule(parentClassLoader));
        addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
    
        return digester;
    
    }
    

    4.启动Server

    解析server.xml文件过程中根据规则创建server组件,为Server组件添加Service组件

    //StandardServer.addService()
    @Override
    public void addService(Service service) {
        service.setServer(this);
        synchronized (servicesLock) {
            Service results[] = new Service[services.length + 1];
            System.arraycopy(services, 0, results, 0, services.length);
            results[services.length] = service;
            services = results;
            ......
                service.start(); //启动service
           	......
    

    5.启动Service

    通过生命周期方法start()---->startInternal() 初始化组件

    //standardService
    protected void startInternal() throws LifecycleException {
    
        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        setState(LifecycleState.STARTING);
    
        //初始化engine
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }
    	//初始化executor
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }
    	//启动mapperLinstener
        mapperListener.start();
    
       	//启动connectors
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
                    connector.start();
                }
            }
        }
    }
    

    service组件在初始化过程中会初始化engin,Executor,Connector等组件,这些组件都是在解析server.xml文件时添加的

    6.Engine

    7.启动Connector

    Connector是Tomcat的连接器,默认使用Http协议

    public class Connector extends LifecycleMBeanBase  {
     	//默认http1.1协议
        public Connector() {
            this("org.apache.coyote.http11.Http11NioProtocol");
        }
        ......
        public Connector(String protocol) {
        	......
            ProtocolHandler p = null;
            try {
                p = ProtocolHandler.create(protocol, apr);//创建protocolhandler
            ......
        }
    }
    

    startInternal()

    启动protocolHandler

    try {
        protocolHandler.start();
    } 
    

    8. 启动ProtocolHandler

    ProtocolHandler是负责处理请求的,一般情况下处理Http请求,因此默认使用Http协议

    AbstractProtocol

    public abstract class AbstractProtocol<S> implements ProtocolHandler,
            MBeanRegistration {
        //处理请求之前需要先监听请求,Endpoint负责绑定请求并进行监听
        private final AbstractEndpoint<S,?> endpoint;
        
        //构造方法
        public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) {
            super(endpoint);
            setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
            ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
            setHandler(cHandler);
            //NioEndpoint设置protocelHandler
            getEndpoint().setHandler(cHandler);
        }
    

    protocolHandler.start()

    protocolHandler通过endpoint监听http请求,初始endpoint

    //AbstractProtocol.start()
    public void start() throws Exception {
        if (getLog().isInfoEnabled()) {
            getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
            logPortOffset();
        }
    	//初始化endPoint
        endpoint.start();
        monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
            new Runnable() {
                @Override
                public void run() {
                    if (!isPaused()) {
                        startAsyncTimeout();
                    }
                }
            }, 0, 60, TimeUnit.SECONDS);
    }
    

    9.AbstractEndpoint

    Endpoint负责监听Socket,在监听之前先初始化并进行与socket绑定

    start()

    public final void start() throws Exception {
        if (bindState == BindState.UNBOUND) {//未绑定状态
            bindWithCleanup();
            bindState = BindState.BOUND_ON_START;
        }
        startInternal();//初始化操作,由具体子类实现
    }
    

    10. 启动NioEndpoint

    此处有三个组件:分别是Poller,PollerEvent,Acceptor

    启动NioEndpoint,设置最大连接数,开启线程监听socket等

    //NioEndpoint
    @Override
    public void startInternal() throws Exception {
    
        if (!running) {
            running = true;
            paused = false;
    
            ......
    
            // Create worker collection
            if (getExecutor() == null) {
                createExecutor();
            }
            //设置最大连接数
            initializeConnectionLatch();
    
            //初始化Poller并开启线程监听事件队列
            poller = new Poller();
            Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
            //启动Acceptor
            startAcceptorThread();
        }
    }
    

    11.启动Acceptor

    开启Acceptor线程来监听socket

    //AbstractEndpoint
    protected void startAcceptorThread() {
        acceptor = new Acceptor<>(this);
        String threadName = getName() + "-Acceptor";
        acceptor.setThreadName(threadName);
        Thread t = new Thread(acceptor, threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start();
    }
    
    展开全文
  • 上篇文章我们介绍了Tomcat的核心组件的的基本功能以及在Tomcat中的实现,但是这些组件类是如何被启动起来的?比如我们知道Context组件的功能是管理子容器Wraper的生命周期,那么在Context组件启动时,肯定要创建...

    上篇文章我们介绍了Tomcat的核心组件的的基本功能以及在Tomcat中的实现,但是这些组件类是如何被启动起来的?比如我们知道Context组件的功能是管理子容器Wraper的生命周期,那么在Context组件启动时,肯定要创建Wrapper实例并启动Wrapper。同理,我们知道Tomcat这么多组件,肯定有一个先后的启动过程(因为Tomcat的组件本来就是分层级的)。本篇文章我们就来介绍一下,Tomcat是怎么启动的。

    使用过Tomcat的我们都知道,可以通过Tomcat的/bin目录下的脚本startup.sh来启动Tomcat,那么这个脚本肯定就是Tomcat的启动入口了,执行过这个脚本之后发生了什么呢?

    1. Tomcat本质上也是一个Java程序,因此startup.sh脚本会启动一个JVM来运行Tomcat的启动类 Bootstrap
    2. Bootstrap的主要任务是初始化Tomcat的类加载器,并且创建Catalina。关于Tomcat为什么需要自己的类加载器,我们后面再介绍
    3. Catalina是一个启动类,它通过解析server.xml,创建相应的组件,并调用 Server的start方法
    4. Server组件的职责就是管理Service组件,它会负责调用Service的start方法
    5. Service组件的职责就是管理连接器和顶层容器Engine,它会调用连接器和 Engine的start方法
    6. Engine组建负责启动管理子容器,通过调用Host的start方法,将Tomcat各层容器启动起来(这里是分层级的,上层容器管理下层容器)

    1. Tomcat一键启停机制——LifeCycle

    我们再来回顾一下Tomcat各组件之间的关系,很容易发现:如果想让Tomcat能够对外提供服务,我们需要创建、组装并启动这些组件;在服务停止的时候,我们还需要释放资源,销毁这些组件,因此这是一个动态的过程。也就是说,Tomcat 需要动态地管理这些组件的生命周期

    Tomcat这么大一个框架,如何统一管理组件的创建、初始化、启动、停止和销毁?如何做到代码逻辑清晰?如何方便地添加或者删除组件?如何做到组件启动和停止不遗漏、不重复?

    在回答上述问题之前,先来看看组件之间的关系,可以发现它们具有两层关系:

    • 组件有大有小,大组件管理小组件,比如Server管理Service,Service又管理连接器和容器
    • 组件有外有内,外层组件控制内层组件,比如连接器是外层组件,负责对外交流,外层组件调用内层组件完成业务功能。也就是说,请求的处理过程是由外层组件来驱动的

    这两层关系决定了系统在创建组件时应该遵循一定的顺序:

    • 先创建子组件,再创建父组件,子组件需要被“注入”到父组件中
    • 先创建内层组件,再创建外层组件,内层组建需要被“注入”到外层组件

    因此,最直观的做法就是将图上所有的组件按照先小后大、先内后外的顺序创建出来,然后组装在一起。但实际上这个思路其实很有问题,因为这样不仅会造成代码逻辑混乱和组件遗漏,而且也不利于后期的功能扩展

    所以需要一个一种通用的、统一的方法来管理Tomcat各组件的生命周期,最好可以实现“一键启动”的效果。而Tomcat就是用LifeCycle实现了Tomcat一键启停的机制

    1.1 什么是LifeCycle

    文章最开始介绍的每个组件都要经历创建、初始化、启动这几个过程,这些状态以及状态的转化是不变的。而每个具体组件的初始化方法,启动方法是不一样的。Tomcat把不变的抽象出来成为一个接口,这个接口跟生命周期有关,叫作 LifeCycle。LifeCycle接口定义如下,每个具体的组件去实现这些方法。

    public interface Lifecycle {
        // 添加监听器
        public void addLifecycleListener(LifecycleListener listener);
        // 获取所以监听器
        public LifecycleListener[] findLifecycleListeners();
        // 移除某个监听器
        public void removeLifecycleListener(LifecycleListener listener);
        // 初始化方法
        public void init() throws LifecycleException;
        // 启动方法
        public void start() throws LifecycleException;
        // 停止方法,和start对应
        public void stop() throws LifecycleException;
        // 销毁方法,和init对应
        public void destroy() throws LifecycleException;
        // 获取生命周期状态
        public LifecycleState getState();
        // 获取字符串类型的生命周期状态
        public String getStateName();
    }

    这里我们把LifeCycle接口定义分为两部分,一部分是组件的生命周期方法,比如init()、start()、stop()、destroy()。另一部分是扩展接口就是状态和监听器,关于状态和监听器,我们文章下面再介绍。

    因为所有的组件都实现了LifeCycle接口,如果可以在父组件的init()方法里创建子组件并调用子组件的init()方法,在父组件的start()方法里调用子组件的start()方法,那么调用者就可以无差别的调用各组件的init()方法和start()方法,并且只要调用最顶层组件,也就是Server组件的init()和start()方法,整个Tomcat就被启动起来了

    1.2 LifeCycleBase抽象类

    有了LifeCycle接口,我们就要用类去实现接口。一般来说实现类不止一个,不同的类在实现接口时往往会有一些相同的逻辑,如果让各个子类都去实现一遍,就会有重复代码。那子类如何重用这部分逻辑呢?其实就是定义一个基类来实现共同的逻辑,然后让各个子类去继承它,就达到了重用的目的。基类中往往会定义一些抽象方法,并调用这些方法来实现骨架逻辑,抽象方法是留给各个子类去实现,如下图所示:

    LifeCycleBase实现了LifeCycle接口中所有的方法,还定义了相应的抽象方法交给具体子类去实现,这是典型的模板设计模式。

    我们来简单看一下LifeCycleBase的init方法,如下:

    • 检查状态的合法性,比如当前状态必须是NEW然后才能进行初始化
    • 触发INITIALIZING事件的监听器,setStateInternal()方法中,会调用监听器的业务方法
    • 调用具体子类实现的抽象方法initInternal()方法。为了实现一键式启动,具体组件在实现initInternal()方法时,又会调用它的子组件的init()方法
    • 子组件初始化后,触发INITIALIZED事件的监听器,相应监听器的业务方法就会被调用

    从而实现调用顶层组建的init方法就可以实现顶层及子组件的初始化,start()方法一个道理,这里不多介绍了。

    1.3 LifeCycle扩展——事件监听机制

    各个组件init()和start()方法的具体实现是复杂多变的,比如在Host容器的启动方法里需要扫描webapps目录下的Web 应用,创建相应的Context容器,如果将来需要增加新的逻辑,直接修改start()方法会违反开闭原则(为了扩展系统的功能, 你不能直接修改系统中已有的类,但是你可以定义新的类),那如何解决这个问题呢?

    组件的init()和start()调用是由它的父组件的状态变化触发的,上层组件的初始化会触发子组件的初始化,上层组件的启动会触发子组件的启动。因此我们把组件的生命周期定义成一个个状态,把状态的转变看作是一个事件。而事件是有监听器的,在监听器里可以实现一些逻辑,并且监听器也可以方便的添加和删除,这就是典型的观察者模式。

    这就是为什么LifeCycle接口中有两个添加和删除监听器的方法的原因。但除了上述监听器维护方法,我们还需要定义一些状态。所以Tomcat定义一个枚举LifeCycleState来表示组件有哪些状态。

    可以看到组件的生命周期有NEW、INITIALIZING、INITIALIZED、STARTING_PREP、STARTING、STARTED等,而一旦组件到达相应的状态就触发相应的事件,比如 NEW状态表示组件刚刚被实例化。而当init()方法被调用时,状态就变成INITIALIZING 状态,这个时候,就会触发BEFORE_INIT_EVENT事件,如果有监听器在监听这个事件,它的方法就会被调用。

    下面我们来看一下在上述init方法中触发事件监听的方法setStateInternal:

    private synchronized void setStateInternal(LifecycleState state,
            Object data, boolean check) throws LifecycleException {
    
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("lifecycleBase.setState", this, state));
        }
    
        if (check) {
            // Must have been triggered by one of the abstract methods (assume
            // code in this class is correct)
            // null is never a valid state
            if (state == null) {
                invalidTransition("null");
                // Unreachable code - here to stop eclipse complaining about
                // a possible NPE further down the method
                return;
            }
    
            // Any method can transition to failed
            // startInternal() permits STARTING_PREP to STARTING
            // stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
            // STOPPING
            if (!(state == LifecycleState.FAILED ||
                    (this.state == LifecycleState.STARTING_PREP &&
                            state == LifecycleState.STARTING) ||
                    (this.state == LifecycleState.STOPPING_PREP &&
                            state == LifecycleState.STOPPING) ||
                    (this.state == LifecycleState.FAILED &&
                            state == LifecycleState.STOPPING))) {
                // No other transition permitted
                invalidTransition(state.name());
            }
        }
    
        // 设置状态
        this.state = state;
        // 触发事件
        String lifecycleEvent = state.getLifecycleEvent();
        if (lifecycleEvent != null) {
            fireLifecycleEvent(lifecycleEvent, data);
        }
    }
    
    protected void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(this, type, data);
        // lifecycleListeners为监听器数组
        for (LifecycleListener listener : lifecycleListeners) {
            // 触发监听器的监听逻辑
            listener.lifecycleEvent(event);
        }
    }

    可以看到,通过setStateInternal方法,将LifeCycleBase的state成员变量设置为新的状态,并且触发了监听器的监听逻辑。那么什么时候、谁把监听器注册进来的呢?主要分为以下两种情况:

    • Tomcat自定义了一些监听器,这些监听器是父组件在创建子组件的过程中注册到子组件的。比如MemoryLeakTrackingListener监听器,用来检测 Context容器中的内存泄漏,这个监听器是Host容器在创建Context容器时注册到Context中的
    • 还可以在server.xml中定义自己的监听器,Tomcat在启动时会解析 server.xml,创建监听器并注册到容器组件

    到这里我们可以得出结论:

    • Tomcat通过LifeCycle实现了一键启停(调用顶层组件的init方法就可以实现所有组件的初始化,调用顶层组件的start方法就可以完成所有组件的启动)
    • Tomcat通过LifeCycle事件监听机制,实现了对生命周期过程的优雅扩展
    • 另外LifeCycle还实现了一个状态机,管理LifeCycle的状态转化(具体在LifeCycleBase的init、start、stop、destroy方法中,有兴趣可以去看一下,代码比较简单),比如:
      • 当组件在STARTING_PREPSTARTINGSTARTED时,调用start()方法没有任何效果
      • 当组件在NEW状态时,调用start()方法会导致init()方法被立刻执行,随后start()方法被执行
      • 当组件在STOPPING_PREPSTOPPINGSTOPPED时,调用stop()方法没有任何效果
      • 当一个组件在NEW状态时,调用stop()方法会将组件状态变更为STOPPED,比较典型的场景就是组件启动失败,其子组件还没有启动。当一个组件停止的时候,它将尝试停止它下面的所有子组件,即使子组件还没有启动

    2. Bootstrap启动入口

    2.1 main()

    本文最开始,我们介绍到,Tomcat是通过startup.sh调用了Bootstra的main方法启动的,这里我们来分析一下Bootstrap的main方法。

    public static void main(String args[]) {
    
        synchronized (daemonLock) {
            if (daemon == null) {
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try {
                    // 1. 初始化Bootstrap,主要包括实例化Tomcat特有的类加载器和Catalina实例
                    bootstrap.init();
                } catch (Throwable t) {
                    handleThrowable(t);
                    t.printStackTrace();
                    return;
                }
                daemon = bootstrap;
            } else {
                // When running as a service the call to stop will be on a new
                // thread so make sure the correct class loader is used to
                // prevent a range of class not found exceptions.
                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 t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    }

    核心方法就是调用init方法初始化deamon成员变量,以及当接收到start命令时,调用deamon的load和start方法启动。

    2.2 init()

    下面来看一下Bootstrap的init方法:

    public void init() throws Exception {
        // 1. 初始化Tomcat类加载器
        initClassLoaders();
    
        Thread.currentThread().setContextClassLoader(catalinaLoader);
    
        SecurityClassLoad.securityClassLoad(catalinaLoader);
    
        // Load our startup class and call its process() method
        if (log.isDebugEnabled())
            log.debug("Loading startup class");
        // 2. 实例化Catalina实例
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();
    
        // 3. 反射调用Catalina的setParentClassLoader方法,将sharedLoader设置为Catalina的parentClassLoader成员变量
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);
    
        catalinaDaemon = startupInstance;
    }
    1. 创建commonLoader、catalinaLoader、sharedLoader类加载器(关于Tomcat的类加载器,后面文章单独介绍,这里我们把关注点放在Tomcat的启动上)
    2. 实例化org.apache.catalina.startup.Catalina对象,并赋值给静态成员 catalinaDaemon,以sharedLoader作为入参通过反射调用该对象的 setParentClassLoader方法

    这里如果不深入探讨Tomcat类加载器的话,我们可以简单理解为init方法,就是用来构造Catalina实例的。

    2.3 load()

    private void load(String[] arguments) throws Exception {
    
        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled()) {
            log.debug("Calling startup class " + method);
        }
        method.invoke(catalinaDaemon, param);
    }

    通过反射调用catalinaDaemon对象的load方法,catalinaDaemon对象就是在上面的init方法中实例化的。Catalina的load方法,我们下面再介绍,不过我们可以猜想,Catalina的load方法中应该会完成一些Tomcat组件的初始化操作。

    2.4 start()

    public void start() throws Exception {
        if (catalinaDaemon == null) {
            init();
        }
    
        Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
        method.invoke(catalinaDaemon, (Object [])null);
    }

    start方法比较简单,就是通过反射调用catalinaDaemon对象上的start方法,该start方法,完成了Tomcat的启动。

    3. Catalina

    通过上面的介绍我们知道,Bootstrap的main方法中,就是调用了Catalina的load和start方法,从而完成了Tomcat的启动。这里我们来继续看一下这两个方法。

    3.1 load()

    public void load() {
    
        if (loaded) {
            return;
        }
        loaded = true;
    
        long t1 = System.nanoTime();
    
        initDirs();
    
        // Before digester - it may be needed
        initNaming();
    
        // 1. 解析server.xml,实例化各Tomcat组件
        parseServerXml(true);
        Server s = getServer();
        if (s == null) {
            return;
        }
    
        // 2. 为Server组件实例设置Catalina相关成员value
        getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
    
        // Stream redirection
        initStreams();
    
        // 3. 调用Server组件的init方法,初始化Tomcat各组件
        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(sm.getString("catalina.initError"), e);
            }
        }
    
        if(log.isInfoEnabled()) {
            log.info(sm.getString("catalina.init", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
        }
    }

    Catalina的init方法主要完成server.xml的解析以及Tomcat各组建的初始化。这里要着重强调的的有两点:

    • parseServerXml()方法完成了xml配置到Tomcat各组件的Java对象的转化,也就是说,通过调用该方法,我们之前介绍的Tomcat各组件都实例化出来了(这里parseServerXml()方法解析的细节以及关于使用web.xml配置Context,我们后面通过专门的文章再介绍)
    • getServer().init()方法,使用了我们上面介绍的LifeCycle机制,完成了Server及以下组件的初始化

    3.2 start()

    public void start() {
    
        if (getServer() == null) {
            load();
        }
    
        if (getServer() == null) {
            log.fatal(sm.getString("catalina.noServer"));
            return;
        }
    
        long t1 = System.nanoTime();
    
        // 1. start server
        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;
        }
    
        if (log.isInfoEnabled()) {
            log.info(sm.getString("catalina.startup", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
        }
    
        // 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);
            }
        }
    
        if (await) {
            await();
            stop();
        }
    }

    主要是调用Server组件的start方法,启动Tomcat的所有组件。

    到这里我们知道,Tomcat是通过调用Catalina的load()和start()方法,完成了Tomcat的启动。load()方法负责组件的初始化,start()方法负责组件的启动。关于Tomcat组件的初始化,其中Host及其下级组件的初始化比较特殊,并没有在Engine的初始化方法中完成Host及下级组件的初始化。另外Context的实例化和启动过程也比较特殊(在上述parseServerXml方法中,并没有实例化,是在Host组件启动时动态生成的),另外我们知道Tomcat web应用支持热更新,也与Context的启动策略有关。关于Context实例化和初始化过程,我们先挖个坑,本篇文章中不做详细介绍,在后面用专门的文章详细介绍。

    4. Server

    这里我们着重来介绍一下Server组件的init和start方法。我们知道LifeCycle机制,init方法其实最终调用的是StandardServer的initInternal方法,start方法最终调用的是StandardServer的initInternal方法。

    4.1 initInternal()

    protected void initInternal() throws LifecycleException {
    
        // 1. 调用LifeCycleMBeanBase的initInternal方法,注册JMX Mbean
        super.initInternal();
    
        // Initialize utility executor
        reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
        register(utilityExecutor, "type=UtilityExecutor");
    
        // Register global String cache
        // Note although the cache is global, if there are multiple Servers
        // present in the JVM (may happen when embedding) then the same cache
        // will be registered under multiple names
        onameStringCache = register(new StringCache(), "type=StringCache");
    
        // Register the MBeanFactory
        MBeanFactory factory = new MBeanFactory();
        factory.setContainer(this);
        onameMBeanFactory = register(factory, "type=MBeanFactory");
    
        // Register the naming resources
        globalNamingResources.init();
    
        // Populate the extension validator with JARs from common and shared
        // class loaders
        if (getCatalina() != null) {
            ClassLoader cl = getCatalina().getParentClassLoader();
            // Walk the class loader hierarchy. Stop at the system class loader.
            // This will add the shared (if present) and common class loaders
            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();
            }
        }
        // 2. 调用所有子组件Service的init方法,初始化service
        for (Service service : services) {
            service.init();
        }
    }

    4.2 startInternal()

    protected void startInternal() throws LifecycleException {
    
        // 1. 触发LifeCycle事件监听
        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);
    
        globalNamingResources.start();
    
        // 2. 启动所有Server的子组件Service
        synchronized (servicesLock) {
            for (Service service : services) {
                service.start();
            }
        }
    
        if (periodicEventDelay > 0) {
            monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
                    new Runnable() {
                        @Override
                        public void run() {
                            startPeriodicLifecycleEvent();
                        }
                    }, 0, 60, TimeUnit.SECONDS);
        }
    }

    5. Service

    5.1 initInternal()

    protected void initInternal() throws LifecycleException {
    
        super.initInternal();
    
        if (engine != null) {
            engine.init();
        }
    
        // Initialize any Executors
        for (Executor executor : findExecutors()) {
            if (executor instanceof JmxEnabled) {
                ((JmxEnabled) executor).setDomain(getDomain());
            }
            executor.init();
        }
    
        // Initialize mapper listener
        mapperListener.init();
    
        // Initialize our defined Connectors
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                connector.init();
            }
        }
    }
    1. 首先,往jmx中注册StandardService
    2. 初始化Engine
    3. 如果存在Executor线程池,进行init操作,这个Excecutor是tomcat的接口,继承至java.util.concurrent.Executor和org.apache.catalina.Lifecycle
    4. 初始化Connector连接器,默认有http1.1、ajp连接器,而这个Connector初始化过程,又会对ProtocolHandler进行初始化

    5.2 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) {
                engine.start();
            }
        }
    
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }
    
        mapperListener.start();
    
        // Start our defined Connectors second
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
                    connector.start();
                }
            }
        }
    }

    跟上述initInternal方法比较类似,只不过是调用各子组件(Engine、Connector)的start方法,启动子组件。

    6. Engine

    6.1 initInternal()

    protected void initInternal() throws LifecycleException {
        // Ensure that a Realm is present before any attempt is made to start
        // one. This will create the default NullRealm if necessary.
        getRealm();
        super.initInternal();
    }

    可以看到,Engine的init()初始化方法中,并没有去调用子组件Host的init方法去初始化Host,那么Host组件是在什么时候初始化的呢?答案是在Host的start方法中,在start方法中先进行Host组件的初始化,然后再进行启动操作

    至于为什么没有在Engine的初始化方法中,初始化子组件Host。可能是出于效率考虑。Host、Context、Wrapper这些容器和具体的webapp应用相关联了,初始化过程会更加复杂耗时,在start阶段用多线程完成初始化以及start生命周期,效率可能会更高

    6.2 startInternal()

    protected synchronized void startInternal() throws LifecycleException {
    
        // Log our server identification information
        if (log.isInfoEnabled()) {
            log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
        }
    
        // Standard container startup
        super.startInternal();
    }

    直接调用父类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
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            results.add(startStopExecutor.submit(new StartChild(child)));
        }
    
        MultiThrowable multiThrowable = null;
    
        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
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).start();
        }
    
        setState(LifecycleState.STARTING);
    
        // Start our thread
        if (backgroundProcessorDelay > 0) {
            monitorFuture = Container.getService(ContainerBase.this).getServer()
                    .getUtilityExecutor().scheduleWithFixedDelay(
                            new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
        }
    }

    可以看到,在ContainerBase中,使用了多线程来启动子组件。并且在最后通过setState(LifecycleState.STARTING)触发了事件监听。

    这里启动子组件,使用了线程池,将子组件的启动任务提交到线程池。

    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;
        }
    }

    可以看到,线程池中执行的任务最终调用的还是子组件(Host)的start()方法。我们来看一下Host的start()方法,其实Host的start方法是继承自ContainerBase的,如下:

    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)) {
            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());
        }
    }

    可以看到,如果如果state为LifecycleState.NEW,会先进性初始化,然后在调用startInternal启动组件。这就是我们上面介绍的,Engine并没有在init方法中初始化子组件Host,而是在启动过程start方法中初始化的。

    7. Host

    7.1 initInternal()

    protected void initInternal() throws LifecycleException {
        reconfigureStartStopExecutor(getStartStopThreads());
        super.initInternal();
    }

    可以看到Host的init方法中也没有初始化子组件Context,Context组件的初始化跟Host组件初始化类似,也是跟随者Context的启动start过程初始化的。

    7.2 startInternal()

    protected synchronized void startInternal() throws LifecycleException {
    
        // Set error report valve
        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).getConstructor().newInstance();
                    getPipeline().addValve(valve);
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString(
                        "standardHost.invalidErrorReportValveClass",
                        errorValve), t);
            }
        }
        super.startInternal();
    }

    可以看到,Host的start方法中,仅仅是为Host的piline添加了一些Valve,随后调用了父类ContainerBase的startInternal方法。虽然ContainerBase的startInternal方法中可以启动子组件,但好像server.xml中并么有配置Host子组件Context相关的信息,所以也没办法去启动Context组件。那么Context子组件是如何实例化,并初始化、启动的的呢?这个过程比较复杂,我们后面的文章单独介绍。

    关于Tomcat的启动过程,我们先介绍到这,至于Context和Wrapper组件的初始化和启动过程,我们后面再专门介绍。本文,我们介绍了Tomcat如何实现了一键启停,主要介绍了LifeCycle机制和一些基础类的初始化启动过程,相信通过本文,大家可以对Tomcat的“一键启停”过程有一个大致的认识。

    参考链接:

    1. Tomcat源码

    2. 《深入了解Tomcat&Jetty》

    展开全文
  • 源码下载: tomcat官网 解压下载好的源码包,解压路径不要有英文,不然编译会报错 在解压好的文件夹里新增pom.xml文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns=...

    本机环境: win10 + jdk1.8

    tomcat版本: apache-tomcat-8.5.58

    源码下载: tomcat官网

    image-20210220190544753

    1. 解压下载好的源码包,解压路径不要有英文,不然编译会报错

    2. 在解压好的文件夹里新增pom.xml文件

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      
          <modelVersion>4.0.0</modelVersion>
          <groupId>org.apache</groupId>
          <artifactId>tomcat</artifactId>
          <name>apache-tomcat-8.5.58</name>
          <version>8.5.58</version>
      
          <build>
              <finalName>Tomcat-8.5.58</finalName>
              <sourceDirectory>java</sourceDirectory>
              <testSourceDirectory>test</testSourceDirectory>
              <resources>
                  <resource>
                      <directory>java</directory>
                  </resource>
              </resources>
              <testResources>
                  <testResource>
                      <directory>test</directory>
                  </testResource>
              </testResources>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-compiler-plugin</artifactId>
                      <version>3.5.1</version>
                      <configuration>
                          <encoding>UTF-8</encoding>
                          <source>1.8</source>
                          <target>1.8</target>
                      </configuration>
                  </plugin>
              </plugins>
          </build>
      
          <dependencies>
              <dependency>
                  <groupId>junit</groupId>
                  <artifactId>junit</artifactId>
                  <version>4.12</version>
                  <scope>test</scope>
              </dependency>
              <dependency>
                  <groupId>org.easymock</groupId>
                  <artifactId>easymock</artifactId>
                  <version>3.4</version>
                  <scope>test</scope>
              </dependency>
      
              <dependency>
                  <groupId>org.apache.ant</groupId>
                  <artifactId>ant</artifactId>
                  <version>1.10.0</version>
              </dependency>
              <dependency>
                  <groupId>wsdl4j</groupId>
                  <artifactId>wsdl4j</artifactId>
                  <version>1.6.2</version>
              </dependency>
              <dependency>
                  <groupId>javax.xml</groupId>
                  <artifactId>jaxrpc</artifactId>
                  <version>1.1</version>
              </dependency>
              <dependency>
                  <groupId>org.eclipse.jdt.core.compiler</groupId>
                  <artifactId>ecj</artifactId>
                  <version>4.6.1</version>
              </dependency>
              <!-- https://mvnrepository.com/artifact/org.glassfish/javax.xml.rpc -->
              <dependency>
                  <groupId>org.glassfish</groupId>
                  <artifactId>javax.xml.rpc</artifactId>
                  <version>3.0.1-b03</version>
              </dependency>
      
          </dependencies>
      </project>
      
    3. 项目通过idea打开(注意项目配置要与第4点的版本一致,不要一个用jdk8一个用jdk11)

      image-20210220194240401

    4. 启动配置

      image-20210220193436657

      选择org.apache.catalina.startup.Bootstrap作为启动文件

      image-20210220193511579

      添加VM Option

      -Dcatalina.home=E:/sources/springmvc/apache-tomcat-8.5.58-src
      -Dcatalina.base=E:/sources/springmvc/apache-tomcat-8.5.58-src
      -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
      -Djava.util.logging.config.file=E:/sources/springmvc/apache-tomcat-8.5.58-src/conf/logging.properties
      
    5. 修改MANIFEST.MT文件

      image-20210220193758522

      image-20210220193822399

    6. 启动项目,在test目录中的文件报错的话就直接注释

    7. 启动成功,登录localhost:8080出现500错误

      image-20210220193905997

    8. 错误是因为没有配置初始化JSP

      1. ContextConfig类中,configureStart方法中找到webConfig();

      2. 在其后添加context.addServletContainerInitializer(new JasperInitializer(), null);

        image-20210220194130232

    9. 启动成功,登录localhost:8080成功

    展开全文
  • Tomcat启动带动Spring IoC容器启动流程源码分析目录概述原理概述相关工具如下:分析:小结:参考资料和推荐阅读 LD is tigger forever,CG are not brothers forever, throw the pot and shine forever. Modesty ...
  • Tomcat 9 源码分析(1)— 启动与停止 前言 本文分析的Tomcat版本为Tomcat9.0,该版本与Tomcat8,Tomcat7大体一致,仅在部分地方有所改动,而目前最新的Tomcat10则与之前的版本相差较大。 这是本人第一次阅读主流...
  • 环境以下是我编译Tomcat所采用的环境 OS X Yosemite Version 10.10.5 Intellij IDEA 2017.1 Java version 1.8.0_65 Apache Maven 3.3.9 Tomcat 9.0.0.M21 ...(2)Tomcat源码访问Tomcat下载地址,最底部
  • 我们一般都是使用的web环境,也就是创建的上下文是AnnotationConfigServletWebServerApplicationContext这个上下文,调用refresh这个方法时,会根据自动装配的是tomcat还是其他web服务器来创建一个webserver并启动 ...
  • SpringBoot相信很多同学都非常了解,实际工作中也经常使用到。但是不知道大家在使用过程中有没有想过一个问题,SpringBoot内嵌tomcat...内嵌tomcat启动服务的好处又是什么呢?本文将结合SpringBoot源码探讨下这些问题。
  • 1.SpringApplication 我们先创建一个基本的SpringBoot项目,然后找到...现在我们来思考,是DispatcherServlet先创建还是先启动tomcat,我们直接Debug,我们会发现会先启动Tomcat然后在去创建DispatcherServlet
  • 愿你越努力越幸运「歇一歇,继续奔跑」继上篇讲解完Tomcat源码分析(2)—整体结构与组件后,笔者接下来将分析下tomat的启动过程,层层地剖开tomcat启动时做了哪些事情、如何加载...
  • Tomcat源码分析--停止

    千次阅读 多人点赞 2021-03-21 15:45:46
    在前文Tomcat源码分析--启动中,分析了Tomcat是如何启动并且阻塞主线程的:在我们提供port的前提下,会首先注册一个shutdownHook,然后根据port创建一个serverSocket阻塞等待socket连接,直到收到shutdown指令以达到...
  • 在之前的篇章中,基本上了解了tomcat的整体架构,并通过源码调试的方式分析tomcat初始化过程中各个组件主要做的事情,我们知道,tomcat作为一款企业应用级的容器,其最重要的功能就是发布应用,接收客户端的请求并...
  • 理解思路 继承结构 ...//这里端口8005负责监听tomcat的关闭请求 <Server port="8005" shutdown="SHUTDOWN"> //监听器相关 <Listener className="org.apache.catalina.startup.VersionLoggerL
  • jar做了什么疑惑出现Jar包的打包插件及核心方法jar包目录结构META-INF内容Archive的概念JarLauncherURLStreamHandlerSpring Boot的Jar应用启动流程总结总结SpringBoot是如何启动Spring容器源码:1. 创建...
  • Tomcat源码分析--启动

    万次阅读 多人点赞 2021-03-21 13:12:07
    首先找到catalina.sh中的启动脚本: eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \ -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ -...
  • 《史上最全Tomcat源码解析》Tomcat启动过程 文章目录《史上最全Tomcat源码解析》Tomcat启动过程前言一、启动过程二、使用步骤1.引入库2.读入数据总结 前言 在上一章对Tomcat的架构进行了分析,这一章,我们来看...
  • tomcat不管是在以前的SSH框架,还是SSM,到现在的SpringBoot再到SpringCloud。都一直在使用,只是从之前的外用...所以说学习tomcat源码是有必要的。了解其内在原理,我们才能在开发和优化性能的时候才能游刃有余。
  • Tomcat源码分析环境搭建
  • Tomcat 源码分析

    2021-02-25 15:38:03
    一、 Tomcat 源码分析 1 Tomcat 架构图 1.1 Server Server 服务器的意思,代表整个 tomcat 服务器,一个 tomcat 只有一个 Server Server 中包含至少一个 Service 组件,用于提供具体服务。这个在配置文件中也...
  • 由于篇幅的原因, 很难把Tomcat所有的知识点都放到同一篇文章中, 我将把Tomcat系列文章分为Tomcat启动, Tomcat中各模块的介绍和Tomcat中的设计模式三部分, 欢迎阅读与关注. 一:通过idea搭建Tomcat源码阅读环境 ...
  • Tomcat架构图 网上提供了的Tomcat成熟的架构图,感觉比较形象和全面贴出来看下。...我们看下整体的初始化和启动的流程,在理解的时候可以直接和Tomcat架构设计中组件关联上: 这里涉及的组件有: ...
  • 在 Spring WebFlux源码分析(1)-服务启动流程 中笔者大致分析了 Spring WebFlux 的启动流程,其实这部分可以说是 SprintBoot 的启动主流程,Spring WebMVC 的启动也由此触发。需要注意的是, Spring WebMVC 框架默认...
  • 虽然本文的源码篇幅也不短,但是 Tomcat源码毕竟不像 Doug Lea 的并发源码那么“变态”,对于大部分读者来说,阅读难度比之前介绍的其他并发源码要简单一些,所以读者不要觉得有什么压力。本文基于 Tomcat 当前...
  • tomcat的start启动过程,使用socket网络编程来监听8080端口,使用nio优化bio,使用线程池技术来实际处理请求。
  • 其中在介绍Context解析时,提到,多数情况下,并不需要在server.xml中配置Context,而是由HostConfig自动扫描部署目录,以context.xml文件为基础进行解析创建(如果通过IDE启动Tomcat并部署应用,其Context配置将会...
  • Tomcat启动流程 一般启动Tomcat会是运行startup.bat或者startup.sh文件,实际上这两个文件最后会调用org.apache.catalina.startup.Bootstrap类的main方法, 那么我们要跟踪源码的话首先要找到源码中的BootStrap类的...
  • Bootstrap Loader(启动类加载器):加载lib目录下或者System.getProperty("sun.boot.class.path")、或者-XBootclasspath所指定的路径或jar Extended Loader(扩展类加载器):加载lib\ext目录下或者System....
  • tomcat源码分析

    2021-12-04 10:21:45
    Tomcat源码分析 (二)----- Tomcat整体架构及组件目录概 述小结参考资料和推荐阅读 LD is tigger forever,CG are not brothers forever, throw the pot and shine forever. Modesty is not false, solid is not ...
  • Tomcat中的容器是有层级结构的,每层容器关注点不同 当请求到来时,会将请求交给最上层的容器,当上层容器处理完之后,会交给下层容器 每层容器都有一个Pipeline对象,Piple中包含了多个Valve,每个Valve代表了对...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 27,964
精华内容 11,185
关键字:

tomcat启动源码分析