tomcat源码分析如何启动服务

Posted 歪头儿在帝都

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tomcat源码分析如何启动服务相关的知识,希望对你有一定的参考价值。

从startup.sh入手

os400=false
case "`uname`" in
OS400*) os400=true;;
esac

PRG="$0"

while [ -h "$PRG" ] ; do
  ls=`ls -ld "$PRG"`
  link=`expr "$ls" : \'.*-> \\(.*\\)$\'`
  if expr "$link" : \'/.*\' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi
done

PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh


if $os400; then
  eval
else
  if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
    echo "Cannot find $PRGDIR/$EXECUTABLE"
    echo "The file is absent or does not have execute permission"
    echo "This file is needed to run this program"
    exit 1
  fi
fi

exec "$PRGDIR"/"$EXECUTABLE" start "$@" 

整个脚本核心就是最后一句代码, EXECUTABLE变量是catalina.sh, 代表执行catalina.sh, 参数是start, 再去对比了shutdown.sh, 两个脚本的核心都是调用catalina.sh传递的变量不同。

浏览catalina.sh脚本

整个脚本很长,我这里之截图了我们关心的脚本内容。 这段代码里, 除了能看到参数传递start, 最后会输出Tomcat started外,能看到调用了org.apache.catalina.startup.Bootstrap, 也就是说找到我们的程序入口,或者说找到了我们的程序的main函数。

    shift
    eval $_NOHUP "\\"$_RUNJAVA\\"" "\\"$CATALINA_LOGGING_CONFIG\\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \\
      -D$ENDORSED_PROP="\\"$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\\"" "\\"$CATALINA_LOGGING_CONFIG\\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \\
      -D$ENDORSED_PROP="\\"$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本质上也是一个java程序,因此startup.sh会启动一个jvm来运行tomcat的启动类Bootstrap.java。

Bootstrap类核心功能

  • 静态构造器部分, 主要初始化了CATALINA_HOME和CATALINA_BASE两个变量内容
  • main函数方法部分一,创建和初始化daemon, 创建三个类加载器
  • main函数方法部分二,控制tomcat的启动和停止

从Bootstrap.main方法开始

开始main方法之前,首先看两个关键属性

/*************
守护进程对象
**********/
private static volatile Bootstrap daemon = null;

/***
守护程序用的catalina对象
***/
private Object catalinaDaemon = null;

Bootstrap#main

 public static void main(String args[]) 
	synchronized (daemonLock) 
		if (daemon == null) 
			//初始化完成之前,不要对daemon赋值
			Bootstrap bootstrap = new Bootstrap();
			try 
			    //调用初始化方法, 完成加载器的配置和初始化器的准备
				bootstrap.init();
			 catch (Throwable t) 
				handleThrowable(t);
				t.printStackTrace();
				return;
			
			daemon = bootstrap;
		 else 
			//当作为服务正在运行时,如果调用停止方法,这将在一个新线程上进行,以确保使用正确的类加载器,防止出现未找到类的异常
			Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
		
	

	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);
        //Bootstrap加载
		daemon.load(args);
        //Bootstrap启动
		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 
		
	      

public void init() throws Exception 
    //初始化类的三个加载器
	initClassLoaders();
	//设置线程类加载器, 将容器的加载器传入
	Thread.currentThread().setContextClassLoader(catalinaLoader);
	//加载安全类加载器
	SecurityClassLoad.securityClassLoad(catalinaLoader);
	//通过反射加载catalina
	Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
	//创建对象
	Object startupInstance = startupClass.getConstructor().newInstance();

	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); //对类加载器进行初始化赋值
	//调用catalina类内部的setParentClassLoader方法对catalina类内部的类加载赋值
	method.invoke(startupInstance, paramValues);
	//将创建好的startupInstance对象赋值给catalinaDaemon
	catalinaDaemon = startupInstance;

Catalina#load

Catalina类的load方法核心就解析config/server.xml并创建Server组件实例, 也就是我们在tomcat整体架构章节里了解的一个tomcat只有一个Server实例。 这部分代码块,我删掉了注释代码,try...catch, 只留下了核心业务代码。

public void load() 
	loaded = true;
	long t1 = System.nanoTime();
	initDirs();
	initNaming();
    //利用digester类解析server.xml,得到容器的配置
	Digester digester = createStartDigester();

	InputSource inputSource = null;
	InputStream inputStream = null;
	File file = null;
	
	file = configFile();
	inputStream = new FileInputStream(file);
	inputSource = new InputSource(file.toURI().toURL().toString());
	
	if (inputStream == null) 
		inputStream = getClass().getClassLoader().getResourceAsStream(getConfigFile());
		inputSource = new InputSource(getClass().getClassLoader().getResource(getConfigFile()).toString());	
	

	if (inputStream == null) 
		inputStream = getClass().getClassLoader().getResourceAsStream("server-embed.xml");
		inputSource = new InputSource(getClass().getClassLoader().getResource("server-embed.xml").toString());
	

	if (inputStream == null || inputSource == null) 
		return;
	

	try 
		inputSource.setByteStream(inputStream);
		digester.push(this);
		digester.parse(inputSource);
	 catch (SAXParseException spe) 
		return;
	 catch (Exception e) 
		return;
	
	
	getServer().setCatalina(this);
	getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
	getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

	initStreams();
    //服务器执行初始化 开始调用的Server的初始化方法注意Server是一个接口
	getServer().init();

Catalina#start

public void start() 
	if (getServer() == null) 
		load();
	

	if (getServer() == null) 
		return;
	

	long t1 = System.nanoTime();

	//开始一个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");
	

	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) 
		await();
		stop();
	

从Bootstrap#createStartDigester方法中可以看到Server接口的实现类是StandardServer

digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");

Server接口继承了Lifecycle接口
StandardServer类继承了抽象类LifecycleMBeanBase,同时实现了Server接口
LifecycleMBeanBase抽象类又继承了抽象类LifecycleBase, 而LifecycleBase抽象类又实现了Lifecycle接口
通过前面的调用链看出来Catalina.start会调用Server接口的start方法,而StandardServer实现类的start方法就追溯到了LifeCycleBase抽象的start方法, 这个类里定义了抽象方法startInternal让子类去实现。 在start方法中也调用了startInternal方法。

StandardServer类图

StandardServer#startInternal

protected void startInternal() throws LifecycleException 
	fireLifecycleEvent(CONFIGURE_START_EVENT, null);
	setState(LifecycleState.STARTING);

	globalNamingResources.start();

	synchronized (servicesLock) 
	    //这里启动定义的多个service
		for (Service service : services) 
			service.start();
		
	

根据Server的实现类StandardServer类,我顺便查看了其所在包, 看到了整个tomcat用到的核心组件的实现类都在这里了,比如StandardEngine, StandardService,StandardHost, 可以查看其他的实现类结构。

总结



结合上面的两张图片,以及上述的源码分析,我们就能总结出来整个startup.sh过程中完成的任务

  1. Tomcat本质上是一个java程序,因此startup脚本会启动一个jvm来运行tomcat的启动类Bootstrap.
  2. Bootstrap的主要任务就是初始化tomcat的类加载器,并且创建Catalina.
  3. Catalina是一个启动类,通过解析Server.xml创建相应组件,通过调用Server接口实现类去启动Server.
  4. StandardServer通过调用父类LifecycleBase的start方法,并且重写startInternal方法来启动Server和启动Service.
  5. Service组件的职责就是管理连接器和顶层容器,他会调用连接器和顶层容器的start方法.
  6. 容器组件负责启动管理子容器,并且调用Host的start方法, 将各层容器启动起来。

参考资料

https://juejin.cn/post/7155750621864263716
https://2i3i.com/tomcat-code-3.html
https://juejin.cn/post/7082681444182523934
https://time.geekbang.org/column/article/97603
https://zhuanlan.zhihu.com/p/344635709

以上是关于tomcat源码分析如何启动服务的主要内容,如果未能解决你的问题,请参考以下文章

Tomcat7.0源码分析——启动与停止服务原理

Tomcat源码分析启动Tomcat源码

运维丨详解从源码分析tomcat如何调用Servlet的初始化

通俗易懂之Tomcat源码分析——初始化与启动

Tomcat卷一 ----架构和初始化源码分析

TOMCAT源码分析(启动框架)