tomcat-超详细的启动流程(start)

2020-08-26 17:19:25 浏览数 (1)

一、Bootstrap.start()

(1)tomcat启动时会先调用脚本,在脚本运行时会启动Bootstrap的main方法,mian方法中会调用load方法进行初始化操作,通过责任链模式将所有结点初始化后,会调用start方法-该方法是tomcat启动的核心方法,即启动tomcat的核心线程。

在上一篇 - tomcat-超详细的启动流程(init)中提过这里的daemon实际上为catalina,因此调用catalina的start方法。

main方法中的源码如下:

代码语言:javascript复制
try {
    String command = "start";
    if (args.length > 0) {
        command = args[args.length - 1];
    }

    if (command.equals("startd")) {
        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")) { //都会走进该if分支
        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 var4) {
    Throwable t = var4;
    if (var4 instanceof InvocationTargetException && var4.getCause() != null) {
        t = var4.getCause();
    }

    handleThrowable(t);
    t.printStackTrace();
    System.exit(1);
}

二、Catalina.start()

本质上与init()方法没啥区别,委派给下一个standardServer结点来进行start方法。

代码语言:javascript复制
public void start() {

    if (getServer() == null) {
        load();
    }

    if (getServer() == null) {
        log.fatal(sm.getString("catalina.noServer"));
        return;
    }

    long t1 = System.nanoTime();
    try {        //standardServer的start方法        getServer().start();
    } catch (LifecycleException e) {
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }    //...    //在Bootstrap的main方法中 设置了为true,因此为调用await方法,保证主线程一直处于运行。
    if (await) {
        await();
        stop();
    }
}

这里的start方法也一样,由lifecycleBase来负责统一管理生命周期,并且子类会重新这个start方法,具体源码如下:

代码语言:javascript复制
@Override
public final synchronized void start() throws LifecycleException {
    //...  上面不重要
    try {        //设置状态        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        //该方法由子类重写
        startInternal();
        if (state.equals(LifecycleState.FAILED)) {
            stop();
        } else if (!state.equals(LifecycleState.STARTING)) {
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        } else {
            setStateInternal(LifecycleState.STARTED, null, false);
        }
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.startFail", toString());
    }
}

找到standardServer对应的startInternal方法。

三、StandardServer.startInternal()

在StandardServer.startInternal()中会继续通过责任链模式向后传递,进行StandardService的start方法调用。

代码语言:javascript复制
@Override
protected void startInternal() throws LifecycleException {
//触发监听fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);
    globalNamingResources.start();
    
    //责任链继续向后传递,进行standardService的start方法    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i  ) {
            services[i].start();
        }
}//...
}

四、StandardService.startInternal()

从下图源码中可以看出,StandardService.startInternal(),先进行engine的start调用,再进行connector的start调用。

代码语言:javascript复制
@Override
protected void startInternal() throws LifecycleException {

    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name", this.name));
    setState(LifecycleState.STARTING);
    // engine的start
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }

    //server.xml中无配置executors,所以不会执行这段代码    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    mapperListener.start();
    //connector的start
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            // If it has already failed, don't try and start it
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

(1)StandardEngine.startInternal()

本质上调用了父类ContainerBase.startInternal()。核心源码如下,在代码中主要作用为将tomcat容器以线程池的方式来启动,这个startStopExecutor是在tomcat的init中被构造出来的。

代码语言:javascript复制
@Override
protected synchronized void startInternal() throws LifecycleException {
    // 前面代码我跳过了,不重要
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i  ) {
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }
代码语言:javascript复制
    //...
}

tomcat容器的关系为:Engine——>Host——>Context——>Wrapper——>Servlet。

进入StartChild线程中可以看到线程实现方式为Callable,并且在child中又使用了start()。即Engine——>Host。

代码语言:javascript复制
private static class StartChild implements Callable<Void> {

    private Container child;
    public StartChild(Container child) {
        this.child = child;
    }

    @Override
    public Void call() throws LifecycleException {
        child.start();
        return null;
    }
}

(2)StandardHost.startInternal()

StandardHost.startInternal()中又递归调用了父类ContainerBase.startInternal方法。即下一个线程池需要启动的子类为StandardContext。

代码语言:javascript复制
@Override
protected synchronized void startInternal() throws LifecycleException {    // 前面代码...    super.startInternal();
}

(3)StandardContext.startInternal()

StandardContext.startInternal()这里的触发监听,比较核心,在内部会解析web.xml。

代码语言:javascript复制
protected synchronized void startInternal() throws LifecycleException {
    // 前面代码...    try {             // 前面代码...            // 触发监听 - 解析web.xml
            fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
            // 这里child为Context的子类 Wrapper的start的调用
            for (Container child : findChildren()) {
                if (!child.getState().isAvailable()) {
                    child.start();
                }
}//后面代码...}

触发监听,核心代码:

代码语言:javascript复制
@Override
public void lifecycleEvent(LifecycleEvent event) {
    // 前面代码...
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {        //触发监听的type为这一行        configureStart();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
    } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
        if (originalDocBase != null) {
            context.setDocBase(originalDocBase);
        }
    } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
        configureStop();
    } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        init();
    } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
        destroy();
    }

}
代码语言:javascript复制
protected synchronized void configureStart() {
    //前面代码...
    webConfig();
    // 后面代码...
代码语言:javascript复制
}
代码语言:javascript复制
**
 * Scan the web.xml files that apply to the web application and merge them
 * using the rules defined in the spec. For the global web.xml files,
 * where there is duplicate configuration, the most specific level wins. ie
 * an application's web.xml takes precedence over the host level or global
 * web.xml file.
 *///看英文注释可以知道,这个方法是用来解析tomcat的web.xmlprotected void webConfig() {

在webConfig中也可以找到我们经常会配置的一些解析方法,比如servlet、filter等。

下图为filter的解析。

代码语言:javascript复制
for (FilterDef filter : webxml.getFilters().values()) {
    if (filter.getAsyncSupported() == null) {
        filter.setAsyncSupported("false");
    }
    context.addFilterDef(filter);
}
for (FilterMap filterMap : webxml.getFilterMappings()) {
    context.addFilterMap(filterMap);
}

下图为servlet的解析。

代码语言:javascript复制
for (ServletDef servlet : webxml.getServlets().values()) {
    Wrapper wrapper = context.createWrapper();
    if (servlet.getLoadOnStartup() != null) {
        wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
    }
    if (servlet.getEnabled() != null) {
        wrapper.setEnabled(servlet.getEnabled().booleanValue());
    }
wrapper.setName(servlet.getServletName());    // ...}

下图为listener的解析。

代码语言:javascript复制
for (String listener : webxml.getListeners()) {
    context.addApplicationListener(listener);
}

(4)StandardWrapper.startInternal()

代码语言:javascript复制
protected synchronized void startInternal() throws LifecycleException {

    //本质上也一样 调用父类的startInternal() 线程池启动
代码语言:javascript复制
    super.startInternal();
    
}

这一步为止,tomcat容器(Engine—>Host—>Context—>Wrapper)启动完毕。

五、mapperListern.startInternal()

回到StandardService,会进行mapperListern的启动。找到子类重写的startInernal(),源码如下:

代码语言:javascript复制
@Override
public void startInternal() throws LifecycleException {

setState(LifecycleState.STARTING);
    // 获取engine    Engine engine = service.getContainer();
    if (engine == null) {
        return;
    }

findDefaultHost();
    // 添加engine中的listerner    addListeners(engine);
    Container[] conHosts = engine.findChildren();
    for (Container conHost : conHosts) {
        Host host = (Host) conHost;
        if (!LifecycleState.NEW.equals(host.getState())) {
            //遍历host-注册host
            registerHost(host);
        }
    }
}
代码语言:javascript复制
private void registerHost(Host host) {

    String[] aliases = host.findAliases();
    mapper.addHost(host.getName(), aliases, host);
    for (Container container : host.findChildren()) {
if (container.getState().isAvailable()) {//注册host下的contextregisterContext((Context) container);
        }
    }
}
代码语言:javascript复制
private void registerContext(Context context) {

    String contextPath = context.getPath();
    if ("/".equals(contextPath)) {
        contextPath = "";
    }
    Host host = (Host)context.getParent();
    WebResourceRoot resources = context.getResources();
    String[] welcomeFiles = context.findWelcomeFiles();
    List<WrapperMappingInfo> wrappers = new ArrayList<>();
    for (Container container : context.findChildren()) {        //遍历contxt-注册wrapper        prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);
        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerWrapper",
                    container.getName(), contextPath, service));
        }
    }
    //最后将host名 host端口 context wrapper即servlet 添加到mapper中。
    mapper.addContextVersion(host.getName(), host, contextPath,
            context.getWebappVersion(), context, welcomeFiles, resources,
            wrappers);
    if(log.isDebugEnabled()) {
        log.debug(sm.getString("mapperListener.registerContext",
                contextPath, service));
    }
}

在mapper中会注册engine、host、context、wrapper,组成host名 host端口 context路径 context应用名 servlet添加到mapper中,因此当接受到http请求时,如果tomcat有多个工程,多个host情况下,可以根据url拆分然后然后根据mapper来进行匹配。

六、Connector.startInternal()

责任链模式委托protocolHandler.start(),实际上未NioEndpoint.startInternal()

代码语言:javascript复制
protected void startInternal() throws LifecycleException {
    if (getPortWithOffset() < 0) {
        throw new LifecycleException(sm.getString(
                "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
    }

    setState(LifecycleState.STARTING);
    try {        //protocolHandler的启动        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}

七、NioEndpoint.startInternal()

(1)在NioEndpoint.startInternal()中会创建工作线程池、clientPoller线程池、acceptor线程池。

代码语言:javascript复制
public void startInternal() throws Exception {

    if (!running) {
        //...
        if ( getExecutor() == null ) {            //启动工作线程,            createExecutor();
        }

        initializeConnectionLatch();
        
        pollers = new Poller[getPollerThreadCount()];
        for (int i=0; i<pollers.length; i  ) {
            pollers[i] = new Poller();
            Thread pollerThread       //启动clientPoller线程
       = new Thread(pollers[i], getName()   "-ClientPoller-" i);
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
        }
        //创建acceptor线程
        startAcceptorThreads();
    }
}

最小线程数量默认为10,最大线程数量默认为200,也可读取server.xml中配置的线程数量。

代码语言:javascript复制
public void createExecutor() {
    internalExecutor = true;
    TaskQueue taskqueue = new TaskQueue();
    TaskThreadFactory tf = new TaskThreadFactory(getName()   "-exec-", daemon, getThreadPriority());
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
    taskqueue.setParent( (ThreadPoolExecutor) executor);
}

(2)acceptor线程

在下面方法中会启动,acceptor线程,并创建acceptor对象。

代码语言:javascript复制
protected void startAcceptorThreads() {
    int count = getAcceptorThreadCount();
    acceptors = new ArrayList<>(count);
    for (int i = 0; i < count; i  ) {
        Acceptor<U> acceptor = new Acceptor<>(this);
        String threadName = getName()   "-Acceptor-"   i;
        acceptor.setThreadName(threadName);
        acceptors.add(acceptor);
        Thread t = new Thread(acceptor, threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start();
    }
}

启动线程后,查询acceptor线程的run方法,发现acceptor中会建立socket连接,可以接收http请求。

代码语言:javascript复制
@Override
public void run() {
    while (endpoint.isRunning()) {

        try {
            U socket = null;
            try {
                //接收socket
                socket = endpoint.serverSocketAccept();
            } catch (Exception ioe) {
               //...
            }
           
        } catch (Throwable t) {
           //...//...//...
代码语言:javascript复制
        }
    }
}

到这一步tomcat完全启动成功,并且开启了对应的线程,可以接受http请求,处理http请求。

八、总结

0 人点赞