从Tomcat启动开始的源码分析
Tomcat启动分析
从启动脚本开始进入Tomcat的世界。
1.startup.sh
开源框架源码剖析从tomcat架构开始:
- 获取bin目录路径 ,在bin目录下执行脚本;
- 调用 catalina.sh 的start语句块。
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
2.catalina.sh
脚本根据传参调用Bootstrap类main方法,执行start语句块
org.apache.catalina.startup.Bootstrap "$@" start \
catalina.sh还有其他命令选项,本文围绕启动做源码分析,不是我们此次的重点.
Using CATALINA_BASE: /home/kali/Software/apache-tomcat-9.0.20
Using CATALINA_HOME: /home/kali/Software/apache-tomcat-9.0.20
Using CATALINA_TMPDIR: /home/kali/Software/apache-tomcat-9.0.20/temp
Using JRE_HOME: /home/kali/Software/jdk-12.0.1
Using CLASSPATH: /home/kali/Software/apache-tomcat-9.0.20/bin/bootstrap.jar:/home/kali/Software/apache-tomcat-9.0.20/bin/tomcat-juli.jar
Usage: catalina.sh ( commands ... )
commands:
debug Start Catalina in a debugger
debug -security Debug Catalina with a security manager
jpda start Start Catalina under JPDA debugger
run Start Catalina in the current window
run -security Start in the current window with security manager
start Start Catalina in a separate window
start -security Start in a separate window with security manager
stop Stop Catalina, waiting up to 5 seconds for the process to end
stop n Stop Catalina, waiting up to n seconds for the process to end
stop -force Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running
stop n -force Stop Catalina, wait up to n seconds and then use kill -KILL if still running
configtest Run a basic syntax check on server.xml - check exit code for result
version What version of tomcat are you running?
3.org.apache.catalina.startup.Bootstrap.class
(1) start命令的流程图
这里只列出start命令的流程图,其他的stop和configtest等命令暂不分析.
(2) 初始化全局的静态变量
Bootstrap类的静态方法块主要用来初始化全局的静态变量catalinaBaseFile和catalinaHomeFile,
这两个就是执行catalina.sh命令打印出来的tomcat家目录:
Using CATALINA_BASE: /home/kali/Software/apache-tomcat-9.0.20
Using CATALINA_HOME: /home/kali/Software/apache-tomcat-9.0.20
CATALINA_HOME是Tomcat的安装目录,CATALINA_BASE是Tomcat的工作目录。
多实例tomcat与多版本tomcat运行的时候,需要考虑区别设置.
private static final File catalinaBaseFile;
private static final File catalinaHomeFile;
static {
// Will always be non-null
String userDir = System.getProperty("user.dir");
// Home first
String home = System.getProperty(Globals.CATALINA_HOME_PROP);
File homeFile = null;
if (home != null) {
File f = new File(home);
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
if (homeFile == null) {
// First fall-back. See if current directory is a bin directory
// in a normal Tomcat install
File bootstrapJar = new File(userDir, "bootstrap.jar");
if (bootstrapJar.exists()) {
File f = new File(userDir, "..");
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
}
if (homeFile == null) {
// Second fall-back. Use current directory
File f = new File(userDir);
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
catalinaHomeFile = homeFile;
System.setProperty(
Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());
// Then base
String base = System.getProperty(Globals.CATALINA_BASE_PROP);
if (base == null) {
catalinaBaseFile = catalinaHomeFile;
} else {
File baseFile = new File(base);
try {
baseFile = baseFile.getCanonicalFile();
} catch (IOException ioe) {
baseFile = baseFile.getAbsoluteFile();
}
catalinaBaseFile = baseFile;
}
System.setProperty(
Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
}
(3) main方法
说起main方法,初学Java的时候用的很多,第一次就是打印Hello,World!. 后来WEB项目中实际开发很少用的到main,写单元测试用例都是Junit.分析Tomcat源码遇到老朋友很开心的说,赶紧贴上它:
/**
* Daemon object used by main.
*/
private static final Object daemonLock = new Object();
private static volatile Bootstrap daemon = null;
public static void main(String args[]) {
synchronized (daemonLock) {
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);
daemon.load(args);
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 {
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);
}
}
(4) 初始化Bootstrap实例: daemon对象
main方法开头就是先使用同步代码块对daemon对象进行同步操作,锁对象为daemonLock,进入同步代码块前要获得daemonLock对象的锁. 在同步代码块中判断daemon对象是否为null,如果已经初始化过了,就把daemon对象的catalinaLoader属性放到当前线程的上下文中.如果daemon对象为null,就需要执行init方法,进行初始化.
/**
* Daemon reference.
*/
private Object catalinaDaemon = null;
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if (commonLoader == null) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader = this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
/**
* Initialize daemon.
* @throws Exception Fatal initialization error
*/
public void init() throws Exception {
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
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;
}
init方法做了那些事呢?
- 首先是创建类加载器commonLoader,catalinaLoader和sharedLoader
Tomcat 5.X 有3组目录(common,server,shared)可以存放Java类库,三组目录的可见性不一样.
Tomcat 6.X 以后就没有common,server,shared目录了,只有在catalina.properties中设置了server.loader和share.loader项之后才会建立catalinaLoader和sharedLoader的实例,否则会用到这两个类加载器的地方都会用commonLoader实例代替,从initClassLoaders()方法中可以明显地看到. - 利用Java反射机制,调用了org.apache.catalina.startup.Catalina的setParentClassLoader方法,传递的参数是sharedLoader类加载器.
为什么要用反射机制而不直接创建Catalina对象来调用setParentClassLoader方法呢?
(5) 调用daemon对象的setAwait方法
利用Java反射机制,调用了org.apache.catalina.startup.Catalina的setAwait方法,传递的参数是布尔类型await.
/**
* Set flag.
* @param await <code>true</code> if the daemon should block
* @throws Exception Reflection error
*/
public void setAwait(boolean await)
throws Exception {
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
(6) 调用daemon对象的load方法
利用Java反射机制,调用了org.apache.catalina.startup.Catalina的load方法,传递的参数是字符数组类型arguments.
/**
* Load daemon.
*/
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);
}
(7) 调用daemon对象的start方法
利用Java反射机制,调用了org.apache.catalina.startup.Catalina的start方法
/**
* Start the Catalina daemon.
* @throws Exception Fatal start error
*/
public void start() throws Exception {
if (catalinaDaemon == null) {
init();
}
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
(8) 调用daemon对象的getServer方法判断实例是否创建成功
利用Java反射机制,调用了org.apache.catalina.startup.Catalina的getServer方法获取server实例
/**
* getServer() for configtest
*/
private Object getServer() throws Exception {
String methodName = "getServer";
Method method = catalinaDaemon.getClass().getMethod(methodName);
return method.invoke(catalinaDaemon);
}
如果server实例创建成功则启动成功;
如果server实例创建失败则失败调用System.exit(1);非正常退出程序.