Tomcat源码分析(一):启动流程

Tomcat源码分析(一):启动流程

在看Tomcat启动流程之前,首先了解下Tomcat的架构,类加载父子关系以及容器的生命周期

Tomcat架构

在Tomcat中有Server的概念,一个Tomcat运行实例只会包含一个Server实例
Server实例的作用是用来管理多个Service,那么Service又是干嘛用的呢?
从下面的图可以看出,一个Service实例包含多个Connector实例和一个Container实例,Service是用来管理Connector和Container之间的关联关系
Connector负责处理网络连接,Container负责处理请求并且生成响应
在这里插入图片描述
每个Connector一般用来处理不同种类的连接
在这里插入图片描述
一共有4种容器,Engine,Host,Context,Wrapper,,它们都实现了Container接口
父子关系如下图所示,每种容器的关注点不同
在这里插入图片描述

类加载器父子关系

在这里插入图片描述
在Tomcat中,除了使用常见的启动类加载器,扩展类加载和应用类加载器之外,另外使用了CommonClassLoader,CatalinaClassLoader和SharedClassLoader以及WebappClassLoader

容器的生命周期

在这里插入图片描述

Bootstrap.main

public static void main(String args[]) {

	// 初始化bootstrap
	synchronized (daemonLock) {
	    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);
	    }
	}
	
	// 根据入参执行bootstrap的不同方法
	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);
	}
	}

这里做的主要有两件事:

  1. 创建Bootstrap对象,然后调用Init进行初始化
  2. 根据入参,执行bootstrap对象的不同方法

Bootstrap的初始化

重点看Bootstrap.init做了什么操作

public void init() throws Exception {

	// 初始化三个classloader commonLoader catalinaLoader sharedLoader
    initClassLoaders();

	// 使用catalinaLoader作为当前的类加载器
    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // Load our startup class and call its process() method
    if (log.isDebugEnabled())
        log.debug("Loading startup class");
    // 使用catalinaloader来加载catalina
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();

    // Set the shared extensions class loader
    if (log.isDebugEnabled())
        log.debug("Setting startup class properties");
    // 下面的代码就是调用catalina的setParentClassLoader方法,将sharedLoader设置cataLinaLoader的父加载器
    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;
}
initClassLoaders
private void initClassLoaders() {
    try {
        // 创建三个类加载器
        // 从这里可以看出, commonLoader的父加载器是系统类加载器
        commonLoader = createClassLoader("common", null);
        if (commonLoader == null) {
            // no config file, default to this loader - we might be in a 'single' env.
            commonLoader = this.getClass().getClassLoader();
        }
        // catalinaLoader的父加载器是commonLoader
        catalinaLoader = createClassLoader("server", commonLoader);
        // sharedLoader的父加载器是sharedLoader
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
     throws Exception {

	 // 根据系统配置来获取当前类加载器负责加载的目录,并且创建类加载器
	 // 这里的值可能为common.loader,server.loader,shared.loader
	 // 通过debug,可以知道server.loader和shared.loader的值都为空,因此会使用父classloader
	 // 即如果没有配置common.loader和server.loader,commonLoader,catalinaLoadewr,sharedLoader都是同一个classLoader
	 // 这里配置信息的来源有三种可能可能:系统变量catalina.config指定的文件,catalinaBase路径(一般就是tomcat的源码根目录)下的/resource/conf/catalina.properties文件,/org/apache/catalina/startup/catalina.properties
	 // 这三个配置文件的生效顺序就是上面的顺序,当前一个配置文件存在,就不会读取后面一个配置文件
	 // common.loder的值为 ${tomcat根目录}/resource/lib ${tomcat根目录}/resource/lib/*.jar
     String value = CatalinaProperties.getProperty(name + ".loader");
     if ((value == null) || (value.equals("")))
         return parent;
	 // 进行变量替换
     value = replace(value);
	 
     List<Repository> repositories = new ArrayList<>();
	 // 使用,来分割上面的value
     String[] repositoryPaths = getPaths(value);
     if (!Objects.isNull(repositoryPaths) && repositoryPaths.length > 0) {
         log.info("classLoaderName:" + name + ", repositoryPaths:{}" + StringUtils.join(Arrays.asList(repositoryPaths), ','));
     } else {
         log.info("classLoaderName:" + name + ", repositoryPaths is null");
     }

     for (String repository : repositoryPaths) {
         // Check for a JAR URL repository
         try {
             @SuppressWarnings("unused")
             URL url = new URL(repository);
             repositories.add(new Repository(repository, RepositoryType.URL));
             continue;
         } catch (MalformedURLException e) {
             // Ignore
         }

         // Local repository
         // 根据路径的后缀名的不同创建不同种类的仓库对象
         if (repository.endsWith("*.jar")) {
             repository = repository.substring
                 (0, repository.length() - "*.jar".length());
             repositories.add(new Repository(repository, RepositoryType.GLOB));
         } else if (repository.endsWith(".jar")) {
             repositories.add(new Repository(repository, RepositoryType.JAR));
         } else {
             repositories.add(new Repository(repository, RepositoryType.DIR));
         }
     }

	 // 将当前需要加载的仓库的地址传给ClassLoaderFactory,由其创建ClassLoader
     return ClassLoaderFactory.createClassLoader(repositories, parent);
 }

对指令进行响应

这里主要看指令为start时的执行逻辑

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

Bootstrap.load 会调用Catalina的带参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;
    }
    // 这里的catalinaDaemon就是Catalina的的一个实例
    // 调用catalina的load方法
    Method method =
        catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    if (log.isDebugEnabled()) {
        log.debug("Calling startup class " + method);
    }
    method.invoke(catalinaDaemon, param);
}

Catalina.load的带参版本,首先会检验参数是否合法,如果合法会继续执行不带参版本,如果不合法会打印使用文档

public void load(String args[]) {

    try {
   		// 检验参数
        if (arguments(args)) {
            load();
        }
    } catch (Exception e) {
        e.printStackTrace(System.out);
    }
}

这里的load方法主要完成的工作如下:

  1. 创建Digester对象,向其中添加conf/server.xml文件的解析规则,解析conf/server.xml文件
  2. 调用server对象的init方法,开始初始化所有组件
解析server.xml文件
public void load() {
    if (loaded) {
        return;
    }
    loaded = true;

    long t1 = System.nanoTime();

    initDirs();

    // Before digester - it may be needed
    initNaming();
	
	// 创建Digester对象,开始解析conf/server.xml文件
    // Create and execute our Digester
    // 创建一个digester对象,并且向其中添加解析规则
    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    try {
        try {
            file = configFile();
            inputStream = new FileInputStream(file);
            inputSource = new InputSource(file.toURI().toURL().toString());
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("catalina.configFail", file), e);
            }
        }
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                    .getResourceAsStream(getConfigFile());
                inputSource = new InputSource
                    (getClass().getClassLoader()
                        .getResource(getConfigFile()).toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail",
                        getConfigFile()), e);
                }
            }
        }

        // This should be included in catalina.jar
        // Alternative: don't bother with xml, just create it manually.
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                    .getResourceAsStream("server-embed.xml");
                inputSource = new InputSource
                    (getClass().getClassLoader()
                        .getResource("server-embed.xml").toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail",
                        "server-embed.xml"), e);
                }
            }
        }


        if (inputStream == null || inputSource == null) {
            if  (file == null) {
                log.warn(sm.getString("catalina.configFail",
                    getConfigFile() + "] or [server-embed.xml]"));
            } else {
                log.warn(sm.getString("catalina.configFail",
                    file.getAbsolutePath()));
                if (file.exists() && !file.canRead()) {
                    log.warn("Permissions incorrect, read permission is not allowed on the file.");
                }
            }
            return;
        }

        try {
            inputSource.setByteStream(inputStream);
            // 将catalina对象压入到digester的解析栈中,成为栈顶
            digester.push(this);
            // 开始解析conf/server.xml文件
            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和catalina之间建立关联
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // Stream redirection
    initStreams();

    // Start the new server
    // 初始化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");
    }
}

这里简单地看下createStartDigester是怎么添加解析规则的
createStartDigester会反复地调用digester对象的addObjectCreate,addSetProperties,addSetNext等方法,来添加解析规则
这里的Digester类是在apache Digester类上做了一层封装

// Configure the actions we will be using
// 当遇到server元素时,创将一个StandardServer的实例,如果元素定义了className属性,那么就会使用className对应的类来实例化一个Serve
digester.addObjectCreate("Server",
                         "org.apache.catalina.core.StandardServer",
                         "className");
// 使用Server标签中的属性来设置栈顶元素,也就是Server对象
digester.addSetProperties("Server");
// 将栈顶元素作为参数,传给栈顶元素的下一个元素的指定方法作为入参
// 此时的栈顶元素是Catalina对象
// 即将刚刚解析好的server对象设置到catelian对象中
digester.addSetNext("Server",
                    "setServer",
                    "org.apache.catalina.Server");
Server初始化

这里使用的Server的实现类是StandardServer,StandardServer的init的主要执行逻辑在initInternal里面,下面看下initInternal
StandardServer的初始化主要是触发包含的Service的初始化

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // 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();
        }
    }
    // Initialize our defined Services
    // 启动当前server管理的service
    for (Service service : services) {
        service.init();
    }
}
Service初始化

下面再看下StandardService的initInternal方法
主要是初始化管理的Engine和connector

protected void initInternal() throws LifecycleException {
	
    super.initInternal();

	// 初始化Engine
    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) {
            try {
                connector.init();
            } catch (Exception e) {
                String message = sm.getString(
                        "standardService.connector.initFailed", connector);
                log.error(message, e);

                if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                    throw new LifecycleException(message);
            }
        }
    }
}
Engine初始化

下面再看StandardEngine的initInternal
StandardEngine主要是设置一个Realm,Realm是用来做身份校验的

@Override
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();
}
start

当执行完Bootstrap的start方法后,接着会执行start方法,主要是调用了Catalina的start方法

public void start() throws Exception {
	// 判断catalina是否初始化过,如果没有初始化,那么开始初始化
    if (catalinaDaemon == null) {
        init();
    }

	// 调用Catalina的start方法
    Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
    method.invoke(catalinaDaemon, (Object [])null);
}
Catalina的启动

下面看下Catalina的start方法
主要是调用了Server的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();
            stop();
        }
    }
Server的启动

下面看下StandardServer的startInternal
主要是启动管来的service

protected void startInternal() throws LifecycleException {

	// 发送configure_start事件
   fireLifecycleEvent(CONFIGURE_START_EVENT, null);
   // 设置service的生命周期状态为STARTING
   setState(LifecycleState.STARTING);

   globalNamingResources.start();

   // Start our defined Services
   // 启动所有的service
   synchronized (servicesLock) {
       for (Service service : services) {
           service.start();
       }
   }
}
Service的启动

下面看下StandardService的启动
主要是启动了StandardEngine

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) {
            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的启动

下面看下StandardEngine的startInternal

@Override
protected synchronized void startInternal() throws LifecycleException {

	// Log our server identification information
	if(log.isInfoEnabled())
	    log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
	
	// Standard container startup
	super.startInternal();
}

可以看到只是简单地打了个日志,然后就会执行父类ContainerBase的startInternal方法,主要做了如下几件事儿:

  1. 启动所有的子容器,将启动任务提交到线程池中执行,这里的子容器是StandardHost
  2. 启动一个后台线程,主要用来执行一个需要在后台执行的任务,比如定期检查是否需要代码热替换等
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
    // 这里是关键,获取当前容器的所有子容器,并且将子容器的启动任务提交到线程池中执行
    // 通过debug,这里只有一个StandardHost
    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
    // 启动pipeline
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }

	// 将当前的状态修改为正在启动
    setState(LifecycleState.STARTING);

    // Start our thread
    // 启动一个后台线程,这个线程会执行一些需要后台任务,比如周期性地检查代码是否需要热替换等
    threadStart();
}
protected void threadStart() {

	// 避免重复创建后台线程
    if (thread != null)
        return;
    // 当属性为正值时,会启动后台线程
    // 一些容器的backgroundProcessorDelay为-1,因此不会启动后台线程
    if (backgroundProcessorDelay <= 0)
        return;

    threadDone = false;
    String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
    log.info("threadName:" + threadName);
    thread = new Thread(new ContainerBackgroundProcessor(), threadName);
    thread.setDaemon(true);
    thread.start();

}
Host的启动

下面看下StandardHost的startInternal

protected synchronized void startInternal() throws LifecycleException {

   // Set error report valve
   // 获取错误报告的阀
   String errorValve = getErrorReportValveClass();
   // 判断是否需要在pipeline中添加这个阀
   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);
       }
   }
   // 仍然会调用ContainerBase的startInternal,但是此时的子容器个数为0,并且当前的backgroundProcessorDelay为-1,因此不会启动后台线程
   super.startInternal();
}
应用程序的部署

至此,Server,Service,Connector,Engine,Host已经启动了,那么代表我们应用程序的Context和代表Servlet的Wrapper在哪里启动呢?
我们回到之前使用Digester解析server.xml的地方,看看那里是怎么解析Host标签的

digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));```
接着看下HostRuleSet的addRuleInstances方法中的这一部分
```java
// 当digester对象开始解析<Host>时,会调用LifecycleListenerRule的begin方法
digester.addRule(prefix + "Host",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.HostConfig",
                          "hostConfigClass"));

下面看下LifecycleListenerRule的begin方法

public void begin(String namespace, String name, Attributes attributes)
        throws Exception {
	// 获取栈顶元素,此时获取到的就是StandardHost对象
    Container c = (Container) digester.peek();
    Container p = null;
    // 获取此栈顶元素,此时获取到的是StandardEngine对象
    Object obj = digester.peek(1);
    if (obj instanceof Container) {
        p = (Container) obj;
    }

    String className = null;

    // Check the container for the specified attribute
    // 判断Host标签是否指定了hostConfigClass属性
    if (attributeName != null) {
        String value = attributes.getValue(attributeName);
        if (value != null)
            className = value;
    }

	// 判断上一级容器,StandardEngine是否指定了hostConfigClass属性
    // Check the container's parent for the specified attribute
    if (p != null && className == null) {
        String configClass =
            (String) IntrospectionUtils.getProperty(p, attributeName);
        if (configClass != null && configClass.length() > 0) {
            className = configClass;
        }
    }

	// 如果都没有指定,那么使用默认的值HostConfig
    // Use the default
    if (className == null) {
        className = listenerClass;
    }

    // Instantiate a new LifecycleListener implementation object
    // 创建一个HostConfig的实例,然后添加到StandardHost的生命周期监听器中
    Class<?> clazz = Class.forName(className);
    LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();

    // Add this LifecycleListener to our associated component
    c.addLifecycleListener(listener);
}

我们上面看到过,在tomcat启动的时候会调用StandardHost的start方法,实际调用的是LifecycleBase的start方法,在该方法中会调用startInternal,而这个方法会被ContainerBase来实现,在ContainerBase的setState方法中,会将当前容器的生命周期设置为STARTING,并且会触发START_EVENT事件
HostConfig本质上也是一个Host的生命周期监听器,因此会监听到这个事件,并且调用自身的start方法

public void lifecycleEvent(LifecycleEvent event) {

    // Identify the host we are associated with
    try {
        host = (Host) event.getLifecycle();
        if (host instanceof StandardHost) {
            setCopyXML(((StandardHost) host).isCopyXML());
            setDeployXML(((StandardHost) host).isDeployXML());
            setUnpackWARs(((StandardHost) host).isUnpackWARs());
            setContextClass(((StandardHost) host).getContextClass());
        }
    } catch (ClassCastException e) {
        log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
        return;
    }

    // Process the event that has occurred
    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 (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.warn(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();

}
protected void deployApps() {

    // 部署指定路径下的webapp
    // 默认情况下是${tomcat目录}/resource/webapps
    File appBase = host.getAppBaseFile();
    // 默认情况下是${tomcat目录}/resoure/conf/Catalina/localhost
    File configBase = host.getConfigBaseFile();
    // 会识别出appBase下需要部署的应用程序
    // 默认情况下会识别出docs,manager,examples,ROOT和host-manager这几个应用
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // 部署应用程序一共有三种方式:xml描述符,WAR和文件夹
    // Deploy XML descriptors from configBase
    deployDescriptors(configBase, configBase.list());
    // Deploy WARs
    // 部署后缀名为.war的文件
    deployWARs(appBase, filteredAppPaths);
    // Deploy expanded folders
    // 部署文件夹形式的应用程序
    deployDirectories(appBase, filteredAppPaths);

}

这里简单地看下deployDirectories

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

    if (files == null)
        return;

    ExecutorService es = host.getStartStopExecutor();
    List<Future<?>> results = new ArrayList<>();

	// 遍历目录下的文件
    for (String file : files) {

        if (file.equalsIgnoreCase("META-INF"))
            continue;
        if (file.equalsIgnoreCase("WEB-INF"))
            continue;
        File dir = new File(appBase, file);
        if (dir.isDirectory()) {
            ContextName cn = new ContextName(file, false);

            if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
                continue;
			// 将每个应用的部署封装成一个DeployDirectory任务,然后使用线程池来部署
            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);
        }
    }
}

下面看下DeployDirectory具体如何部署应用的

private static class DeployDirectory implements Runnable {

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

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

    @Override
    public void run() {
        config.deployDirectory(cn, dir);
    }
}

可以看到实际上是通过HostConfig的deployDirecotry,那么接着看下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);
   // ${tomcat目录}/resource/conf/Catalina/localhost/docs.xml
   // configBaseFile代表当前host的配置文件
   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 {
               	   // 解析context.xml文件
               	   // 这里多个解析任务公用同一个digester,并且解析的过程中会修改digester,比如在栈中压入解析后的结果,因此需要上锁
               	   // 并且在解析完毕之后,重置digester
                   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();
       }

	   // 这里创建一个ContextConfig,是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());
       // 将解析后的context作为host的子容器
       host.addChild(context);
   } catch (Throwable t) {
       ExceptionUtils.handleThrowable(t);
       log.error(sm.getString("hostConfig.deployDir.error",
               dir.getAbsolutePath()), t);
   } finally {
       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()) {
           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)));
   }
}

StandardHost在调用addChild的时候,最终会调用到ContainerBase的addChildInternal方法
在该方法中会启动子容器

private void addChildInternal(Container child) {

    if( log.isDebugEnabled() )
        log.debug("Add child " + child + " " + this);
    synchronized(children) {
        if (children.get(child.getName()) != null)
            throw new IllegalArgumentException("addChild:  Child name '" +
                                               child.getName() +
                                               "' is not unique");
        child.setParent(this);  // May throw IAE
        children.put(child.getName(), child);
    }

    // Start child
    // Don't do this inside sync block - start can be a slow process and
    // locking the children object can cause problems elsewhere
    try {
        if ((getState().isAvailable() ||
                LifecycleState.STARTING_PREP.equals(getState())) &&
                startChildren) {
            child.start();
        }
    } catch (LifecycleException e) {
        log.error("ContainerBase.addChild: start: ", e);
        throw new IllegalStateException("ContainerBase.addChild: start: " + e);
    } finally {
        fireContainerEvent(ADD_CHILD_EVENT, child);
    }
}

最终会调用LifecycleBase的start
在执行start的时候,首先会判断当前的状态是否可以执行start操作
此时Context的状态为new,不能直接执行start操作,所以会首先执行init,然后执行start

public final synchronized void start() throws LifecycleException {

    if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
            LifecycleState.STARTED.equals(state)) {

        if (log.isDebugEnabled()) {
            Exception e = new LifecycleException();
            log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
        } else if (log.isInfoEnabled()) {
            log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
        }

        return;
    }

    // 首先执行初始化
    if (state.equals(LifecycleState.NEW)) {
        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());
    }
}

接着会调用StandardContextd的startInternal方法,下篇分析Context具体如何部署应用的时候再分析

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值