当前位置: 首页 > 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(一)
  • 软考高级系统架构设计师系列论文二:论软件的性能优化设计
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • extjs4学习之配置
  • HTML-表单
  • Java面向对象及其三大特征
  • laravel5.5 视图共享数据
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • MySQL主从复制读写分离及奇怪的问题
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • October CMS - 快速入门 9 Images And Galleries
  • Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比...
  • Twitter赢在开放,三年创造奇迹
  • vuex 笔记整理
  • win10下安装mysql5.7
  • 包装类对象
  • 闭包--闭包之tab栏切换(四)
  • 番外篇1:在Windows环境下安装JDK
  • 思考 CSS 架构
  • 通过几道题目学习二叉搜索树
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • (动态规划)5. 最长回文子串 java解决
  • (算法)N皇后问题
  • (转)关于pipe()的详细解析
  • .NET Core引入性能分析引导优化
  • .net refrector
  • .net 程序 换成 java,NET程序员如何转行为J2EE之java基础上(9)
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .NET开发者必备的11款免费工具
  • .net中应用SQL缓存(实例使用)
  • .net专家(张羿专栏)
  • @ModelAttribute 注解
  • [C和指针].(美)Kenneth.A.Reek(ED2000.COM)pdf
  • [HJ56 完全数计算]
  • [IDF]啥?
  • [JS入门到进阶] 哎,被vite小坑了一波,大家记得配置build.cssTarget为‘chrome61‘
  • [MySQL光速入门]003 留点作业...
  • [Poetize6] IncDec Sequence
  • [POJ - 2386]
  • [SDOI2017]数字表格
  • [UnityEditor基础]脚本自动定位选择Hierarchy或Project下的对象
  • [翻译]Gallery Server Pro ----用于分享相片,视频,音频及其他媒体的ASP.NET相册[Carol]...