Tomcat源码分析:web应用程序的发布

1、容器组件的监听器

已知在tomcatcontext代表一个个应用程序。创建Context等组件时会同时注册对应的监听器,监听器都实现了LifecycleListener接口。在Catalina容器启动过程中会加载server.xml配置文件,并通过Digester完成解析,注意到Catalina.createStartDigester()方法中有这样几行代码:

        digester.addRuleSet(new EngineRuleSet("Server/Service/"));
        digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
        digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));

表示向Digester实例中添加多个规则(RuleSet)HostRuleSet继承了RuleSetBase类,并实现了它的抽像方法:addRuleInstances

HostRuleSet类的addRuleInstances方法中Host节点添加一个org.apache.catalina.startup.HostConfig对象作为org.apache.catalina.core.StandardHost对象的监听器

public void addRuleInstances(Digester digester) {
        digester.addObjectCreate(prefix + "Host",
                                 "org.apache.catalina.core.StandardHost",
                                 "className");
        digester.addSetProperties(prefix + "Host");
        digester.addRule(prefix + "Host",
                         new CopyParentClassLoaderRule());
        digester.addRule(prefix + "Host",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.HostConfig",
                          "hostConfigClass"));
        digester.addSetNext(prefix + "Host",
                            "addChild",
                            "org.apache.catalina.Container");
        //...
      }

类似地,ContextRuleSet中的addRuleInstances方法中,为Engine节点添加EngineConfig对象作为StandardEngine的监听器。  EngineRuleSet中的addRuleInstances方法中,为Context节点添加ContextConfig对象作为StandardContext的监听器。

2、启动过程中web应用程序的发布

从tomcat启动过程可知,当StandardHost启动时会触发Lifecycle. START_EVENT事件,然后通知所有注册在host上的监听器。

从上面的分析我们已经知道HostConfig是StandardHost的监听器。在HostConfig的lifecycleEvent方法中可以看到,Host组件是如何响应Lifecycle. START _EVENT事件的:

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.START_EVENT)) {
            start();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }
    }

如果事件类型是Lifecycle. START _EVENT,执行此监听器(HostConfig对象)start方法()

public void start() {
		if (log.isDebugEnabled())
			log.debug(sm.getString("hostConfig.start"));
		// 为Host注册JMX MBean
		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);
		}
		// 创建appBase目录,Context配置目录(也即conf/Catalina/localhost/)
		if (host.getCreateDirs()) {
			File[] dirs = new File[] { appBase(), configBase() };
			for (int i = 0; i < dirs.length; i++) {
				if (!dirs[i].mkdirs() && !dirs[i].isDirectory()) {
					log.error(sm.getString("hostConfig.createDirs", dirs[i]));
				}
			}
		}
		// 如果Host的appBase目录(默认为wabapps)不存在,则设置deployOnStartup为true
		if (!appBase().isDirectory()) {
			log.error(sm.getString("hostConfig.appBase", host.getName(), appBase().getPath()));
			host.setDeployOnStartup(false);
			host.setAutoDeploy(false);
		}
		// 部署Web应用
		if (host.getDeployOnStartup())
			deployApps();
	}

Start()方法最后调用deployApps()方法发布应用。

protected void deployApps() {
    File appBase = appBase();//conf/[Engine Name]/[Host Name]
        File configBase = configBase();//(默认webapps)
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // Deploy XML descriptors from configBase 部署configBase中的XML描述符
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs 部署appBase下的war工程
        deployWARs(appBase, filteredAppPaths);
        // Deploy expanded folders 部署扩展文件夹
        deployDirectories(appBase, filteredAppPaths);
}

可以看到发布应用的方法有3种,XML文件描述符、WAR包、文件目录(webapp)。这三种方法,都是创建Context对象,将构建好的Context对象与Host组件关联(调用host.addChild(context))。

deployDirectories为例,在部署的过程最后有几行,会新创建的Context对象,并添加一个监听器,这里监听器的名称是configClass变量,其默认值就是ContextConfig。然后设置context的一些属性name,path,version等,最后添加到父容器host中。

            Class clazz = Class.forName(host.getConfigClass());
            LifecycleListener listener =
                (LifecycleListener) clazz.newInstance();
            context.addLifecycleListener(listener);
            context.setName(cn.getName());
            context.setPath(cn.getPath());
            context.setWebappVersion(cn.getVersion());
            context.setDocBase(cn.getBaseName());
            host.addChild(context);

hostaddChild方法

public void addChild(Container child) {
        child.addLifecycleListener(new MemoryLeakTrackingListener());
        if (!(child instanceof Context))
            throw new IllegalArgumentException
                (sm.getString("standardHost.notContext"));
        super.addChild(child);
    }

其中调用父类(ContainerBase)的addChild方法,此方法又调用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);
        }
        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);
        }
    }

从上面方法中可以看到会调用子容器的start方法,就是指调用StandardContextstart方法。也就是说,给host添加子容器context时调用contextstart方法启动子容器。StandardContextstartInternal方法会触发事件CONFIGURE_START_EVENT

// Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

前面已知在创建Context对象时都会注册一个监听器ContextConfig,我们看看这个类的lifecycleEvent方法中监听了哪些事件:

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

如果事件是Lifecycle.CONFIGURE_START_EVENT,则调用configureStart方法,此方法将直接调用webConfig(),web.xml文件的解析就是由这个方法完成的


网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者何欢授权发布。