TOMCAT启动-源码跟踪

《看透SpringMVC源码分析与实践》
Tomcat源码分析—-初始化与启动

tomcat环境搭建

源码下载
由于项目使用的tomcat版本时7.47,从apache svn check代码,svn地址是:
http://svn.apache.org/repos/asf/tomcat/tc7.0.x/tags/TOMCAT_7_0_47/

idea环境搭建
idea搭建tomcat7开发环境,参照基于IntelliJ IDEA 15.0.2的Tomcat7.0.69源码运行环境搭建

  • 将tomcat_7_0_47转换为Maven工程,添加 pom.xml 文件。
  • 在tomcat_7_0_47的源码根目录下,新建 catalina-base目录,作为Tomcat的工作目录
  • 将 conf、logs(新建目录)、webapps、work (新建目录)文件夹,移入 catalina-base目录
  • idea引入import Project –>pom.xml
  • 删除无用的文件、文件夹(非强迫症患者,此步可跳过)。删除bin/,modules/,res/,test/,build.properties等文件,最终的项目结构如下图:
    这里写图片描述
  • 运行org.apache.catalina.startup.Bootstrap.main(),jvm参数为:-Dcatalina.base="你的工程目录\catalina-base",log配置为output to file:你的工程目录\catalina-base\logs
    这里写图片描述

可能会报错:
java.lang.ClassNotFoundException: websocket.drawboard.DrawboardContextListener 只需将/webapps/example 文件夹删除删除即可。


tomcat启动

tomcat的启动入口是org.apache.catalina.startup.Bootstrap.main()

Bootstrap.main()

//org.apache.catalina.startup.Bootstrap
public static void main(String args[]) {
         //创建一个Bootstrap,当bootstrap 初始化完成之后,再赋值给daemon
        if (daemon == null) {
            //try-catch 省略....
            Bootstrap bootstrap = new Bootstrap();
            //调用init()方法,初始化ClassLoader,并用ClassLoader穿件Catalina实例,赋给catalinaDaemon变量。
            bootstrap.init();
            daemon = bootstrap;
        } else {
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
            //默认command为start
            String command = "start";
            if (args.length > 0) {
                //取args最后一个参数,为command
                command = args[args.length - 1];
            }

            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args);
                daemon.start();
            } else if (command.equals("start")) {
                //start比 startd多了一行代码: daemon.setAwait(true);
                daemon.setAwait(true);
                daemon.load(args);
                //调用start方法...
                daemon.start();
            }
            //省略,stopd,stop
            else if (command.equals("configtest")) {
                daemon.load(args);
                if (null==daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            }
        } catch (Throwable t) {
            //省略log....
            System.exit(1);
        }

    }

main方法可传入的参数有
startd、stopd、start、stop,startd和start的区别是设置了一个await的boolean变量为true

main方法内容分为三部分:

  • 首先创建Bootstrap,并执行init()方法初始化,
  • 然后调用load()
  • 最后调用start()方法。

Bootstrap.init()
调用Boostrap的init方法主要完成:

  • 初始化路径:CATALINA_HOME,CATALINA_BASE
  • 初始化类加载器:commonLoader、catalinaLoader、sharedLoader.
  • 初始化Boostrap的Catalina对象:通过反射生成Catalina对象,并通过反射调用setParentClassLoader方法设置其父 ClassLoader为sharedLoader
//org.apache.catalina.startup.Bootstrap
public void init() throws Exception {
       //设置安装目录:CATALINA_HOME
       setCatalinaHome();

       //设置工作目录CATALINA_BASE
        setCatalinaBase();

       //初始化classLoaders
       initClassLoaders();

       //主线程的classLoader设置为catalinaLoader
       Thread.currentThread().setContextClassLoader(catalinaLoader);
      //安全管理的classLoad设置为catalineLoader
       SecurityClassLoad.securityClassLoad(catalinaLoader);

       //省略log....

      //load启动类Catalina
       Class<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
       Object startupInstance = startupClass.newInstance();

       /* 
        * 反射实现:catalina的ParentClassLoader设置为sharedLoader
        * catalina.setParentClassLoader(sharedLoader);
        */
       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;

   }

CATALINA_HOME和CATALINA_BASE区别?


CATALINA_HOME是Tomcat的安装目录,指向公用信息的位置,就是bin和lib的父目录。。
CATALINA_BASE是Tomcat的工作目录指向每个Tomcat目录私有信息的位置,就是conf、logs、temp、webapps和work的父目录。
这里写图片描述
如果我们想要运行Tomcat 的 多个实例,但是不想安装多个Tomcat软件副本。那么我们可以配置多个工作 目录,每个运行实例独占一个工作目录,但是共享同一个安装目录
tomcat多实例配置

Bootstrap.initClassLoaders()
tomcat的classloader通过conf/catalina.properties配置,配置内容为:

package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.

##classloader 相关
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=

//省略....

initClassLoaders()方法逻辑

//org.apache.catalina.startup.Bootstrap
private void initClassLoaders() {
      try {
          //commonLoader 设置父类加载器为null,则为jdk中的
           commonLoader = createClassLoader("common", null);
           if( commonLoader == null ) {
               //若没有配置文件配置,则取SystemClassLoader。
               commonLoader=this.getClass().getClassLoader();
           }

           //catalinaLoader , sharedLoader 设置父类加载器为 commonLoader
           catalinaLoader = createClassLoader("server", commonLoader);
           sharedLoader = createClassLoader("shared", commonLoader);
       } catch (Throwable t) {
           handleThrowable(t);
           log.error("Class loader creation threw exception", t);
           System.exit(1);
       }
   }

这里写图片描述


Bootstrap.load()
Bootstrap.load最终调用的是Catalina的load方法.,主要完成:

  • 设置系统变量
  • 初始化命名系统
  • Digester类,默认将conf/server.xml解析成相应的对象,这个解析很重要,因为是他完成了Connector和Container的初始化工作。
  • Server调用init初始化声明周期,其父类LifecycleMBeanBase实现
//org.apache.catalina.startup.Catalina
private void load() {
    //当前面代码没有初始化CATALINA_BASE和CATALINA_HOME时,重新设置二者值
    initDirs();

    //初始化命名系统,即向System.Properties设置"java.naming.factory.initial"为"org.apache.naming.java.javaURLContextFactory"
    initNaming();

    //创建Digester,主要用于处理xml配置文件,创建Server,初始化Connector和Container
    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    try {
     //获取配置文件:默认值conf/server.xml,可由command参数-config指定
        file = configFile();
        inputStream = new FileInputStream(file);
        inputSource = new InputSource(file.toURI().toURL().toString());
    } //省略log,catch...

   //省略log,try-catch,部分代码.....

    try{
     inputSource.setByteStream(inputStream);
     digester.push(this);
    //解析xml
     digester.parse(inputSource);
    } //省略log,catch...

    getServer().setCatalina(this);

    //System.out , System.error
    initStreams();

    //调用LifecycleBase.init();最终调用自身的initInternal()方法
    getServer().init();
}

Digester解析server.xml创建Server,并初始化Container、Connector之后文章解析。


Boostrap.start()
最终调用的是Catalina.start(),它主要完成:

  • Server加载:Server才是正真的tomcat服务执行者。调用load方法,初始化Connector和Container
  • 调用Server的start方法,最终调用的是StandardServer的startInternal方法,调用自己所有的Service的start方法,启动connector和container、excutor的start
  • 注册钩子
//org.apache.catalina.startup.Catalina
public void start() {
        if (getServer() == null) {
            //Server加载
            load();
        }

        try {
            //服务启动,//调用LifecycleBase.start();最终调用自身的startInternal()方法
            getServer().start();
        } //省略 catch,log.....

        // 注册shutdown钩子
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }

        //进入等待状态
        if (await) {
            //调用Server.await()方法
            await(); 
            stop(); 
        }
    }

StandardServer类图

这里写图片描述

  • Catalina在调用init(),start(),stop()方法时,最终都调用的是LifecycleBase的同名方法,而在LifecycleBase内部又是调用的StandardServer的xxxInternal()方法。
  • Catalina在调用await()方法时,则调用的是,StandardServer的同名方法
//org.apache.catalina.core.StandardServer
protected void initInternal() throws LifecycleException {
    //省略其他部分.....
  // Initialize our defined Services
  for (int i = 0; i < services.length; i++) {
       services[i].init();
   }
}

protected void startInternal() throws LifecycleException {
    //省略其他部分.....
   // Start our defined Services
   synchronized (services) {
       for (int i = 0; i < services.length; i++) {
           services[i].start();
       }
   }
}

public void await() {
    //server.xml中<Server port="8005" shutdown="SHUTDOWN">
    //port默认8005,shutdown默认值为SHUTDOWN

    //如果端口是-2,则不进入方法,直接返回。
     if( port == -2 ) {
         return;
     }

    //如果端口是-1则进入循环,但是无法通过网络命令退出
     if( port==-1 ) {
         try {
             awaitThread = Thread.currentThread();
             while(!stopAwait) {
                Thread.sleep( 10000 );
             }
         } finally {
             awaitThread = null;
         }
         return;
     }

    //如果不是-1,-2(应该是一个大于0的端口),则会新建一个监听关闭命令的serverSocket;
     awaitSocket = new ServerSocket(port, 1,InetAddress.getByName(address));

    awaitThread = Thread.currentThread();
    while (!stopAwait) {
        ServerSocket serverSocket = awaitSocket;
        if (serverSocket == null) {
            break;
        }

        //监听关闭连接
        Socket socket = serverSocket.accept();
        StringBuilder command = new StringBuilder();
        InputStream stream =socket.getInputStream();

        //匹配关闭命令是否等于"SHUTDOWN"
        boolean match = command.toString().equals(shutdown);
        if (match) {
            break;
        } 
    }
 }

StandardService类图
在StandardServerinitInternal(),startInternal()方法,会循环调用Service的start(),init()方法,Service的默认实现是org.apache.catalina.core.StandardService,如下类图
这里写图片描述
最终也会经过LifecycleBase最终调用StandardService的initInternal(),startInternal()方法。

 //org.apache.catalina.core.StandardService
 protected void initInternal() throws LifecycleException {

     super.initInternal();

     //初始化container
     if (container != null) {
         container.init();
     }

     //Executors:Connectors线程池
     for (Executor executor : findExecutors()) {
         if (executor instanceof LifecycleMBeanBase) {
             ((LifecycleMBeanBase) executor).setDomain(getDomain());
         }
         executor.init();
     }

     //初始化Connectors
     synchronized (connectors) {
         for (Connector connector : connectors) {
            connector.init();
         }
     }
 }

protected void startInternal() throws LifecycleException {

    setState(LifecycleState.STARTING);

    //首先启动Container 
    if (container != null) {
        synchronized (container) {
            container.start();
        }
    }

    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    //其次启动Connectors 
    synchronized (connectors) {
        for (Connector connector: connectors) {
            // If it has already failed, don't try and start it
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

其中connectors配置在Server.xml中,默认是备注释掉,未开启。


<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>

    <Connector executor="tomcatThreadPool"
        port="8080" protocol="HTTP/1.1"
        connectionTimeout="20000"
        redirectPort="8443" />
 </Service>
</Server> 

上例表示:Connector配置一个叫做tomcatThreadPool的线程池,最多可同时启用150个线程,至少要有4个可用线程。

这里写图片描述
至此,tomat就启动完成了。

猜你喜欢

转载自blog.csdn.net/it_freshman/article/details/81539009