当前位置: 首页 > news >正文

Tomcat - 初始化流程分析

前言

在 Tomcat - 源码阅读环境搭建 这篇文章中我们搭建了 Tomcat 源码阅读的环境;在 Tomcat - 从源码阅读中分析核心组件 中我们分析了 Tomcat 的核心组件及其依赖关系。接下来我们就一起来跟踪一下 Tomcat 源码中的初始化流程

分析

org.apache.catalina.startup.Bootstrap 入口类

public static void main(String args[]) {

    synchronized (daemonLock) {
        if (daemon == null) {
            // 首先会初始化一个 Bootstrap 对象,初始化一些静态的变量和代码块
            Bootstrap bootstrap = new Bootstrap();
            try {
            	// 1、核心流程
                bootstrap.init();
            }
            ...
            daemon = bootstrap;
        }
        ...
    }

    try {
    	// 默认命令为 start
        String command = "start";
        ...
        } else if (command.equals("start")) {
            // 调用 Catalina 实例的 setAwait 方法,参数为 true
            daemon.setAwait(true);
            // 2、核心流程,利用反射调用 Catalina 实例的 load 无参方法
            daemon.load(args);  // 核心组件初始化流程(Server、Connector、Engine、Service、Endpoint、ProtocolHandle等),并绑定好了端口,准备接收数据
            // 3、利用反射调用 Catalina 实例的 start 方法
            daemon.start();
            // 利用反射调用 Catalina 实例的 getServer 方法
            if (null == daemon.getServer()) {
                System.exit(1);
            }
        }
        ...
}

1、init() 方法

public void init() throws Exception {

   	// 初始化类加载器
    initClassLoaders();

    ...
    // 获取到 org.apache.catalina.startup.Catalina 类
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    // 初始化 Catalina 实例
    Object startupInstance = startupClass.getConstructor().newInstance();

   	...
    // 下面这一块是调用 Catalina 类中的 setParentClassLoader 方法,把 sharedLoader 作为方法入参
    {
        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);
    }
    // startupInstance 是初始化后的 Catalina,并且设置了其中的 parentClassLoader 属性
    catalinaDaemon = startupInstance;
}

1.1、initClassLoaders() 方法

ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;

/**
 * 初始化 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 = createClassLoader("server", commonLoader);
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}

2、load() 方法

private void load(String[] arguments) throws Exception {

   	// Call the load() method
    String methodName = "load";
    ...
    // 调用 Catalina 的 load 无参方法
    Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    method.invoke(catalinaDaemon, param);
}
  • 查看 Catalinaload() 方法
public void load() {

  	...

    // Parse main server.xml
    // 1、解析 server.xml 配置文件,并创建了 Server 对象
    parseServerXml(true);
    Server s = getServer();
    if (s == null) {
        return;
    }

    ...

    // Start the new server
    try {
        // 这里初始化好了 service、engine、connector
        // 2、核心流程
        getServer().init(); // 初始化 server
    }
    ...
}

2.1、parseServerXml() 方法

protected void parseServerXml(boolean start) {
 	// Set configuration source
    // 首先获取到默认的配置文件 conf/server.xml
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
    File file = configFile();
    ...

    ServerXml serverXml = null;

    if (serverXml != null) {
        serverXml.load(this);
    } else {
        try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
            // Create and execute our Digester
            // 1、创建 解析器,来解析 xml 文件
            Digester digester = start ? createStartDigester() : createStopDigester();
            InputStream inputStream = resource.getInputStream();
            InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
            inputSource.setByteStream(inputStream);
            digester.push(this);
            ...
            // 2、核心流程,解析到了 Server
            digester.parse(inputSource);
            ...
        }
        ...
    }
}
2.2.1、createStartDigester() 创建解析器
protected Digester createStartDigester() {
  	// Initialize the digester
    Digester digester = new Digester();
	...
    // Configure the actions we will be using
    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
	...
    digester.addObjectCreate("Server/GlobalNamingResources",
                             "org.apache.catalina.deploy.NamingResourcesImpl");
	...
    digester.addObjectCreate("Server/Service",
                             "org.apache.catalina.core.StandardService",
                             "className");
	...
    digester.addRule("Server/Service/Connector",
                     new ConnectorCreateRule());
	...
    digester.addRule("Server/Service/Connector", new AddPortOffsetRule());
	...
    // When the 'engine' is found, set the parentClassLoader.
    digester.addRule("Server/Service/Engine",
                     new SetParentClassLoaderRule(parentClassLoader));
	...
    return digester;

}
2.2.2、Digesterparse() 方法

server.xml 中解析出 Server 对象

public Object parse(InputSource input) throws IOException, SAXException {
  	configure();
    getXMLReader().parse(input); // 获取到 XMLReader,并解析 server.xml
    return root;    // 封装为 Catalina 后返回
}
  • getXMLReader() 获取 XMLReader
public XMLReader getXMLReader() throws SAXException {
  	if (reader == null) {
  		// 1、getParser
  		// 2、getXMLReader
        reader = getParser().getXMLReader();
    }

    ...
    return reader;
}

getParser() 获取解析器:

public SAXParser getParser() {

  	// Return the parser we already created (if any)
    if (parser != null) {
        return parser;
    }

    // Create a new parser
    try {
    	// 工厂模式
        parser = getFactory().newSAXParser();
    } catch (Exception e) {
        log.error(sm.getString("digester.createParserError"), e);
        return null;
    }

    return parser;
}

获取到工厂类:

public SAXParserFactory getFactory() throws SAXNotRecognizedException, SAXNotSupportedException,
            ParserConfigurationException {

	if (factory == null) {
		// 返回工厂类 SAXParserFactory 的实例
        factory = SAXParserFactory.newInstance();

        ...
    }
    return factory;
}

newInstance()

public static SAXParserFactory newInstance() {
 	return FactoryFinder.find(
            /* The default property name according to the JAXP spec */
            SAXParserFactory.class,
            /* The fallback implementation class name */
            "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
}

getFactory() 返回的是 com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl 实现类。

那么 parser 通过 SAXParserFactoryImplnewSAXParser() 返回:

public SAXParser newSAXParser()
        throws ParserConfigurationException
{
    SAXParser saxParserImpl;
    try {
    	// 调用构造函数创建,会初始化 xmlReader 为 JAXPSAXParser
        saxParserImpl = new SAXParserImpl(this, features, fSecureProcess);
    } catch (SAXException se) {
        // Translate to ParserConfigurationException
        throw new ParserConfigurationException(se.getMessage());
    }
    return saxParserImpl;
}

getXMLReader() 即返回 SAXParserImpl 中的 xmlReader,在构造函数中初始化为 JAXPSAXParser

SAXParserImpl(SAXParserFactoryImpl spf, Map<String, Boolean> features, boolean secureProcessing)
        throws SAXException
{
    fSecurityManager = new XMLSecurityManager(secureProcessing);
    fSecurityPropertyMgr = new XMLSecurityPropertyManager();
    // Instantiate a SAXParser directly and not through SAX so that we use the right ClassLoader
    xmlReader = new JAXPSAXParser(this, fSecurityPropertyMgr, fSecurityManager);
    ...
}
  • parse(input) 解析 server.xml 文件流

这里就是根据定义好的解析规则来解析并返回一个 Catalina,同时根据 server.xml 中的内容生成了 Server 对象(这个解析过程并非主要流程,这里也不展开了,感兴趣的朋友可以自己 Debug 看一下):

在这里插入图片描述

2.2、Serverinit() 方法

在 Tomcat - 从源码阅读中分析核心组件 这篇文章中我们知道,Server 等组件都是有生命周期的,实现了 Lifecycle 接口:

  • LifecycleBase 使用模板设计模式,定义了执行流程,由子类实现具体逻辑
@Override
public finalsynchronized void init() throws LifecycleException {
    ...

    try {
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        // 核心流程,由子类实现具体初始化过程
        initInternal();
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    }
    ...
}

protected abstract void initInternal() throws LifecycleException;
  • 子类 StandardServer 实现 initInternal() 方法:
@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    ...
    // Initialize our defined Services
    for (Service service : services) {
    	// 初始化 Server 的时候,会初始化 Service
        service.init();
    }
}
2.2.1、Serviceinit() 方法
  • 继续查看 Serviceinit() 方法,同样是由实现类 StandardService 实现具体的 initInternal() 模板方法:
@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    // 初始化 service 之前需要先初始化 engine
    if (engine != null) {
        engine.init();
    }

    // Initialize any Executors
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }

    // Initialize mapper listener
    mapperListener.init();

    // Initialize our defined Connectors
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            // 会初始化 endpoint,并绑定好端口
            connector.init();
        }
    }
}
2.2.2、Engineinit() 方法

Engine 中可能会处理一些 Realm

@Override
protected void initInternal() throws LifecycleException {
    // Ensure that a Realm is present before any attempt is made to start
    // one. This will create the default NullRealm if necessary.
    getRealm();
    super.initInternal();
}
2.2.3、Connectorinit() 方法
@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    ...

    try {
        // 协议处理器初始化
        protocolHandler.init();
    }
    ...
}
2.2.4、ProtocolHandlerinit() 方法

ProtocolHandlerinit() 方法由抽象类 AbstractProtocol 实现:

@Override
public void init() throws Exception {
    ...

    String endpointName = getName();
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));
    endpoint.setDomain(domain);

    // 端点初始化
    endpoint.init();
}
2.2.5、AbstractEndpointinit() 方法
public final void init() throws Exception {
	if (bindOnInit) {
        bindWithCleanup();
        bindState = BindState.BOUND_ON_INIT;
    }
    if (this.domain != null) {
        // Register endpoint (as ThreadPool - historical name)
        oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
        Registry.getRegistry(null, null).registerComponent(this, oname, null);

        ObjectName socketPropertiesOname = new ObjectName(domain +
                ":type=SocketProperties,name=\"" + getName() + "\"");
        socketProperties.setObjectName(socketPropertiesOname);
        Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);

        for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
            registerJmx(sslHostConfig);
        }
    }
}
private void bindWithCleanup() throws Exception {
   try {
   		// 由实现类实现
        bind();
    }
    ...
}

public abstract void bind() throws Exception;

bind() 由实现类 NioEndpoint 实现:

@Override
public void bind() throws Exception {
    // 初始化 ServerSocket
    initServerSocket();

    setStopLatch(new CountDownLatch(1));

    // Initialize SSL if needed
    initialiseSsl();
}
protected void initServerSocket() throws Exception {
  	...
    } else {
        // java nio 中的 ServerSocketChannel 打开一个 channel
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
        // 绑定端口,到这里为止还只是初始化 ServerSocket。并没有准备好开始接收请求
        serverSock.bind(addr, getAcceptCount());
    }
    serverSock.configureBlocking(true); //mimic APR behavior
}

到此为止,已经走到了 Server 初始化栈的顶端,表示相关的组件 ServiceEngineConnector 等都已经初始化完了。

同时也是终于执行完了 Catalina 对象的 load() 方法,整个过程还是比较长的,而且涉及到的概念比较多,又使用了设计模式,所以这块可能需要多看几遍才能理解。

3、start() 方法

上一部分中已经完成了 Bootstrap 对象的 load() 方法,同时也初始化好了 Server 对象。在下一篇文章 Tomcat - 启动流程分析 中我们再来分析 启动流程

相关文章:

  • Golang:strings模块常用的字符串操作函数
  • Kibana:使用新的 control 可视化 - 8.3
  • [Servlet 3]会话管理、进阶API、监听过滤器
  • springboot基于协同过滤算法的书籍推荐毕业设计源码101555
  • K-Means聚类算法
  • golang 切片(slice)简单使用
  • SQL Server Reporting Services
  • 加速迈入云原生时代,国产数据库行业要变天
  • PMP每日一练 | 考试不迷路-9.1(包含敏捷+多选)
  • 一体式城市内涝监测站
  • 【高等数学基础进阶】定积分应用
  • RabbitMQ基本使用一
  • CentOS 7.2 正确安装 MySQL 5.6.35
  • 计算机组成与设计-第五章 memory hierarchy(一)
  • 软考高级系统架构设计师系列论文二:论软件的性能优化设计
  • 【个人向】《HTTP图解》阅后小结
  • css属性的继承、初识值、计算值、当前值、应用值
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • egg(89)--egg之redis的发布和订阅
  • Flannel解读
  • Hibernate最全面试题
  • Python中eval与exec的使用及区别
  • rabbitmq延迟消息示例
  • tweak 支持第三方库
  • 爱情 北京女病人
  • 成为一名优秀的Developer的书单
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 提醒我喝水chrome插件开发指南
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • 新版博客前端前瞻
  • Semaphore
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • ​520就是要宠粉,你的心头书我买单
  • #define,static,const,三种常量的区别
  • #QT(智能家居界面-界面切换)
  • (4)Elastix图像配准:3D图像
  • (pojstep1.1.2)2654(直叙式模拟)
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (一) storm的集群安装与配置
  • (转载)Google Chrome调试JS
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • ... 是什么 ?... 有什么用处?
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .NET : 在VS2008中计算代码度量值
  • .net core开源商城系统源码,支持可视化布局小程序
  • .NET delegate 委托 、 Event 事件,接口回调
  • .net6使用Sejil可视化日志
  • .net通用权限框架B/S (三)--MODEL层(2)
  • /usr/bin/perl:bad interpreter:No such file or directory 的解决办法
  • @data注解_SpringBoot 使用WebSocket打造在线聊天室(基于注解)
  • [2013][note]通过石墨烯调谐用于开关、传感的动态可重构Fano超——
  • [BZOJ] 1001: [BeiJing2006]狼抓兔子
  • [CareerCup] 13.1 Print Last K Lines 打印最后K行