小白记录自己的一点理解,有错误和不足的地方欢迎各位朋友指正。
1main方法
main方法包括两段代码
第一段:
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);
}
}
判断daemon是否为null,不为null,直接将Catalina ClassLoader设置到当前线程,用于加载服务器相关类,为null则进入bootstrap的init方法。
public void init() throws Exception {
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
...
}
init方法会调用initClassLoaders,同样也会将Catalina ClassLoader设置到当前线程设置到当前线程,进入initClassLoaders来看看。initClassLoaders会初始化三个类加载器,分别是commonLoader,catalinaLoader,sharedLoader并建立他们之间的关系,catalinaLoader和sharedLoader的parent是commonLoader。
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);
}
}
进入createClassLoader
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// Local repository
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(new Repository(repository, RepositoryType.DIR));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
逐个方法跟代码
首先是CatalinaProperties.getProperty(name + ".loader");
CatalinaProperties先通过静态代码块loadProperties()方法加载~\conf\catalina.properties的配置到CatalinaProperties的成员变量properties中。~\conf\catalina.properties配置类加载器common classloader,shared classloader,catalina classloader的加载路径,分别由common.loader ,shared.loader,server.loader指定,默认分别是指向${catalina.home}/lib下的包,空,空。
createClassLoader(String name, ClassLoader parent)先获得~\conf\catalina.properties配置的类加载器要加载的路径/文件,如果没有直接返回parent ,如果有,将加载路径/文件放到repositories中,调用ClassLoaderFactory.createClassLoader(repositories, parent);
ClassLoaderFactory的静态方法createClassLoader(repositories, parent)校验路径/文件是否可读,把可读的url放入array,返回URLClassLoader。
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
}
回看init()方法,初始化完类加载器后,使用反射机制调用org.apache.catalina.startup.Catalina类下的setParentClassLoader方法。
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;
}
main方法的第二段代码:
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);
}
根据传递进来的参数决定走哪一步,当双击startup.bat时,传进来的是start(???),所以会来到这段:
else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
这里主要是调用三个方法,setAwait,load和start。
所以对于Bootstrap重要关注的就是init,setAwait,load和start这四个方法。
init方法调用initClassLoaders初始化类加载器,然后将初始化好的catalinaLoader设置到当前线程,接着通过反射调用org.apache.catalina.startup.Catalina类的setParentClassLoader,将sharedLoader传入。
为什么不直接通过Bootstrap类直接启动tomcat,而是通过反射生成Catalina实例启动?
再查看tomcat目录结构时应该发现,Bootstrap并不在CATALINAHOME/lib目录下,而是在CATALINA_HOME/bin目录中,Bootstrap和Catalina松耦合(通过反射调用Catalina),它直接依赖JRE运行并为Tomcat应用服务器创建共享类加载器,用于构造Catalina和整个tomcat服务器。实现了启动入口和核心环境的解耦,简化了启动。??????
2setAwait
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);
}
通过反射调用catalina的setAwait方法,其设置的值是留给后面用的,当Catalina将Tomcat的所有组件启动之后,会检查await属性,如果为true,会调用Catalina.await(),而Catalina.await()又会调用其内部的Server的await()。
public void start() {
...
if (await) {
await();
stop();
}
}
Server.await()包含一个while循环,此循环用于监听指定socket端口(默认为8005)的连接,当某个连接传入的参数为”SHUTDOWN”(默认为”SHUTDOWN”)时,终止此while循环(端口号和终止while循环的参数,在server.xml的Server标签设置)。
Server.await()用来维持Bootstrap的main方法(main thread)处于运行状态,而线程池中监听http请求的线程是守护线程(daemon thread)。
当Tomcat的指定端口接收到关闭命令时,Server.await()内的while循环终止,然后Catalina会调用stop()方法,关闭Tomcat的所有组件,最终Bootstrap的main thread终止,Tomcat关闭。
3load
该方法通过反射调用catalina的load方法。
catalina的load方法首先初始化目录(initDirs)和初始化命名服务(initNaming),然后是createStartDigester(要了解这个方法,应该先了解一下Digester,它是apache的一个开源组件,通过它可以很方便的从xml文件生成java对象),该方法初始化Digester,为Xml的标签即解析模式增加处理规则rule。
关于Digester,可以参考这篇博文:
4start