Tomcat源码剖析|多图分析Tomcat启动时的初始化流程

Tomcat源码剖析——初始化

本文解析源码来自于Tomcat8.5.33

本文引用参考文献为《Tomcat架构解析-刘光瑞》

注:此文为连载文章,可以参考前序文章《类加载器》,以及后续文章《启动》


组件介绍

在这里插入图片描述

Lifecycle

声明周期的通用接口,所有Tomcat的组件均实现了该接口;

在这里插入图片描述

Server

表示整个Servlet容器,在Tomcat运行环境中只有唯一一个Server实例;

在这里插入图片描述

Service

表示一个或多个Connector集合,这些Connector共享同一个Container来处理其请求。在同一个Tomcat实例中可以存在任意个Service实例并保持彼此独立;

从Tomcat架构图上可以看出,Service主要包含两部分: Connector和Container;

  • Connector用于处理连接相关的事情,并提供Socket与Request请求和Response响应相关的转化;
  • Container用于封装和管理Servlet,以及具体处理Request请求;

在这里插入图片描述

Container

表示能够执行客户端请求并返回响应的一类对象。在Tomcat中存在以下容器:Engine、Host、C哦那text、Wrapper;

在这里插入图片描述

Engine

容器。整个Servelt的引擎。用来管理多个站点,一个Service最多只能有一个Engine; 其为Tomcat中最高级别的容器对象、尽管Engine不是直接处理请求的,却是获取目标容器的入口;

在这里插入图片描述

Host

容器。 代表一个站点,也可以叫虚拟主机 ,与服务器的域名等地址有关,客户端可以使用该域名连接服务器,这个名称必须在DNS服务器注册;

在这里插入图片描述

Context

容器。 代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件,用于表示ServletContext;一个ServletContext代表一个独立的Web应用;

在这里插入图片描述

在这里插入图片描述

Wrapper

容器。 每一Wrapper封装着一个Servlet,表示Web中定义的Servlet,最底层的容器,用于处理请求数据。

在这里插入图片描述

Executor

表示组件间共享的线程池;

在这里插入图片描述

源码分析

流程图

在这里插入图片描述

代码剖析
Bootstrap静态代码块
// Bootstrap.class
static {
    // ...
    // 设置安装目录和工作目录-未设置的话与安装目录一致
    // 就是当前项目目录(eg: D:\person_project\tomcat8.5)
    System.setProperty(
        Constants.CATALINA_HOME_PROP, catalinaHomeFile.getPath());
    System.setProperty(
        Constants.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
}
1. Bootstrap.main()

bootstrap去调用初始化方法;见方法2

然后bootstrap调用load方法;见方法3

public static void main(String args[]) {
    synchronized (daemonLock) {
        // 初始化类加载器,设置线程上下文加载器并用catalinaLoader实例化catalina
        bootstrap.init();
        // 守护对象
        daemon = bootstrap;
        // ....
    }
    try {
        // ....
        if (command.equals("startd")) {
            args[args.length - 1] = "start";
            daemon.load(args);
            daemon.start();
        }
        // ....
    } catch(e){}
}
2. Bootstrap.init()
  1. 初始化类加载器(commonLoader, catalinaLoader,sharedLoader ):这里见类加载器的文章;
  2. 设置线程上下文类加载器——catalinaLoader;
  3. 通过类加载器创建Catalina;
  4. 并通过反射调用Catalina的 setParentClassLoader方法;
public void init() throws Exception {

    // 初始化类加载器
    // commonLoader: 加载catalina.properties配置文件中common.loader=>加载工作/安装目录下的lib文件
    // catalinaLoader: server.loader,默认为空,因此是commonLoader
    // sharedLoader: shared.loader,默认为空,因此是commonLoader
    initClassLoaders();

    // 设置线程上下文类加载器——catalinaLoader
    Thread.currentThread().setContextClassLoader(catalinaLoader);
    // 安全策略,权限相关
    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.getConstructor().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;
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    method.invoke(startupInstance, paramValues);
    // 就是Catalina
    catalinaDaemon = startupInstance;
}
3.Bootstrap.load()

因为引导类和Tomcat实例是解耦的,这里是bootstrap通过反射调用catalina的load()方法;见方法4

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

    // 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);
}
4. Catalina.load()

通过Digester去解析conf/server.xml文件;初始化Server的工作目录和安装目录,并调用server的init()方法。见方法5

// Catalina.class
public void load() {
    // 空方法
    initDirs();

    // Before digester - it may be needed
    // 初始化一些属性
    initNaming();

    // 创建解析器Digester-server.xml created
    // Create and execute our Digester
    Digester digester = createStartDigester();
    // standardSever初始化环境
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // Stream redirection
    initStreams();
    getServer().init();
}
5. StandardServer#LifecycleBase.init()

我们看,上一步 getServer().init();实际上调用的是StandardServer的生命周期LifecycleBase的init方法,这里使用模板设计模式;

我们通过父类的init()方法设置该组件的加载事件,具体启动交由子类去实现initInternal()方法。见方法6

// LifecycleBase.class
@Override
public final synchronized void init() throws LifecycleException {
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        initInternal();
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.initFail", toString());
    }
}
6. StandardServer.initInternal()

这里就是初始化环境,加载公共jar包,然后启动Service,见方法7

// StandardServer.class
@Override
protected void initInternal() throws LifecycleException {
    // 注册MBean
	super.initInternal();
    // Register global String cache
    // Note although the cache is global, if there are multiple Servers
    // present in the JVM (may happen when embedding) then the same cache
    // will be registered under multiple names
    onameStringCache = register(new StringCache(), "type=StringCache");

    // Register the MBeanFactory
    MBeanFactory factory = new MBeanFactory();
    factory.setContainer(this);
    onameMBeanFactory = register(factory, "type=MBeanFactory");

    // Register the naming resources
    // 初始化命名资源 GlobalNamingResources
    globalNamingResources.init();

    // Populate the extension validator with JARs from common and shared
    // 将lib下的公共jar包加载到系统资源中
    if (getCatalina() != null) {
        // ...
    }
    // Initialize our defined Services
    for (Service service : services) {
        // 启动Service
        service.init();
    }
}
7. StandardService.initInternal()

这块我就省略LifecycleBase.init()了,毕竟都是一样的逻辑。

Service这里会初始化引擎(见方法8),线程池(见方法9),以及连接器(见方法10);

// StandardService.class
protected void initInternal() throws LifecycleException {
    // 注册MBean
    super.initInternal();
    // 加载引擎
    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) {
            try {
                connector.init();
            } catch (Exception e) {
              // ...
            }
        }
    }
}
8. StandardEngine.initInternal()

引擎初始化只是做了配置安全模块和调用父类容器ContainerBase.class,去创建单个线程的线程池,最大存活时间10s;

// StandardEngine.class
@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.
    // 处理获取Realm模块(安全配置)
    getRealm();
    // 创建了一个启动终止线程池
    super.initInternal();
}

// StandardEngine#ContainerBase.class
@Override
protected void initInternal() throws LifecycleException {
    BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
    // 创建单个线程的线程池,最大存活时间10s,可超时
    startStopExecutor = new ThreadPoolExecutor(
        getStartStopThreadsInternal(),
        getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
        startStopQueue,
        new StartStopThreadFactory(getName() + "-startStop-"));
    startStopExecutor.allowCoreThreadTimeOut(true);
    super.initInternal();
}

9. StandardThreadExecutor.initInternal()

这里就是单纯的注册了MBean;

@Override
protected void initInternal() throws LifecycleException {
    // 注册MBean
    super.initInternal();
}

10. Connector.initInternal()

连接器中初始化了protocolHandler;那么我们在server.xml配置连接器的时候,是如下配置的:

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

因此,这里的protocolHandler实际上就是HTTP1.1对应Http11NioProtocol.class见方法11;

// Connector.class
protected final ProtocolHandler protocolHandler;
protected void initInternal() throws LifecycleException {

    super.initInternal();

    // Initialize adapter
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);
    // ......
    try {
        protocolHandler.init();
    } catch (Exception e) {
    }
}

11. Http11NioProtocol.init()

我们先看一下Http11NioProtocol.class他的继承关系;

在这里插入图片描述

这里实际上调用的是父类AbstractHttp11Protocol.init();其中又调用了它自身的父类AbstractProtocol.init();

在AbstractProtocol的init()方法中,我们创建了端点监听器endpoint,并对其初始化,见方法12

// AbstractHttp11Protocol.class
@Override
public void init() throws Exception {
    // Upgrade protocols have to be configured first since the endpoint
    // init (triggered via super.init() below) uses this list to configure
    // the list of ALPN protocols to advertise
    for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
        configureUpgradeProtocol(upgradeProtocol);
    }
    // 调用父类AbstractProtocol.init()
    super.init();

    // Set the Http11Protocol (i.e. this) for any upgrade protocols once
    // this has completed initialisation as the upgrade protocols may expect this
    // to be initialised when the call is made
    for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
        if (upgradeProtocol instanceof Http2Protocol) {
            ((Http2Protocol) upgradeProtocol).setHttp11Protocol(this);
        }
    }
}
// AbstractProtocol.class
@Override
public void init() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
    }

    if (oname == null) {
        // Component not pre-registered so register it
        oname = createObjectName();
        if (oname != null) {
            Registry.getRegistry(null, null).registerComponent(this, oname, null);
        }
    }

    if (this.domain != null) {
        ObjectName rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
        this.rgOname = rgOname;
        Registry.getRegistry(null, null).registerComponent(
            getHandler().getGlobal(), rgOname, null);
    }
    // 创建端点监听器 AbstractEndpoint endpoint
    String endpointName = getName();
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));
    endpoint.setDomain(domain);

    endpoint.init();
}

12. NioEndpoint.init()

我们来分析一下NioEndpoint:

在这里插入图片描述

这里面我们调用的是父类AbstractEndpoint.init();而其中bind()方法是交由NioEndpoint去实现的;

其就是创建socket去等待连接;

// AbstractEndpoint.class
public void init() throws Exception {
    if (bindOnInit) {
        // 交由子类实现
        bind();
        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);
        }
    }
}

// NioEndpoint.class
@Override
public void bind() throws Exception {

    if (!getUseInheritedChannel()) {
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        serverSock.socket().bind(addr,getAcceptCount());
    } else {
        // Retrieve the channel provided by the OS
        Channel ic = System.inheritedChannel();
        if (ic instanceof ServerSocketChannel) {
            serverSock = (ServerSocketChannel) ic;
        }
        if (serverSock == null) {
            throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
        }
    }
    serverSock.configureBlocking(true); //mimic APR behavior

    // Initialize thread count defaults for acceptor, poller
    if (acceptorThreadCount == 0) {
        // FIXME: Doesn't seem to work that well with multiple accept threads
        acceptorThreadCount = 1;
    }
    if (pollerThreadCount <= 0) {
        //minimum one poller thread
        pollerThreadCount = 1;
    }
    setStopLatch(new CountDownLatch(pollerThreadCount));

    // Initialize SSL if needed
    initialiseSsl();

    selectorPool.open();
}

总结

Connector的运行模式
  1. BIO:同步阻塞。一个线程处理一个请求,当请求遇到高峰时,线程数增加导致资源的浪费。8.5后已移除BIO,若安装了APR库,则使用Http11AprProtocol,否则使用的Http11NioProtocol;

    主要步骤:

    1. 服务端监听某个端口是否由链接请求;
    2. 客户端向服务器发出链接请求;
    3. 服务端向客户端返回Accept(接收)消息,此时链接成功;
    4. 客户端和服务器通过Send(),Write()等方法与对方通信;
    5. 关闭链接;
    <Connector protocol="HTTP/1.1" />
    
    
  2. NIO/NIO2:同步非阻塞。通过多路复用,使用少量线程处理大量请求;NIO2可支持异步IO(AIO);

    <Connector protocol="org.apache.coyote.http11.Http11NioProtocol" />
    
    
  3. APR: 即Apache Portable Runtime(AIO异步非阻塞)。从操作系统层面解决IO阻塞问题; 需在本地服务器安装APR库 ;

    <Connector protocol="org.apache.coyote.http11.Http11AprProtocol" />
    
    
Tomcat的4种部署方式
  1. 自动部署

    将web应用放置webapps目录。Tomcat启动时会加载目录下的应用,并将编译后的数据放入工作目录下;

  2. 使用Manager App控制台部署

    进入Tomcat主页去指定一个Web应用的路径或war包;

  3. 修改配置文件

    修改conf/server.xml文件;

  4. 增加自定义的Web部署文件

    通过一个独立的Context描述文件来配置并启动Web,其独立的文件由Host的xmlBase属性指定。默认是在$ CATALINA_BASE/conf/<Engine名称>/<Host名称>;即默认是在$ CATALINA_BASE/conf/Catalina/localhost目录下增加 custom.xml文件,内容是Context节点,可以部署应用;

Tomcat如何创建Servlet类实例?

我们在源码分析的时候,看到方法4Catalina.load()方法时,里面有个关键性的地方:

 Digester digester = createStartDigester();

我们通过server.xml配置的数据,都是由Digester进行解析处理的。这里我简单介绍一下:

Digester是一款用于将XML转为Java对象的事件驱动型工具,是对SAX的封装。其隐藏了XML节点解析的具体层级细节,他通过流读取XML文件,当识别出特定的XML节点后会做出特定的动作,或者创建Java对象等,其核心是匹配模式和处理规则。Digester是非线程安全的。

Tomcat工作模式

作为Servlet容器有三种工作模式:

  1. 独立的Servlet容器,是Web服务器的一部分;
  2. 进程内的servlet容器,是作为web服务器的插件和Java容器的实现。web服务器插件在内部地址空间打开一个JVM使得java容器在内部运行。反应快但扩展不足;
  3. 进程外的servlet容器,运行于web服务器之外的内存中,并作为web服务器插件和java容器的结合。反应不如进程内,但稳定性更优;

作为请求可以分为两种工作模式:

  1. 作为应用程序服务器:请求来自于前端的Web服务器,例如IIS,Nginx;
  2. 作为独立的服务器:请求来自于web浏览器;
  • 19
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 30
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BugGuys

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值