Tomcat组件梳理—Bootstrap启动器

一开始是直接从Server开始做梳理的,但是发现有很多东西是从Catalina传输过来的,Catalina又是从Bootstrap启动的,所以还是回过头来从Bootstrap开始梳理吧。

1.定义和功能

Bootstrap是Tomcat的入口类,main方法也在这个类中,脚本启动往往也是直接调用这个类。

该类作为启动整个Tomcat的启动器,有自己的一些特点,该类主要操作对象是Catalina类,但是这两个又是解耦的,解耦的方法是通过反射去获取Catalina的实例和方法,并调用。这样,Bootstrap和Catalina是隔离开的,因此如果有需要,也是可以自己实现一个启动器的。

Bootstrap是Catalina的装载器,并负责创建Tomcat自用的类加载器。

2.启动器的属性

先看Bootstrap的属性有哪些,属性是一个类的基本特征,一个类的具体实现就是由属性和功能方法来实现的。

public final class Bootstrap {

  /**
* main使用的守护对象
*/
private static Bootstrap daemon = null; //CatalinaBase的File对象
private static final File catalinaBaseFile;
//CatalinaHome的File对象
private static final File catalinaHomeFile; }

需要解释一下CatalinaBase和CatalinaHome

我们先提出一个需求:如果要在一台机器上部署多个Tomcat,必须要在一个台机器上安装多个tomcat吗?虽然直接解压就能用,但是tomcat有很多压缩包和公用的目录文件,都不能公用吗?

答案是:能。

那怎么实现呢?就是先确认tomcat的哪些目录能被共享,哪些目录不能被共享,然后共享的所有tomcat实例都能访问,不被共享的只能被自己访问。那现在来看看哪些能被共享吧。

tomcat解压后,除了一些文件,有以下目录:

  • bin —运行脚本
  • conf —配置文件
  • lib —核心库文件
  • logs --日志目录
  • temp --临时目录
  • webapps —自动装载的应用程序目录
  • work --JVM临时文件目录(java.io.tmpdir)

在这些目录中,只有bin和lib目录能被多个tomcat实例公用,于是我们把这两个目录的父级目录叫做安装目录,并用catalina.home来表示。

在目录中,被tomcat实例所私有的目录为:conf,logs,temp,webapps,work。于是我们把这些目录的父级目录叫做工作目录,并用catalina.base来表示。

到这里,我们应该已经明白了catalina.homecatalina.base两个参数的作用了。这两个参数只在用这种方法来做单机器多实例的时候才有用,但是现在微服务即使部署多个实例,也会直接放多个tomcat,因为磁盘的价格已经很便宜了,可以不用复杂度来换区空间。

所以,如果是一个tomcat,这两个属性指向的是相同位置,即都是这些目录的父级目录。

到这里,上面我们提出的问题,基本就被解决。

参考了文章:https://blog.csdn.net/duqian94/article/details/52460703

3.启动器的功能

3.1.Main方法解析

还是先看Main方法的代码吧

/**
* main方法,整个程序的主入口。
*/
public static void main(String args[]) { if (daemon == null) { //1.new过程中主要执行static中的设置Catalina.home和Catalina.base的值
Bootstrap bootstrap = new Bootstrap();
try {
//2.主要完成类加载器的初始化,包括common,catalina,share三个类加载器,
// 并设置catalina的父类加载器。
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];
} //3.判断shell传入的值,执行对应的动作
if (command.equals("startd")) {
//执行start方法的内容,主要为执行Catalina的load()和start()方法
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) {
handleThrowable(t);
t.printStackTrace();
System.exit(1);
} }

Bootstrap中的main()方法时tomcat软件的入口方法,不管用什么方法启动tomcat,都是在调用这个方法。

该方法的业务逻辑比较简单,只有三个点:

  • 1.new一个Bootstrap实例,在new的过程中有一段static的代码段会一起初始化,该代码段主要解决catalina.homecatalina.base参数的值,因为代码中会遇到这些变量,所以要去这些变量一定是在的。
  • 2.执行init()方法,该方法主要用来创建common,shared,catalinaLoader三个类加载器,并将对应的jar包加载进来。
  • 3.判断传入的参数是什么,然后通过反射调用Catalina中对应的方法。

3.2.new方法执行静态代码块

这里的代码就是在new时,需要初始化的代码。这里的代码逻辑没啥好说的,就是在检查参数,然后重新设值。放一下代码看看。

static {
//该段主要设置Catalina.home路径和Catalina.base路径
// 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.3.init方法创建类加载器

init()方法主要用来创建三个类加载器,并指定catalina的parent类加载器,看下代码中的处理逻辑。

public void init() throws Exception {

  //1.初始化类加载器:commonLoader,catalinaLoader,sharedLoader
initClassLoaders(); //2.设置当前线程的类加载器
Thread.currentThread().setContextClassLoader(catalinaLoader); //3.设置Java SecutityManager,目前看没啥用?
SecurityClassLoad.securityClassLoad(catalinaLoader); //4.加载Catalina类,并为Catalina设置父类加载器
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance(); //通过反射调用catalina.setParentClassLoader(ClassLoader classloader)方法,
// 目的是设置Catalina的父类加载器为sharedLoader类加载器
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
//反射找到对应的方法时,需要指明两个参数:1.方法名称,2.方法对应的参数类型的列表,数组形式
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues); //5.把catalinaDaemon的值设置为org.apache.catalina.startup.Catalina
catalinaDaemon = startupInstance; }

先梳理此处的逻辑:

  • 1.创建三个类加载器:commonLoadercatalinaLoadershareLoader,这三个类加载器的目的不一样。

    • commonLoader:Tomcat最基本的类加载器,直接继承systemLoader,加载路径中的class可以被tomcat容器本身以及各个webapp访问。加载路径为:common.loader,默认执行${catalina.home}/lib下的包。
    • catalinaLoader:tomcat容器私有的类加载器,加载路径中的class对webapp不可见。加载路径为server.loader,默认为空。
    • sharedLoader:各个webapp共享的类加载器,加载路劲中的class对于多有webapp可见,但是对于tomcat容器不可见。加载路径为shared.loader,默认为空。
  • 2.把当前线程的类加载器设置为catalinaLoader.即当前线程只会加载tomcat私有的jar包。当前线程启动所有的组件,然后后面又会被阻塞,用来等待shutdown命令。
  • 3.设置Java 的SecutityManager,这个东西讲不清楚是干嘛,可以先不管。
  • 4.通过反射实例化Catalina类,并调用setParentClassLoader方法,为catalina设置ParentClassLoader属性。

其实从上面和代码中都可以看到,最重要的是就是第一步,创建三个类加载器了。

3.4.反射调用catalina的方法

在main方法的第三步中,根据传入的参数,选择对应的方法,此处虽然调用的是Bootstrap的方法,但是实际上时在通过放射调用Catalina中的方法,可以看一个代码示例:

private void load(String[] arguments) throws Exception {
//主要是通过反射调用Catalina的load()方法 // 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); }

可以看到代码,其实就是通过反射调用Catalina的load()方法。

3.5.操作Catalina的方法总结

Bootstrap在代码中对Catalina的操作,我们总结一下看看。

第一个是实例化Catalina

//org.apache.catalina.startup.Bootstrap#init()
//4.加载Catalina类,并为Catalina设置父类加载器
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();

第二个是调用方法,设置Catalina的parentClassLoader为sharedLoader

//通过反射调用catalina.setParentClassLoader(ClassLoader classloader)方法,
// 目的是设置Catalina的父类加载器为sharedLoader类加载器
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
//反射找到对应的方法时,需要指明两个参数:1.方法名称,2.方法对应的参数类型的列表,数组形式
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);

第三个是调用Catalina的其他方法,主要接解决对应的启动或者关闭的命令

//org.apache.catalina.startup.Bootstrap#start
public void start() throws Exception {
//主要调用Catalina的start()方法
if( catalinaDaemon==null ) {
init();
} Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null); }

4.总结

再明确一次Bootstrap的作用,一个是检查当前环境,二是创建三个类加载器,三是根据用户输入的参数,通过反射调用对应的方法。

环境检查在3.2中讲解的,类加载器在3.3中讲解的,根据参数执行方法在3.4.中讲解的。

以上,我们对Tomcat的启动器组件Bootstrap的分析就结束了。

1.Tomcat组件梳理—Bootstrap启动器的更多相关文章

  1. Tomcat组件梳理--Catalina

    Tomcat组件梳理--Catalina 1.定义和功能 Catalina是Tomcat的核心组件,是Servlet容器,Catalina包含了所有的容器组件,其他模块均为Catalina提供支撑.通 ...

  2. Tomcat组件梳理—Service组件

    Tomcat组件梳理-Service组件 1.组件定义 Tomcat中只有一个Server,一个Server可以用多个Service,一个Service可以有多个Connector和一个Contain ...

  3. Tomcat组件梳理—Digester的使用

    Tomcat组件梳理-Digester的使用 再吐槽一下,本来以为可以不用再开一个篇章来梳理Digester了,但是发现在研究Service的创建时,还是对Digester的很多接口或者机制不熟悉,简 ...

  4. Tomcat组件梳理--Server

    Tomcat组件梳理--Server 1.Server组件的定义和功能概述 定义: Server组件用于描述一个启动的Tomcat实例,一个Tocmat被启动,在操作系统中占用一个进程号,提供web服 ...

  5. tomcat 组件研究一--启动过程总结

    作为java 开发者,从开始学习java 便知道tomcat 这个容器了,但是一直却没有怎么研究过它的内部结构,以前对tomcat的认识也仅仅局限在那几个常用的目录放什么东西,那几个常用的配置文件应该 ...

  6. JS组件系列——Bootstrap文件上传组件:bootstrap fileinput

    前言:之前的三篇介绍了下bootstrap table的一些常见用法,发现博主对这种扁平化的风格有点着迷了.前两天做一个excel导入的功能,前端使用原始的input type='file'这种标签, ...

  7. JS组件系列——Bootstrap组件福利篇:几款好用的组件推荐(二)

    前言:上篇 JS组件系列——Bootstrap组件福利篇:几款好用的组件推荐 分享了几个项目中比较常用的组件,引起了许多园友的关注.这篇还是继续,因为博主觉得还有几个非常简单.实用的组件,实在不愿自己 ...

  8. JS组件系列——Bootstrap寒冬暖身篇:弹出框和提示框效果以及代码展示

    前言:对于Web开发人员,弹出框和提示框的使用肯定不会陌生,比如常见的表格新增和编辑功能,一般常见的主要有两种处理方式:行内编辑和弹出框编辑.在增加用户体验方面,弹出框和提示框起着重要的作用,如果你的 ...

  9. JS组件系列——Bootstrap 树控件使用经验分享

    前言:很多时候我们在项目中需要用到树,有些树仅仅是展示层级关系,有些树是为了展示和编辑层级关系,还有些树是为了选中项然后其他地方调用选中项.不管怎么样,树控件都是很多项目里面不可或缺的组件之一.今天, ...

随机推荐

  1. 阿里云服务器25端口禁用之如何使用Java发邮件(解决25端口禁用问题)

    通常我们在本地使用Java发送邮件,通常是没有问题的,但是部署到服务器的话,就可能遇到问题.当然了,这与运营商也有关系.比如我之前在咖啡主机上购买虚拟机,然后将个人网站部署上去,通常是没有问题的,没有 ...

  2. DateUtils.formate()函数的“bug”

    写在前面 项目功能测试, 日期格式转换出现个诡异的问题, 转换后的时间总是和系统当前时间相差8小时, 问题是另一个项目和这个代码完全一样, DateUtils.java, 包括formatDate() ...

  3. android -------- VideoCache 视频播放(缓存视频到本地)

    先前做了一个小视频的功能,里面有播放多个视频的功能,为了效率,我加了视频缓存功能: 一方面耗费用户的流量,另一方面直接从本地播放要更流畅 网上看资料,一个视频缓存库,使用起来很方便,还不错,就分享给大 ...

  4. C#实体类null自动转空字符串

    C#实体类null自动转空字符串 using System.ComponentModel.DataAnnotations; [DisplayFormat(ConvertEmptyStringToNul ...

  5. Python 使用 win32com 模块对 word 文件进行操作

    what's the win32com 模块 win32com 模块主要为 Python 提供调用 windows 底层组件对 word .Excel.PPT 等进行操作的功能,只能在 Windows ...

  6. 如何将本地jar包放入本地maven仓库和远程私服仓库

    1.将本地jar包放入本地仓库.只需执行如下命令即可: mvn install:install-file -Dfile=D:/demo/fiber.jar -DgroupId=com.sure -Da ...

  7. pipeline结合jacoco获取自动化测试代码覆盖率

    1下载jacoco,并上传至服务器:https://www.eclemma.org/jacoco/ 2.应用服务tomcat的catalina.sh增加jacocoagent #JAVA_OPTS=& ...

  8. Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavailable) 解决办法

    1:我遇到的问题: 在开机运行apt install vim 命令的时候,如下报错: 2:参考博客: 在Ubuntu中,有时候运用sudo  apt-get install 安装软件时,会出现一下的情 ...

  9. (转)Intellij Idea工具栏添加打开选中文件的资源管理器位置

    背景:在idea的view>toolbar上面添加工具按钮,能够简化操作,现在添加打开资源管理按钮,后续功能待研究 Intellij Idea工具栏添加打开选中文件的资源管理器位置 工具栏-右击 ...

  10. CentOS7开机进入紧急模式EmergencyMode的解决办法

    这个情况主要是 修改了 /etc/fstab 文件 vi /etc/fstab 文件 如果之前添加过一行 把添加的一行注释掉 如果之前没有添加过,把挂载到 /home 这一行取消注释 操作之后 记得 ...