Tomcat源码解析:5、Tomcat启动

Bootstrap

org.apache.catalina.startup.Bootstrap类是Tomcat的入口,当我们想在IDE中运行Tomcat进行调试,可以找到这个类直接运行main方法。

我们看下Bootstrap的main方法

    public static void main(String args[]) {

        if (daemon == null) {
            // 实例化Bootstrap对象
            Bootstrap bootstrap = new Bootstrap();
            try {
            	 // 调用init进行Tomcat初始化,这节主要讲这个
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
            String command = "start";
            // args数组为空,所以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);
                // Tomcat启动的方法,后面在讲start
                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);
        }

    }

上面的代码,精简如下

	Bootstrap bootstrap = new Bootstrap();
	/*
	 * 初始化过程
	 */
	bootstrap.init();
	daemon = bootstrap;
	daemon.setAwait(true);
    daemon.load(args);
    /*
	 * 启动过程
	 */
    daemon.start();

上节已经把Tomcat初始化讲完,也就是init()、load()方法,这节讲start()方法,这个过程涉及到的流程比较多,相对上一节比较复杂一点。

setAwait()方法

	/*
	 * 实际上是反射调用了Catalina.setAwait方法
	 */
    public void setAwait(boolean await)
        throws Exception {

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

所以Bootstrap.setAwait方法,实际上调用的是Catalina.setAwait方法,标识await为true。这个标识作用是阻塞主线程,阻塞的方式是开启一个ServerSocket监听关闭端口,直到ServerSocket接受到SHUTDOWN命令,才结束主线程。后面分析。

下面我们开始看下Tomcat启动过程,也就是Boostrap.start

Boostrap
	/*
	 * 实际上调用的是Catalina的start方法
	 */
    public void start()
        throws Exception {
        if( catalinaDaemon==null ) init();

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

    }

Catalina

Catalina.start()

    public void start() {
		
		/*
		 * 如果Server实例为空,那么调用load()
		 */
        if (getServer() == null) {
            load();
        }

		/*
		 * 如果到达这里Server实例还为空,那么直接打印异常信息,return
		 */
        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }

        long t1 = System.nanoTime();

        // Start the new server
        try {
        	// 调用Server的start方法,启动Server
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
            	// 如果Server.start()启动失败,则调用Server.destroy()销毁组件
                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
        // 注册一个SHUTDOWN钩子函数,用于Tomcat关闭后清理资源
        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);
            }
        }
		
		// 如果await=true,那么调用 await(),阻塞主线程
        if (await) {
            await();
            stop();
        }
    }

简单总结下Catalina的start方法,主要为三点:

  • 判断Server实例是否为空,如果为空那么调用load()初始化组件
  • 如果Server实例不为空,那么调用Server.start()方法启动组件,我们上一节说过组件的初始化时由父组件驱动子组件进行初始化的,那么启动也是一样的,由父组件驱动子组件进行启动
  • 如果await为true。那么它会调用await()方法进行阻塞当前主线程

下面我们先看await()方法是如何阻塞当前主线程的,在看Server.start()去启动各个组件

Catalina:
	/*
	 * 实际上调用的是Server.await()
	 */
    public void await() {

        getServer().await();

    }
    
Server:
    public void await() {
    	// port = -2,那么直接return,不阻塞当前线程了。这个port怎么来,后面会说
        if( port == -2 ) {
            // undocumented yet - for embedding apps that are around, alive.
            return;
        }
        /*
         * 如果port = -1,则进去循环,则Tomcat只能调用Thread.interrupt()来关闭
         */
        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
        /*
         * 如果port != -1 && port != -2,则会到达这里
         */
        try {
        	// 开启一个ServerSocket,监听了默认8085端口
            awaitSocket = new ServerSocket(port, 1,
                    InetAddress.getByName(address));
        } catch (IOException e) {
            log.error("StandardServer.await: create[" + address
                               + ":" + port
                               + "]: ", e);
            return;
        }

        try {
            awaitThread = Thread.currentThread();
            // 这里开启一个死循环
            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连接,如果没有连接,则一直阻塞
                    	 * 所以它是靠这种方式进行线程阻塞的。
                    	 */
                        socket = serverSocket.accept();
                        // 设置Socket超时时间为10s
                        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;
                        }
                        // Control character or EOF (-1) terminates loop
                        if (ch < 32 || ch == 127) {
                            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
                    }
                }
                // 如果读取到的字符串是关闭字符串,默认是SHUTDOWN,也可以设置的,后面说
                boolean match = command.toString().equals(shutdown);
                if (match) {
                	// 匹配成功,则直接break跳出循环,结束该线程
                    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
                }
            }
        }
    }

上面说的port和关闭字符串都可以在server.xml进行设置
在这里插入图片描述

上面的port:关闭端口,shutdown:关闭字符串

大家如果感兴趣,可以手动测试一下,本机启动Tomcat,然后启动成功后,使用Telnet工具连接8005端口,然后发送SHUTDOWN,然后观察Tomcat的状态即可。

下面接着来说Server.start方法,开始进入Tomcat组件启动的源码分析

因为StandardServer继承的是LifecycleMBeanBase,所以实际上调用的是startInternal,不懂的可以去看我之前写的Tomcat生命周期Lifecycle

StandardServer

protected void startInternal() throws LifecycleException {
	
 		/*
 		 * 给Server的监听器发送CONFIGURE_START_EVENT事件
 		 * 这些监听器主要可以看server.xml文件中server标签的下一级listener都是
 		 * Server的监听器 		 
 		 */
        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
		/*
		 * 设置Server状态为LifecycleState.STARTING
		 * 并且给监听器发送各种事件,就不深究了
		 * 
		 */
        setState(LifecycleState.STARTING);

        globalNamingResources.start();

        // Start our defined Services
        // 遍历Service,调用start方法,默认只有一个
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
    }

紧接着我们看下Service的start方法,实现类为StandardService,因为它也继承了LifecycleMBeanBase,所以实际上调用的是StandardService的startInternal方法

StandardService

protected void startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        // 设置Service的state为LifecycleState.STARTING
        setState(LifecycleState.STARTING);

        // Start our defined Container first
        // 调用Engine的start方法
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }
		
		// 如果有配置自定义的Executor,则调用它的start方法
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }

        mapperListener.start();
	
		/*
		 * 循环调用Connector.start()
		 * 默认有两个,Tomcat初始化的时候说过,
		 * 1个是处理Http请求的Connector
		 * 1个是处理AJP请求的Connector。当然这里以Http请求为主
		 */
        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);
                }
            }
        }
    }

简单总结下Service的start

  • 调用了Engine的start方法
  • 如果有自定义Executor,那么循环遍历Executor集合,依次调用它的start方法
  • 循环调用Connector的start方法。默认有2个Connector,1个是处理Http请求的Connector,1个是处理AJP请求的Connector。当然这里以Http请求为主

首先看下Engine的start方法,实现类是StandardEngine,它继承了ContainerBase,所以调用了startInternal方法

StandardEngine

StandardEngine:
    protected synchronized void startInternal() throws LifecycleException {

        // Log our server identification information
        if(log.isInfoEnabled())
            log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());

        // 调用父类ContainerBase的startInternal()
        super.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();
        }

        /*
         * 首先获取子容器,我们回想一下第一节讲的Tomcat架构
         * Engine是顶层容器
         * Host是Engine的子容器
         * Context是Host的子容器
         * 因为这里是Engine,所以这里获取的Host容器
         */
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (int i = 0; i < children.length; i++) {
        	// 往startStopExecutor提交一个Task,这个startStopExecutor在上一节有说过。默认线程数只有1
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }

        boolean fail = false;
        // 遍历futrue,调用get()阻塞,直到子容器启动完毕
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Exception e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                fail = true;
            }

        }
        if (fail) {
            throw new LifecycleException(
                    sm.getString("containerBase.threadedStartFailed"));
        }

        // Start the Valves in our pipeline (including the basic), if any
        if (pipeline instanceof Lifecycle)
            ((Lifecycle) pipeline).start();

		// 设置当前Engine的start为LifecycleState.STARTING
        setState(LifecycleState.STARTING);

        // 开启一个线程,这个先不说,这个跟session机制有关
        threadStart();

    }
    private static class StartChild implements Callable<Void> {

        private Container child;

        public StartChild(Container child) {
            this.child = child;
        }

        @Override
        public Void call() throws LifecycleException {
        	// 实际上调用的是子容器的start方法
            child.start();
            return null;
        }
    }

紧接着我们看下Host的start方法,实现类是StandardHost,它继承了ContainerBase,所以调用了startInternal方法

StandardHost

StandardHost:
protected synchronized void startInternal() throws LifecycleException {
        // errorValve默认使用org.apache.catalina.valves.ErrorReportValve
        String errorValve = getErrorReportValveClass();
        
        if ((errorValve != null) && (!errorValve.equals(""))) {
            try {
                boolean found = false;
                 // 如果所有的阀门中已经存在这个实例,则不进行处理,否则添加到  Pipeline 中
                Valve[] valves = getPipeline().getValves();
                for (Valve valve : valves) {
                    if (errorValve.equals(valve.getClass().getName())) {
                        found = true;
                        break;
                    }
                }
			// 如果未找到则添加到 Pipeline 中,注意是添加到 basic valve 的前面
            // 默认情况下,first valve 是 AccessLogValve,basic 是 StandardHostValve
                if(!found) {
                    Valve valve =
                        (Valve) Class.forName(errorValve).getConstructor().newInstance();
                    getPipeline().addValve(valve);
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString(
                        "standardHost.invalidErrorReportValveClass",
                        errorValve), t);
            }
        }
        /*
         *调用的是ContainerBase的startInternal方法,上面分析过了,它调用了子容器的start方法
         * 因为在server.xml中 host标签下没有Context标签,所以Host目前是没有子容器。
         * 那是不是说启动就到此结束了呢?后面继续说
         */
        super.startInternal();
    }

StandardHost Pipeline 包含的 Valve 组件:

  1. basic:org.apache.catalina.core.StandardHostValve
  2. first:org.apache.catalina.valves.AccessLogValve
    这个Value的执行时机,是在接受到请求,并且准备调用Servlet前执行

需要注意的是,在往 Pipeline 中添加 Valve 阀门时,是添加到 first 后面,basic 前面

由上面的代码可知,在 start 的时候,StandardHost 并没有做太多的处理,而且在server.xml中,默认情况host标签下时没有context的,所以目前来说,Host它是没有子容器Context的。那么 StandardHost 又是怎么知道它有哪些 child 容器需要启动呢?

tomcat 在这块的逻辑处理有点特殊,使用 HostConfig 加载子容器,而这个 HostConfig 是一个 LifecycleListener,它会处理 start、stop 事件通知,并且会在线程池中启动、停止 Context 容器,接下来看下 HostConfig 是如何工作的

HostConfig

我们看下HostConfig的lifecycleEvent方法

public void lifecycleEvent(LifecycleEvent event) {

		// (省略若干判断代码)
        // 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();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }
    }
    public void start() {
 		// (省略若干代码)
        if (host.getDeployOnStartup())
            deployApps();

    }
    
    protected void deployApps() {
		
		// 这里获取部署文件夹路径。默认是webapp文件夹路径
        File appBase = host.getAppBaseFile();
        File configBase = host.getConfigBaseFile();
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // Deploy XML descriptors from configBase
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs
        deployWARs(appBase, filteredAppPaths);
        // Deploy expanded folders
        deployDirectories(appBase, filteredAppPaths);
    }

deployWARs:根据war包来部署项目
deployDirectories:根据文件夹来部署项目
这里我们只看deployDirectories

protected void deployDirectories(File appBase, String[] files) {

        if (files == null)
            return;
		// 获取startStopExecutor
        ExecutorService es = host.getStartStopExecutor();
        List<Future<?>> results = new ArrayList<>();
		// 遍历webapp下的文件
        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 
                ContextName cn = new ContextName(files[i], false);
                if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
                    continue;
				// 提交一个DeployDirectory Task到startStopExecutor
                results.add(es.submit(new DeployDirectory(this, cn, dir)));
            }
        }
		// 调用future的get方法,阻塞当前线程,直到部署成功
        for (Future<?> result : results) {
            try {
                result.get();
            } catch (Exception e) {
                log.error(sm.getString(
                        "hostConfig.deployDir.threaded.error"), e);
            }
        }
    }
	
	private static class DeployDirectory implements Runnable {

        private HostConfig config;
        private ContextName cn;
        private File dir;

        public DeployDirectory(HostConfig config, ContextName cn, File dir) {
            this.config = config;
            this.cn = cn;
            this.dir = dir;
        }

        @Override
        public void run() {
        	// 很简单,直接调用了HostConfig.deployDirectory
            config.deployDirectory(cn, dir);
        }
    }

接下来我们看下HostConfig.deployDirectory,是如何真正的进行部署文件夹

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;
        /*
         * 获取当前项目下的META-INF/context.xml
         * 这个我们一般开发的项目一般不存在的
         */
        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 {
        	// 如果项目下存在META-INF/context.xml,并且deployThisXML =true
            if (deployThisXML && xml.exists()) {
                synchronized (digesterLock) {
                    try {
                    	/*
                    	 * 那么这里使用Digester去解析xml,并且生成Context【实现类StandardContext】对象
                    	 */
                        context = (Context) digester.parse(xml);
                    } catch (Exception e) {
                        log.error(sm.getString(
                                "hostConfig.deployDescriptor.error",
                                xml), e);
						// 解析失败 则创建一个FailedContext                           
                        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 {
            	/* 
            	 * 如果项目下不存在META-INF/context.xml,并且deployThisXML = false,
            	 * 则通过反射实例化一个Context【StandardContex】对象
            	 */
                context = (Context) Class.forName(contextClass).getConstructor().newInstance();
            }
			
			/*
			 * host.getConfigClass() = "org.apache.catalina.startup.ContextConfig",
			 * 这里也通过反射实例化了一个ContextConfig对象,而且它是一个LifecycleListener 
			 * 根据HostConfig和Host的关系,就很明显知道这个对象的作用了
			 */
            Class<?> clazz = Class.forName(host.getConfigClass());
            LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
            // 把ContextConfig设置为Context的监听器
            context.addLifecycleListener(listener);
			
			// 设置一些Context的属性
            context.setName(cn.getName());
            context.setPath(cn.getPath());
            context.setWebappVersion(cn.getVersion());
            context.setDocBase(cn.getBaseName());
            /*
             * 把Context设置为Host的子容器,
             * 一旦调用addChild,那么它就会启动子容器。也就是调用Context.start()
             * 感兴趣的,可以去看下源码
             */
            host.addChild(context);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString("hostConfig.deployDir.error",
                    dir.getAbsolutePath()), t);
        } finally {
          // 省略一些信息的设置  
		}

    }

我们对HostConfig.deployDirectory做下简单的总结,如下

  • 判断当前项目下是否存在META/context.xml,存在的话,使用Digester解析context.xml,来生成Context对象
  • 如果存在的该xml,那么通过反射实例化来生成Context对象
  • 实例化ContextConfig,把ContextConfig设置为Context的监听器,ContextConfig的作用跟HostConfig一样,借助XXConfig帮助容器启动
  • 将当前Context设置为Host的子容器,并且启动子容器,也就是调用Context.start()

StandardContext

上面说过调用host.addChild(context);的时候,会启动Context。Context主要是解析项目的web.xml、加载Servlet、Filter等等资源,而且每一个Context对应一个WebappClassLoader,用于资源的隔离【试想一种情况,当一个Tomcat,跑了2个项目,而且2个项目都使用了fastjson这个jar,但是这2个jar的版本不一样,Tomcat如何识别那个jar是对应那个项目的?这里就是用了WebappClassLoader,每一个项目它会对应一个WebappClassLoader,用于加载本项目的jar和class。】,所以Context加载过程比较复杂。

所以首先我们来看下StandardContext这个对象的比较重要的属性,然后在慢慢分析

public class StandardContext extends ContainerBase implements Context, NotificationEmitter {
	protected ApplicationContext context:即ServletContext上下文
	
	private InstanceManager instanceManager:根据 class 实例化对象,比如 Listener、Filter、Servlet 实例对象
	
	private List<Object> applicationEventListenersList:SessionListener、ContextListner 等集合
	
	private HashMap<String, ApplicationFilterConfig> filterConfigs:filer 名字与 FilterConfig 的映射关系
	
	private Loader loader:用于加载class等资源
	
	private final ReadWriteLock loaderLock:用于对loader的读写操作
	
	protected Manager manager:Session管理器
	
	private final ReadWriteLock managerLock:用于对manager的读写操作
	
	private HashMap<String, String> servletMappings:url与Servlet名字的映射关系
	
	private HashMap<Integer, ErrorPage> statusPages:错误码与错误页的映射
	
	private JarScanner jarScanner:用于扫描jar包资源
}

这个我们继续看下它的start方法,如下,为了更容器逻辑,会省略一些比较不重要的代码,只留下比较关键的代码,如果想看更多细节的,可以去看下源码。

protected synchronized void startInternal() throws LifecycleException {

		// 省略一些判断
		/*
		 * 一个标识符,标识某些阶段调用是否成功
		 */
		boolean ok = true;
        /*
         * 1、根据项目名,在Catalina目录下创建一个工作目录
         * 比如$CATALINA_HOME\work\Catalina\localhost\examples
         */
        postWorkDirectory();
		
		/*
		 * 2、创建一个StandardRoot,这类对象主要用于获取项目下的class文件、项目引入的第三方jar等资源
		 */
        setResources(new StandardRoot(this));
		
		/*
		 * 3、启动StandardRoot,也就是调用StandardRoot的start
		 */
		resourcesStart();
		
		/*
		 * 4、创建WebappLoader,上面说过,这个ClassLoader主要用于项目资源隔离
		 * 用于加载本项目的jar和class
		 */
        WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);
	
		/*
		 * 5、创建一个Cookie处理器,这个先不说
		 */
	    cookieProcessor = new LegacyCookieProcessor();



        /*
         * 6、把WebappLoader绑定到当前线程
         * 也就是调用Thread.currentThread().setContextClassLoader()
         */
        ClassLoader oldCCL = bindThread();

        try {
            if (ok) {                
                /*
                 * 7、向监听器发送CONFIGURE_START_EVENT事件,
                 * 这时候ContextConfig就上场了,等等再分析ContextConfig,
                 * ContextConfig主要作用就是解析web.xml,加载Servlet、Filter、Listener的定义等等,
                 * 注意不是加载Servlet、Filter、Listener的对象。
                 */
                fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

                /*
                 * 8、获取Context的子容器也就是Wrapper【Servlet实际上是封装在Wrapper里面】
                 * 并且启动Wrapper
                 */
                for (Container child : findChildren()) {
                    if (!child.getState().isAvailable()) {
                        child.start();
                    }
                }

                /*
                 * 9、创建StandardManager,作用主要是对session的管理
                 */
                Manager contextManager = null;

                contextManager = new StandardManager();
				
				setManager(contextManager);
            }

            if (ok)
                getServletContext().setAttribute
                    (Globals.RESOURCES_ATTR, getResources());

            if (ok ) {
                if (getInstanceManager() == null) {
                    javax.naming.Context context = null;
                    if (isUseNaming() && getNamingContextListener() != null) {
                        context = getNamingContextListener().getEnvContext();
                    }
                    Map<String, Map<String, String>> injectionMap = buildInjectionMap(
                            getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
                    setInstanceManager(new DefaultInstanceManager(context,
                            injectionMap, this, this.getClass().getClassLoader()));
                    getServletContext().setAttribute(
                            InstanceManager.class.getName(), getInstanceManager());
                }
            }
            if (ok) {
                getServletContext().setAttribute(
                        JarScanner.class.getName(), getJarScanner());
            }

           	/*
           	 * 10、为ServletContext设置在web.xml设置的<init-param>的key-value
           	 */
            mergeParameters();

            for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
                initializers.entrySet()) {
                try {
                   /*
                    * 11、调用ServletContainerInitializer的onStartup方法
                    */
                    entry.getKey().onStartup(entry.getValue(),
                            getServletContext());
                } catch (ServletException e) {
                    log.error(sm.getString("standardContext.sciFail"), e);
                    ok = false;
                    break;
                }
            }
            if (ok) {
            	/*
            	 * 12、创建各种Listener,比如ServletContextListener、HttpSessionIdListener等等,
            	 * 而且调用ServletContextListener的contextInitialized方法
            	 */
                if (!listenerStart()) {
                    log.error(sm.getString("standardContext.listenerFail"));
                    ok = false;
                }
            }
            if (ok) {
                checkConstraintsForUncoveredMethods(findConstraints());
            }

            try {
                // Start manager
                Manager manager = getManager();
                if ((manager != null) && (manager instanceof Lifecycle)) {
                    ((Lifecycle) manager).start();
                }
            } catch(Exception e) {
                log.error(sm.getString("standardContext.managerFail"), e);
                ok = false;
            }
            if (ok) {
            	/*
            	 * 13、根据Filter的定义来创建Filter对象
            	 * 并且初始化Filter,也就是调用Filter的init方法,
            	 * 并且把创建的Filter放入filterConfigs
            	 */
                if (!filterStart()) {
                    log.error(sm.getString("standardContext.filterFail"));
                    ok = false;
                }
            }
            if (ok) {
            	/*
            	 * 14、根据在web.xml设置的Servlet的loadOnStartup属性,
            	 * 来依次来创建Servlet,并且调用Servlet的init方法。
            	 * 没有设置loadOnStartup属性的,则是懒加载。            	 
            	 */
                if (!loadOnStartup(findChildren())){
                    log.error(sm.getString("standardContext.servletFail"));
                    ok = false;
                }
            }
			/*
			 * 15、启动有关session管理的线程,这个先不管了
			 */
            super.threadStart();
        } finally {
            // Unbinding thread
            unbindThread(oldCCL);
        }

        // Set available status depending upon startup success
        if (ok) {
            if (log.isDebugEnabled())
                log.debug("Starting completed");
        } else {
            log.error(sm.getString("standardContext.startFailed", getName()));
        }

        startTime=System.currentTimeMillis();

        // Send j2ee.state.running notification
        if (ok && (this.getObjectName() != null)) {
            Notification notification =
                new Notification("j2ee.state.running", this.getObjectName(),
                                 sequenceNumber.getAndIncrement());
            broadcaster.sendNotification(notification);
        }

        // The WebResources implementation caches references to JAR files. On
        // some platforms these references may lock the JAR files. Since web
        // application start is likely to have read from lots of JARs, trigger
        // a clean-up now.
        getResources().gc();

        // Reinitializing if something went wrong
        if (!ok) {
            setState(LifecycleState.FAILED);
        } else {
            setState(LifecycleState.STARTING);
        }
    }

上面关于Context的startInternal的分析,关键代码上都有注释,如果想更深入分析,可以点进去看源码,这里就不讲了,下面讲一下ContextConfig。

ContextConfig

上面的Context的startInternal,会触发一个CONFIGURE_START_EVENT事件,自然ContextConfig就能监听的到

    public void lifecycleEvent(LifecycleEvent event) {

        // Identify the context we are associated with
        try {
            context = (Context) event.getLifecycle();
        } catch (ClassCastException e) {
            log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
            return;
        }

        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();
        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            // Restore docBase for management tools
            if (originalDocBase != null) {
                context.setDocBase(originalDocBase);
            }
        } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
            configureStop();
        } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
            init();
        } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
            destroy();
        }

    }

CONFIGURE_START_EVENT事件的处理方法是configureStart

 protected synchronized void configureStart() {
        // 省略一些日志打印

		/*
		 * 解析web.xml
		 */
        webConfig();

        if (!context.getIgnoreAnnotations()) {
            applicationAnnotationsConfig();
        }
        if (ok) {
            validateSecurityRoles();
        }

        // Configure an authenticator if we need one
        if (ok) {
            authenticatorConfig();
        }

        // Dump the contents of this pipeline if requested
        if (log.isDebugEnabled()) {
            log.debug("Pipeline Configuration:");
            Pipeline pipeline = context.getPipeline();
            Valve valves[] = null;
            if (pipeline != null) {
                valves = pipeline.getValves();
            }
            if (valves != null) {
                for (int i = 0; i < valves.length; i++) {
                    log.debug("  " + valves[i].getClass().getName());
                }
            }
            log.debug("======================");
        }
        // Make our application available if no problems were encountered
        if (ok) {
            context.setConfigured(true);
        } else {
            log.error(sm.getString("contextConfig.unavailable"));
            context.setConfigured(false);
        }

    }

 protected void webConfig() {
       
        Set<WebXml> defaults = new HashSet<>();
        defaults.add(getDefaultWebXmlFragment());
		// 创建一个WebXml对象,主要是用于存储web.xml文件的数据
        WebXml webXml = createWebXml();

        InputSource contextWebXml = getContextWebXmlSource();
        /*
         * 使用webXmlParser对象开始解析web.xml,把web.xml的数据解析到WebXml对象中
         */
        if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
            ok = false;
        }

        ServletContext sContext = context.getServletContext();
		
		/*
		 * 下面都是解析web-fragment.xml,这个是Servlet3.0新特性,暂时先不说了
		 */
        Map<String,WebXml> fragments = processJarsForWebFragments(webXml);

        Set<WebXml> orderedFragments = null;
        orderedFragments =
                WebXml.orderWebFragments(webXml, fragments, sContext);

        // Step 3. Look for ServletContainerInitializer implementations
        if (ok) {
        	/*
        	 * 解析ServletContainerInitializer的实现,
        	 * ServletContainerInitializer使用的SPI机制,
        	 * 需要在META-INF/services/javax.servlet.ServletContainerInitializer
        	 * 然后在文件中写入实现类
        	 */
            processServletContainerInitializers();
        }
		
		/*
		 * 下面解析项目中的注解,
		 * 1、首先从/WEB-INF/classes中加载注解的类
		 * 2、然后在从jar中解析,比如有些jar有web-fragment.xml的,那么也会加载
		 */
        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
        	/*
        	 * 从/WEB-INF/classes中加载注解的类
        	 */
            if (ok) {
                WebResource[] webResources =
                        context.getResources().listResources("/WEB-INF/classes");

                for (WebResource webResource : webResources) {
                    // Skip the META-INF directory from any JARs that have been
                    // expanded in to WEB-INF/classes (sometimes IDEs do this).
                    if ("META-INF".equals(webResource.getName())) {
                        continue;
                    }
                    processAnnotationsWebResource(webResource, webXml,
                            webXml.isMetadataComplete());
                }
            }
            if (ok) {
            	/*
            	 * 然后在从jar中解析含有注解的类,比如有些jar有web-fragment.xml的,那么也会加载
            	 */
                processAnnotations(
                        orderedFragments, webXml.isMetadataComplete());
            }

            // Cache, if used, is no longer required so clear it
            javaClassCache.clear();
        }

        if (!webXml.isMetadataComplete()) {
            // Step 6. Merge web-fragment.xml files into the main web.xml
            // file.
            if (ok) {
                ok = webXml.merge(orderedFragments);
            }

            // Step 7. Apply global defaults
            // Have to merge defaults before JSP conversion since defaults
            // provide JSP servlet definition.
            webXml.merge(defaults);

            // Step 8. Convert explicitly mentioned jsps to servlets
            if (ok) {
                convertJsps(webXml);
            }

            // Step 9. Apply merged web.xml to Context
            if (ok) {
                configureContext(webXml);
            }
        } else {
            webXml.merge(defaults);
            convertJsps(webXml);
            /*
             * 把WebXml的数据,封装到Context对象中,比如Wrapper、Filter、Listener、Servlet的映射、Filter的映射等等,由Context来加载对象,并且初始化,后面会讲一下
             */
            configureContext(webXml);
        }

        // Step 9a. Make the merged web.xml available to other
        // components.
        String mergedWebXml = webXml.toXml();
        @SuppressWarnings("deprecation")
        String attributeName = org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML;
        sContext.setAttribute(attributeName, mergedWebXml);
        if (context.getLogEffectiveWebXml()) {
            log.info("web.xml:\n" + mergedWebXml);
        }

        // Always need to look for static resources
        // Step 10. Look for static resources packaged in JARs
        if (ok) {
            // Spec does not define an order.
            // Use ordered JARs followed by remaining JARs
            Set<WebXml> resourceJars = new LinkedHashSet<>();
            for (WebXml fragment : orderedFragments) {
                resourceJars.add(fragment);
            }
            for (WebXml fragment : fragments.values()) {
                if (!resourceJars.contains(fragment)) {
                    resourceJars.add(fragment);
                }
            }
            processResourceJARs(resourceJars);
        }

        // Step 11. Apply the ServletContainerInitializer config to the
        // context
        if (ok) {
        	/*
             * 把ServletContainerInitializer,设置到Context,由Context来加载,并且调用
             */
            for (Map.Entry<ServletContainerInitializer,
                    Set<Class<?>>> entry :
                        initializerClassMap.entrySet()) {
                if (entry.getValue().isEmpty()) {
                    context.addServletContainerInitializer(
                            entry.getKey(), null);
                } else {
                    context.addServletContainerInitializer(
                            entry.getKey(), entry.getValue());
                }
            }
        }
    }
private void configureContext(WebXml webxml) {
    // 设置 Filter 定义
    for (FilterDef filter : webxml.getFilters().values()) {
        if (filter.getAsyncSupported() == null) {
            filter.setAsyncSupported("false");
        }
        context.addFilterDef(filter);
    }

    // 设置 FilterMapping,即 Filter 的 URL 映射 
    for (FilterMap filterMap : webxml.getFilterMappings()) {
        context.addFilterMap(filterMap);
    }

    // 往 Context 中添加子容器 Wrapper,即 Servlet
    for (ServletDef servlet : webxml.getServlets().values()) {
        Wrapper wrapper = context.createWrapper();
        // 省略若干代码。。。
        wrapper.setOverridable(servlet.isOverridable());
        context.addChild(wrapper);
    }

    // ......
}

这里我们可以知道ContextConfig只是解析web.xml、web-fragment.xml,然后得到Wrapper、Filter、Listenter、ServletContainerInitializer、还有Servlet的映射等等的定义,然后把这些定义设置到Context,由Context进行加载对象,并且进行一些对象初始化

整个Tomcat启动就差不多是这样了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值