先从tomcat启动脚本开始,我们可以使用startup.sh启动tomcat
startup.sh脚本分析
先判断操作系统(os400是 IBM的AIX、darwin是MacOSX 操作环境的操作系统成份、Darwin是windows平台上运行的类UNIX模拟环境)
获取catalina.sh的真实路径,并判断是否有可执行权限。调用catalina.sh脚本
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
# Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
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 "$@"
catalina.sh
和start.sh类似,先检测操作系统
获取catalina.sh真实路径,并设置环境变量CATALINA_HOME、CATALINA_BASE。一般情况下两者都是tomcat根目录
调用setenv.sh设置classpath环境变量,但是这个文件是不存在的,如果想要额外的classpath,可以新建,感觉这么设计为了扩展性
在clsspath后追加Bootstrap.jar、Tomcat-juli.jar
解析脚本参数,执行java类Bootsrap的main方法,将start作为参数传入,也就是tomcat的入口
和tomcat启动主要的是两个类:Bootstrap和Catalina类
Bootstarp.java类
main方法
1.加锁新建一个Bootstrap对象,调用init初始化并且赋值给了私有静态对象daemon
2.调用了daemon对象load和start方法,而load和start方法里面利用了反射调用了catalinaDeamon的load和start方法
可以看到该类有一个Object作为锁,新建了一个为null的私有静态Bootstrap对象。
/**
* 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);
}
}
deamon.start方法代码,调用了catelinaDaemon的start方法,之前的load方法也类似
public void start() throws Exception {
if (catalinaDaemon == null) {
init();
}
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
Catalina的start方法,启动服务器实例,注册Hook(用于资源的回收的组件的stop)
/**
* Start a new server instance.
*/
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// 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;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
init方法
1.设置了catelina.home和catalina.base
2.使用initClassLoaders()创建了commonLoader、catalinaLoader和sharedLoader
3.利用反射新建一个org.apache.catalina.startup.Catalina类对象,赋值给了catalinaDeamon。
private Object catalinaDaemon = null;
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;
public void init() throws Exception {
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
//tomcat 违背了双亲委派原则
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.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;
}
initClassLoaders创建了3个类加载器
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、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);
}
}
看到里边其实会有很多疑问:
为啥tomcat要破坏双亲委派原则?
先简单说说双亲委派原则,所有的加载器加载类的时候都会有父类加载器先去加载,父类加载不了才子类加载。因为jvm中的类的唯一性是由类加载器和类的全限定名来标识的,这种模型保证了类的唯一性。
但是tomcat是web服务器,需要解决几个问题:
- 一个web容器可能会部署两个应用程序,不同应用程序会依赖不同版本的类库,因此要保证每个应用程序的类库是独立的、互相隔离的
- web容器也有依赖的类库,不能和应用程序类混淆,要让容器类库和程序的类库隔离开来
很明显双亲委派模型解决不了问题,看看tomcat的加载器模型设计:
由WebappClassLoader去加载Webapp/WEB-INF/*的java类库,每个应用程序都有不同的WebAppClassLoader加载,这样实现了隔离。那么很显然,tomcat破坏了双亲违背原则,每个webappClassloade加载各自项目下的class文件,不会传递给父类去加载。
为啥catalinaDeam要用反射加载,调用它的start和load方法为什么还要用反射?
大家都知道,反射是会降低性能的。