前面一篇文章提到,当Tomecat启动(startup.sh)时,会调用org.apache.catalina.startup.Bootstrap.main()方法。
通过《Tomcat9源代码分析(一)-源码切入点》中的Tomcate9源代码下载地址我们获取到了apache-tomcat-9.0.2-src.zip文件,直接解压即可得到apache-tomcat-9.0.2源代码。用IDEA打开后如下图
什么都不说,咱们直奔主题来看看Bootstrap.main()做了些什么。
org.apache.catalina.startup.Bootstrap#main
/**
* 通过提供的启动Tomcat时的主要方法和入口点
*
* @param args命令行参数进行处理
*/
public static void main(String args[]) {
if (daemon == null) {
// 创建Bootstrap对象
Bootstrap bootstrap = new Bootstrap();
try {
//bootstrap初始化
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
//重置当前线程类加载器
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";
//Bootstrap加载信息
//内部操作:用反射调用catalinaDaemon的load方法加载和解析server.xml配置文件
daemon.load(args);
//Bootstrap启动
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();
} 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);
}
}
通过代码,我们可以看到该方法的执行情况
bootstrap.init();
本章讲Tomcat服务启动我们直接看start做了些什么。
我们逐一来了解各步骤做了些什么。(异常情况本系列文章暂不做探究)我们先来看看初始化(==bootstrap.init();
==)做了些什么。
org.apache.catalina.startup.Bootstrap#init()
/**
* 初始化程序
*
* @throws 抛出异常致命初始化错误
*/
public void init() throws Exception {
//初始化类加载器
initClassLoaders();
//设置Tomcat私有加载器到当前线程
Thread.currentThread().setContextClassLoader(catalinaLoader);
//加载Tomcat容器所需的class
SecurityClassLoad.securityClassLoad(catalinaLoader);
// 加载我们的启动类并调用它的process()方法
if (log.isDebugEnabled())
log.debug("Loading startup class");
//通过反射找到启动类Catalina
Class> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// 设置共享类加载器
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
//反射找到父类加载器
String methodName = "setParentClassLoader";
Class> paramTypes[] = new Class[1];
//反射找到类加载器ClassLoader
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
//调用ClassLoader.setParentClassLoader方法(执行加载器)
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
通过代码2-2我们可以看到引导程序(Bootstrap)初始化的步骤:
- 初始化类加载器
- 将类加载器设置到当前线程
- 加载Tomcat容器所需class
- 通过反射加载Tomcat启动类Catalina并调用process()类
在看初始化类加载的实现之前,我们需要先了解Java类加载器都有哪些,分别是做什么用的。
Java虚拟机规范中提到的主要类加载器;
根据java虚拟机的双亲委派模式的原则,类加载器在加载一个类时,首先交给父类加载器加载,层层往上直到Bootstrap Loader。也就是一个类最先由Bootstrap Loader加载,如果没有加载到,则交给下一层的类加载器加载,如果没有加载到,则依次层层往下,直到最下层的类加载器。这也就是说,凡是能通过父一级类加载器加载到的类,对于子类也是可见的。因此可以利用双亲委派模式的特性,使用类加载器对不同路径下的jar包或者类进行环境隔离。
然后用一张图片来展示Tomcat的类加载体系:
结合之前对双亲委派模式的类加载过程的描述,对上图所示类加载体系进行介绍:
org.apache.catalina.startup.Bootstrap#initClassLoaders
/**
* 初始化类加载器
*/
private void initClassLoaders() {
try {
//创建Tomcat基本类加载器
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();
}
//创建Tomcat容器私有类加载器
catalinaLoader = createClassLoader("server", commonLoader);
//创建WebApp共享类加载器
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
==代码2-3==我们了解initClassLoaders顺序创建各个类加载器。
org.apache.catalina.startup.Bootstrap#createClassLoader
/**
* 创建类加载器
* @param name 加载器名称
* @param parent 加载器
* @return 加载器对象
* @throws Exception 议持仓
*/
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 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
}
// 加载资源文件
if (repository.endsWith("*.jar")) {
//全局资源文件(*.jar)
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(
new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
//JAR包
repositories.add(
new Repository(repository, RepositoryType.JAR));
} else {
//文件
repositories.add(
new Repository(repository, RepositoryType.DIR));
}
}
//创建加载器对象
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
==代码2-4==则是获取创建类加载器所需参数(资源文件),调用==ClassLoaderFactory.createClassLoader==方法创建类加载器对象并返回。
/**
* 基于配置(默认值和指定的目录路径)创建和返回一个新的类加载器
*
* @param 类目录、JAR文件、JAR目录的存储库列表,或者应该添加到存储库中的URL,类加载器
* @param 新类加载器对应的父类加载器
* @return 新类加载器
*
* @exception Exception if an error occurs constructing the class loader
*/
public static ClassLoader createClassLoader(List repositories,
final ClassLoader parent)
throws Exception {
if (log.isDebugEnabled())
log.debug("Creating new class loader");
// 为类加载器构建包路径
Set set = new LinkedHashSet<>();
if (repositories != null) {
//遍历资源仓库列表
for (Repository repository : repositories) {
//如果资源类型为包路径
if (repository.getType() == RepositoryType.URL) {
//根据路径构建类加载器包路径
URL url = buildClassLoaderUrl(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 = buildClassLoaderUrl(directory);
if (log.isDebugEnabled())
log.debug(" Including directory " + url);
set.add(url);
} else if (repository.getType() == RepositoryType.JAR) {//如果资源文件路径为JAR包
File file=new File(repository.getLocation());
file = file.getCanonicalFile();
//验证JAR包
if (!validateFile(file, RepositoryType.JAR)) {
continue;
}
//根据JAR包构建类加载器包路径
URL url = buildClassLoaderUrl(file);
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();
if (filenames == null) {
continue;
}
//循环构建类加载器包路径
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 = buildClassLoaderUrl(file);
set.add(url);
}
}
}
}
// 将构造的包放入类加载器中
final URL[] array = set.toArray(new URL[set.size()]);
if (log.isDebugEnabled())
for (int i = 0; i < array.length; i++) {
log.debug(" location " + i + " is " + array[i]);
}
//类加载器授权
return AccessController.doPrivileged(
new PrivilegedAction() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
}
==代码2-5==可我们可以看出来,类加载器的创建即将资源文件添加到加载器中并授权,到此处我们可以想想,如果我们想Tomcat加载我们自己的类,是否可以在此处做功夫呢?理论可行,有兴趣的同学自己自己尝试下
,
此处本人还有个疑问的地方,老版本的类加载器,创建完毕后会将创建的类加载器注册到JMX服务中,但在Tomcat9中此处不再注册,那移动到哪去了呢?有了解的同学欢迎帮忙解答。
看到此处我们继续深入看看类加载器资源配置文件都写了些什么
conf/catalina.properties
# 多个服务资源文件路径使用“,”分隔
# 加载器的前缀用来定义加载器类型(common,server,shared)
# 路径不许是相对与catalina.base或catalina.home或绝对路径
# “common.loader”如果不设置值则将作为Catalina服务的资源文件
# “foo”:添加整个文件夹中的内容
# "foo/*.jar" 添加文件夹中所有的.jar文件
# "foo/bar.jar" 添加指定的jar文件
# 设置值使用“...”的情况下catalina.base或catalina.base路径包含一个逗号,因为“”用于赋值,路径可能会不存在
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
看到==代码2-6==我们会发现基本类加载器加载了Tomcat目录下lib文件夹下的所有文件
通过==代码2-2==我们知道下一步操作为:加载Tomcat容器所需的class
org.apache.catalina.security.SecurityClassLoad#securityClassLoad(ClassLoader)
/**
* 加载Tomcat容器所需的class
*
* @param loader 加载器(CatalinaLoader)
* @param requireSecurityManager 空
* @throws Exception
*/
static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager)
throws Exception {
if (requireSecurityManager && System.getSecurityManager() == null) {
return;
}
//加载Tomcat 核心class(org.apache.catalina.core)
loadCorePackage(loader);
loadCoyotePackage(loader);
//WebappClassLoade基础class(org.apache.catalina.loader)
loadLoaderPackage(loader);
loadRealmPackage(loader);
//Tomcat有关Servlet的class(org.apache.catalina.servlets)
loadServletsPackage(loader);
//Tomcat有关session的class(org.apache.catalina.session)
loadSessionPackage(loader);
//Tomcat工具类的class(org.apache.catalina.util)
loadUtilPackage(loader);
//Tomcat处理Cookie的
loadJavaxPackage(loader);
//Tomcat处理请求的class(org.apache.catalina.connector)
loadConnectorPackage(loader);
loadTomcatPackage(loader);
}
到这里,我们Tomcat初始化就完成了。下一篇文件我们一起来看看初始化后Bootstrap.load做了些什么