Tomcat中项目的部署以及其源码分析(一)

摘要:本文介绍在tomcat中部署项目的几种方式以及内部相关的类。

众所周知在tomcat中部署一个项目是一件很轻松的事情,我们总结一下在tomcat中部署项目的四种方式。

  • 第一种方式是我们最常见的,直接把war包丢到webapp目录下即可,或者将war包解压出来的文件夹放到webapp目录下也可以。

  • 第二种方式是修改server.xml文件,在Host标签下新增Context标签,例如下面:

      <Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">
      	<Context path="/test" docBase="G:apache-tomcat-8.0.30myapps"></Context>
      </Host>	
    
  • 第三种方式是在conf/Catalina/localhost中添加xxx.xmlxxx指的是你要访问的路径,例如添加的是test.xml,那么最终的访问路径就是localhost:8080/test/,文件内容是如下:

      <Context path="/test" docBase="G:apache-tomcat-8.0.30myapps"></Context>
    
  • 第四种方式其实已经脱离了原有的默认形式,那就是在server.xml添加一个host,因为在tomcat中默认的host是localhost,所以新加了一个host以后,所有的操作都需要类似之前默认的localhost。在server.xml新加一个自定义host,如下:

      	<Host  name="abc"  appBase="G:apache-tomcat-8.0.30 - test-deploymyapps"  unpackWARs="true" autoDeploy="true"></Host>		
    

    添加了以后需要在hosts文件中设置新添加的host

      127.0.0.1 abc
    

    设置好后,访问路径就是

      http://abc:8080/项目名称/
    

需要注意的是:第三种方式配置的Context标签,如果docBase配置的是相对路径,那么解压出来的war包会放到默认的webapp目录下。如果配置的是绝对路径文件夹,那么该文件夹里面的内容是war包解压出来的文件夹,如果是配置的绝对路径war包,那么该war包会默认解压到webapp目录下,解压出来的名称同path名称(例如上面path配置的是/test,如果docBase配置绝对路径war包,那么最后会把这个war包解压到webapp目录下,文件夹名字叫test)。

好了,外部配置的方式我们都搞清楚了,现在我们要去查看下tomcat在内部是如何实现发布项目的,下面分享下我第一次在不查询任何资料的时候是如何查到相关类的相关方法的:

1、我的想法:在tomcat内部存在很多默认的应用,比如ROOTexample等,tomcat肯定要发布这些项目,所以我先启动tomcat,查看日志,找到一些关键字如下:

Deploying web application directory F:coooooooooooooooooooolTomcatSourcewebappsdocs

搜索关键字Deploying web application directory,结果如下:

看来日志是tomcat统一写到properties文件中的,再继续根据key来查找,随便找个hostConfig.deploy,全局搜索如下:

可以看出来 代码中没有任何引用,再查另外一个keyhostConfig.deployDir,结果如下:

因为符合条件的就一个,我们点进去,找到的是一个HostConfig类的deployDirectory(ContextName cn, File dir)方法,我们继续查找这个方法的引用,过程我就不一一赘述,最后查处的结果指向的是lifecycleEvent(LifecycleEvent event),如下:

  /**
 * Process the START event for an associated Host.
 *
 * @param event The lifecycle event that has occurred
 */
@Override
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)) {
		//11111
        check();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
    } else if (event.getType().equals(Lifecycle.START_EVENT)) {
		//22222222
        start();
    } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
        stop();
    }
}

可以查询到,最后指向的分别是check()start()方法,我们可以得出一个大概的结论,发布项目是在tomcat启动的时候进行的,是触发了某个组件的生命周期事件来完成的。那么具体是哪个类的生命周期事件呢?其实看方法注释就能看出来了,associated Host,那么是跟Host有关的生命周期事件,也就是StandardHost。我们查看下StandardHoststart()方法。

   @Override
protected synchronized void startInternal() throws LifecycleException {

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

可以看到标注1到标注2的地方都在给StandardHost安装Valve,标注2的地方调用了父类的startInternal(),也就是ContainerBasestartInternal()方法如下,因为我们在ContainerBase类的讲解中讲过,这里删减了大部分代码,如下:

   @Override
protected synchronized void startInternal() throws LifecycleException {

  
    // Start our child containers, if any
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<Future<Void>>();
    for (int i = 0; i < children.length; i++) {
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }

    boolean fail = false;
    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();

	//111111111
    setState(LifecycleState.STARTING);

    // Start our thread
    threadStart();

}

这个startInternal()方法,我们在ContainerBase文章中没有提到标注1处的方法,我们查看下源码:

//此处代码 来自LifecycleBase

protected synchronized void setState(LifecycleState state)
        throws LifecycleException {
    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;
    String lifecycleEvent = state.getLifecycleEvent();
	//111111
    if (lifecycleEvent != null) {
        fireLifecycleEvent(lifecycleEvent, data);
    }
}

protected void fireLifecycleEvent(String type, Object data) {
    lifecycle.fireLifecycleEvent(type, data);
}

 public void fireLifecycleEvent(String type, Object data) {

    LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
    LifecycleListener interested[] = listeners;
    for (int i = 0; i < interested.length; i++)
        interested[i].lifecycleEvent(event);

}

点进去才发现,原来调用的是LifecycleBase类中的方法,这个我们在之前的文章中也讲解过,查看标注1的地方可以看到,就是在这里触发了生命周期的方法,对StandardHost的所有listener触发一下start事件。

那么StandardHostlistenerHostConfig类是如何连接起来的呢。思来想去看看,先找找看构造函数,set方法都没有找到相关的设置。那有可能是server.xml中解析出来的配置,我们就需要去查看下Catalina类里面的load()方法中的Digester digester = createStartDigester();方法了。

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

createStartDigester()方法中,找到了配置Host标签的方法,查看HostRuleSet相关方法如下:

 @Override
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());
	//1111
    digester.addRule(prefix + "Host",
                     new LifecycleListenerRule
                     ("org.apache.catalina.startup.HostConfig",
                      "hostConfigClass"));
    digester.addSetNext(prefix + "Host",
                        "addChild",
                        "org.apache.catalina.Container");

    digester.addCallMethod(prefix + "Host/Alias",
                           "addAlias", 0);

    //Cluster configuration start
    digester.addObjectCreate(prefix + "Host/Cluster",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Cluster");
    digester.addSetNext(prefix + "Host/Cluster",
                        "setCluster",
                        "org.apache.catalina.Cluster");
    //Cluster configuration end

    digester.addObjectCreate(prefix + "Host/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Listener");
    digester.addSetNext(prefix + "Host/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));

    digester.addObjectCreate(prefix + "Host/Valve",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Valve");
    digester.addSetNext(prefix + "Host/Valve",
                        "addValve",
                        "org.apache.catalina.Valve");

}

可以看到在标注1的地方就提到了HostConfig类,查看其内部源码(begin()方法):

  @Override
public void begin(String namespace, String name, Attributes attributes)
    throws Exception {

    Container c = (Container) digester.peek();
    Container p = null;
    Object obj = digester.peek(1);
    if (obj instanceof Container) {
        p = (Container) obj;
    }

    String className = null;
    
    // Check the container for the specified attribute
    if (attributeName != null) {
        String value = attributes.getValue(attributeName);
        if (value != null)
            className = value;
    }

    // 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;
        }
    }
    
    // Use the default
    if (className == null) {
        className = listenerClass;
    }
    //11111
    // Instantiate a new LifecyleListener implementation object
    Class<?> clazz = Class.forName(className);
    LifecycleListener listener =
        (LifecycleListener) clazz.newInstance();
	//22222222
    // Add this LifecycleListener to our associated component
    c.addLifecycleListener(listener);
}

可以很清晰的看到在标注1的地方新建了一个实例(也就是HostConfig实例),然后将这个实例添加到了listener数组里了,既然这样我们推测HostConfig肯定要实现LifecycleListener,再回去查看下HostConfig的类定义:

public class HostConfig implements LifecycleListener

果然是这样,这样整个流程就比较清晰了,我们梳理下。

  1. Catalina启动的时候,解析server.xml的时候会给StandardHost添加一个starting事件的监听器,也就是一个HostConfig实例。
  2. StanardHost启动的时候(调用start()),触发该监听器开始deploy项目。

所以我们只需要查看HostConfig类的相关方法即可知道tomcat是如何发布项目的。

原文地址:https://www.cnblogs.com/coldridgeValley/p/6096154.html