tomcat启动浅析 - 969251639/study GitHub Wiki

在启动tomcat时基本上所有的开发人员都知道在tomcat目录中bin目录下执行startup.sh(linux)或startup.bat(windows)这个脚本

...
PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
...
传递一个start参数到catalina脚本中
exec "$PRGDIR"/"$EXECUTABLE" start "$@"  

最终这个脚本最后会执行当前目录下的catalina.sh(linux)或catalina.bat(windows)这个脚本

elif [ "$1" = "start" ] ; then
  检测tomcat是否已经启动
  if [ ! -z "$CATALINA_PID" ]; then
    if [ -f "$CATALINA_PID" ]; then
      if [ -s "$CATALINA_PID" ]; then
        echo "Existing PID file found during start."
        if [ -r "$CATALINA_PID" ]; then
          PID=`cat "$CATALINA_PID"`
          ps -p $PID >/dev/null 2>&1
          if [ $? -eq 0 ] ; then
            echo "Tomcat appears to still be running with PID $PID. Start aborted."
            echo "If the following process is not a Tomcat process, remove the PID file and try again:"
            ps -f -p $PID
            exit 1
          else
            echo "Removing/clearing stale PID file."
            rm -f "$CATALINA_PID" >/dev/null 2>&1
            if [ $? != 0 ]; then
              if [ -w "$CATALINA_PID" ]; then
                cat /dev/null > "$CATALINA_PID"
              else
                echo "Unable to remove or clear stale PID file. Start aborted."
                exit 1
              fi
            fi
          fi
        else
          echo "Unable to read PID file. Start aborted."
          exit 1
        fi
      else
        rm -f "$CATALINA_PID" >/dev/null 2>&1
        if [ $? != 0 ]; then
          if [ ! -w "$CATALINA_PID" ]; then
            echo "Unable to remove or write to empty PID file. Start aborted."
            exit 1
          fi
        fi
      fi
    fi
  fi
  
  shift
  touch "$CATALINA_OUT"  //输出CATALINA日志
  if [ "$1" = "-security" ] ; then
    if [ $have_tty -eq 1 ]; then
      echo "Using Security Manager"
    fi
    shift
    //nohup方式后台启动java进程,并且指定org.apache.catalina.startup.Bootstrap这个类面方法启动,并传递start参数到main方法中
    eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
      -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
      -Djava.security.manager \
      -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
      -Dcatalina.base="\"$CATALINA_BASE\"" \
      -Dcatalina.home="\"$CATALINA_HOME\"" \
      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
      org.apache.catalina.startup.Bootstrap "$@" start \
      >> "$CATALINA_OUT" 2>&1 "&"

  else
    eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
      -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
      -Dcatalina.base="\"$CATALINA_BASE\"" \
      -Dcatalina.home="\"$CATALINA_HOME\"" \
      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
      org.apache.catalina.startup.Bootstrap "$@" start \
      >> "$CATALINA_OUT" 2>&1 "&"

  fi

  if [ ! -z "$CATALINA_PID" ]; then
    echo $! > "$CATALINA_PID"
  fi

  echo "Tomcat started."

所以tomcat的启动入口就在org.apache.catalina.startup.Bootstrap这个类的main方法中,且main方法中的args参数会接收一个start字符串的参数

    public static void main(String args[]) {
        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);
        }

        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);
                //加载server。xml,同事初始化server的生命周期
                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);
        }

    }

上面的面方法主要由两个重点

  1. Bootstrap类的初始化,主要加载了tomcat的类加载器,这个在加载器那一章分析
  2. daemon(即Bootstrap)的load和start

下面分析下Bootstrap类的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;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled())
            log.debug("Calling startup class " + method);
        method.invoke(catalinaDaemon, param);

    }

很简单,用反射迪奥用了catalinaDaemon实例的load方法,而catalinaDaemon又是哪个实例呢?

在上面的第一步的时候,也就是Bootstrap类的初始化的时候就已经创建好了,看下Bootstrap类的init方法

    public void init() throws Exception {
        ....
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();
        ...
        catalinaDaemon = startupInstance;
    }

很明显,catalinaDaemon的实例是org.apache.catalina.startup.Catalina,也就是load方法最终会调用org.apache.catalina.startup.Catalina类的load方法

public void load() {
        ...
        // Create and execute our Digester
        // 创建server.xml解析规则
        Digester digester = createStartDigester();
        ...
            try {
                inputSource.setByteStream(inputStream);
                digester.push(this);//将当前对象压入栈顶,这样在创建Server时就可以调用this,也就时Catalina类的setServer注入Server对象了
                digester.parse(inputSource);//解析xml,注入对象
            } catch (SAXParseException spe) {
                log.warn("Catalina.start using " + getConfigFile() + ": " +
                        spe.getMessage());
                return;
            } catch (Exception e) {
                log.warn("Catalina.start using " + getConfigFile() + ": " , e);
                return;
            }

        // Start the new 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);
            }
        }
        ...
    }

    protected Digester createStartDigester() {
        ...
        // Configure the actions we will be using
        digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");//设置server节点的实例类是org.apache.catalina.core.StandardServer
        digester.addSetProperties("Server");//注入Server节点中配置的属性到Server类中
        digester.addSetNext("Server",
                            "setServer",
                            "org.apache.catalina.Server");//调用栈顶类的setServer,也就是Catalina的setServer方法

        digester.addObjectCreate("Server/GlobalNamingResources",
                                 "org.apache.catalina.deploy.NamingResourcesImpl");//设置GlobalNamingResources节点的实例类是org.apache.catalina.core.NamingResourcesImpl
        digester.addSetProperties("Server/GlobalNamingResources");//注入GlobalNamingResources节点中配置的属性到GlobalNamingResources类中
        digester.addSetNext("Server/GlobalNamingResources",
                            "setGlobalNamingResources",
                            "org.apache.catalina.deploy.NamingResourcesImpl");//调用Server类的setGlobalNamingResources,也就是StandardServer的setGlobalNamingResources方法
       ...
    }
  • 调用createStartDigester方法创建Digester,也就是解析server.xml节点规则
  • 调用Digester.parse解析server.xml
  • 初始化Server(org.apache.catalina.core.StandardServer)

另外启动前Bootstrap还有一段静态代码块会先执行,主要设置了下tomcat的当前路径

总之,load方法最终的调用就是Server.init进行服务器的初始化,初始化完后进入Bootstrap.start方法

    public void start()
        throws Exception {
        if( catalinaDaemon==null ) init();

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

    }

也很类似,用反射调用org.apache.catalina.startup.Catalina的start方法

    public void start() {
        // 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;
        }
    }

而start方法最重要还是调用Server.start方法进行服务器启动工作