上一篇文章分析了通过startup.bat启动Tomcat相当于执行如下代码,即运行Bootstrap start
start "Tomcat" "C:\Program Files\Java\jdk1.7.0_51\bin\java" -Djava.util.logging.config.file="D:\Program Files\apache-tomcat-8.0.3\conf\logging.properties" -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs="D:\Program Files\apache-tomcat-8.0.3\endorsed" -classpath "D:\Program Files\apache-tomcat-8.0.3\bin\bootstrap.jar;D:\Program Files\apache-tomcat-8.0.3\bin\tomcat-juli.jar" -Dcatalina.base="D:\Program Files\apache-tomcat-8.0.3" -Dcatalina.home="D:\Program Files\apache-tomcat-8.0.3" -Djava.io.tmpdir="D:\Program Files\apache-tomcat-8.0.3\temp" org.apache.catalina.startup.Bootstrap start
今天来看看Bootstrap的执行流程
1. 执行static块
为什么没有执行main方法,而先执行static块呢?
原来一个类的运行,JVM做会以下几件事情:类装载、链接、初始化、实例化,而初始化阶段做的事情是初始化静态变量和执行静态方法等
static块的作用是设置catalinaBaseFile、catalinaHomeFile
static块:
static { // Will always be non-null //System.getProperty("user.dir"),获取当前目录 //由于是在$CATALINA_HOME\bin下运行的Bootstrap,所以userDir为$CATALINA_HOME\bin String userDir = System.getProperty("user.dir"); // Home first //Globals是存放全局常量的类 //Globals.CATALINA_HOME_PROP = "catalina.home" //catalina.home在运行Bootstrap时已设置(Tomcat的根目录) String home = System.getProperty(Globals.CATALINA_HOME_PROP); File homeFile = null; //获取Tomcat的绝对路径 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 catalinaHomeFile = homeFile; System.setProperty( Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath()); // Then base String base = System.getProperty(Globals.CATALINA_BASE_PROP); //设置catalinaBaseFile 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()); }
对比getAbsoluteFile()、getCanonicalFile()
getAbsoluteFile获取绝对文件(如果文件路径中包含.和..,不解析)
getCanonicalFile获取经典文件(如果文件路径中包含.和..,解析)
创建Test.java
import java.io.File; import java.io.IOException; public class Test { public static void main(String[] args) throws IOException { File f = new File("D://Program Files//apache-tomcat-8.0.3"); File aFile = f.getAbsoluteFile(); File bFile = f.getCanonicalFile(); System.out.println("文件路径不包含.或.."); System.out.println("getAbsoluteFile()--->" + aFile.toString()); System.out.println("getCanonicalFile()--->" + bFile.toString()); File f1 = new File("D://Program Files//apache-tomcat-8.0.3//.."); File aFile1 = f1.getAbsoluteFile(); File bFile1 = f1.getCanonicalFile(); System.out.println("文件路径包含.或.."); System.out.println("getAbsoluteFile()--->" + aFile1.toString()); System.out.println("getCanonicalFile()--->" + bFile1.toString()); } }
输出:
文件路径不包含.或.. getAbsoluteFile()--->D:\Program Files\apache-tomcat-8.0.3 getCanonicalFile()--->D:\Program Files\apache-tomcat-8.0.3 文件路径包含.或.. getAbsoluteFile()--->D:\Program Files\apache-tomcat-8.0.3\.. getCanonicalFile()--->D:\Program Files
接写来将执行main函数(以下以首次运行Bootstrap start进行解读)
2. 执行main函数
main函数:
public static void main(String args[]) {
if (daemon == null) {
// Don‘t set daemon until init() has completed
//***2.1***
Bootstrap bootstrap = new Bootstrap();
try {
//***2.2***
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);
}
//***2.3***
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")) {
//***2.4***
daemon.setAwait(true);
//***2.5***
daemon.load(args);
//***2.6***
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);
}
}
2.1 创建Bootstrap
2.2 调用Bootstrap.init()
init方法:
public void init() throws Exception { //创建commonLoader、catalinaLoader、sharedLoader initClassLoaders(); //为当前线程设置ClassLoader Thread.currentThread().setContextClassLoader(catalinaLoader); //设置SecurityClassLoad。具体作用还不清楚。。。 SecurityClassLoad.securityClassLoad(catalinaLoader); // Load our startup class and call its process() method if (log.isDebugEnabled()) log.debug("Loading startup class"); //通过反射实例化Catalina 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; //通过反射设置Catalina的parentClassLoader Method method = startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues); //将实例化的Catalina赋值给catalinaDaemon catalinaDaemon = startupInstance; }
initClassLoaders方法:
private void initClassLoaders() { try { //创建commonLoader 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 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 { //CatalinaProperties解析$CATALINA_HOME\conf\catalina.properties, //并将catalina.properties内的属性存为系统属性 //catalina.properties内common.loader="${catalina.base}/lib", //"${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" //读取common.loader String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; //将${catalina.base},${catalina.home}替换为Tomcat的绝对路径 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)); } } //ClassLoaderFactory依据repositories的内容创建ClassLoader return ClassLoaderFactory.createClassLoader(repositories, parent); }
ClassLoaderFactory依据repositories的内容创建ClassLoader时,repositories包含的四个值均是$CATALINA_HOME\lib这个路径。那么创建的ClassLoader会不会有重复的jar呢?
查看ClassLoaderFactory.createClassLoader方法,即可找到答案
public static ClassLoader createClassLoader(List<Repository> repositories, final ClassLoader parent) throws Exception { if (log.isDebugEnabled()) log.debug("Creating new class loader"); // Construct the "class path" for this class loader Set<URL> set = new LinkedHashSet<>(); if (repositories != null) { for (Repository repository : repositories) { if (repository.getType() == RepositoryType.URL) { URL url = new URL(repository.getLocation()); if (log.isDebugEnabled()) log.debug(" Including URL " + url); set.add(url); } else if (repository.getType() == RepositoryType.DIR) { File directory = new File(repository.getLocation()); directory = directory.getCanonicalFile(); if (!validateFile(directory, RepositoryType.DIR)) { continue; } URL url = directory.toURI().toURL(); if (log.isDebugEnabled()) log.debug(" Including directory " + url); set.add(url); } else if (repository.getType() == RepositoryType.JAR) { File file=new File(repository.getLocation()); file = file.getCanonicalFile(); if (!validateFile(file, RepositoryType.JAR)) { continue; } URL url = file.toURI().toURL(); if (log.isDebugEnabled()) log.debug(" Including jar file " + url); set.add(url); } else if (repository.getType() == RepositoryType.GLOB) { File directory=new File(repository.getLocation()); directory = directory.getCanonicalFile(); if (!validateFile(directory, RepositoryType.GLOB)) { continue; } if (log.isDebugEnabled()) log.debug(" Including directory glob " + directory.getAbsolutePath()); String filenames[] = directory.list(); for (int j = 0; j < filenames.length; j++) { String filename = filenames[j].toLowerCase(Locale.ENGLISH); if (!filename.endsWith(".jar")) continue; File file = new File(directory, filenames[j]); file = file.getCanonicalFile(); if (!validateFile(file, RepositoryType.JAR)) { continue; } if (log.isDebugEnabled()) log.debug(" Including glob jar file " + file.getAbsolutePath()); URL url = file.toURI().toURL(); set.add(url); } } } }
ClassLoaderFactory在遍历repositories时,将jar文件的URL放在LinkedHashSet里,而LinkedHashSet里不会添加重复的数据。因此,创建的ClassLoader不会有重复的jar
2.3 解析参数
2.4 调用Bootstrap.setAwait(true)
Bootstrap.setAwait(true)内部通过反射,设置Catalina的await属性(默认为false)为true
启动Catalina过程中,当Catalina将Tomcat的所有组件启动之后,会检查await属性,如果为true,会调用Catalina.await(),而Catalina.await()又会调用其内部的Server的await()
if (await) { await(); stop(); }
public void await() { getServer().await(); }
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关闭
2.5 调用Bootstrap.load(args)
Bootstrap.load(args)内部通过反射调用Catalina.load(args),Catalina将利用Digest(Digest详解将在后面整理)解析server.xml,创建相应组件的实例,之后调用Server.init(),Server初始化时,又会调用其内部的Service的init方法,即调用某一组件的init方法时,将触发其子组件的init方法
执行Bootstrap.load(args)将触发的动作
2.6 调用Bootstrap.start()
执行Bootstrap.start()将触发的动作
带注释的 Bootstrap.java文件下载地址:
http://download.csdn.net/detail/flyliuweisky547/7179505
Tomcat 8(七)解读Bootstrap,布布扣,bubuko.com
原文:http://blog.csdn.net/flyliuweisky547/article/details/23464245