揭开tomcat神秘的面纱之bootstrap启动1

在上文揭开tomcat神秘的面纱之bootstrap加载中,本菜鸟分析了bootstrap最终会经过初始化,加载,启动三个步骤。接着来分析启动过程。

tomcat的启动过程.png

bootstrapmain方法中,加载完成之后就会调用catalinastart方法,启动容器,而在catalinastart方法中,实质是调用serverstart方法。

//Bootstrap.java
public void start()
    throws Exception {
    if( catalinaDaemon==null ) init();
    Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
    method.invoke(catalinaDaemon, (Object [])null);//catalina的start方法。
}   
Catalina.java
 public void start() {
    if (getServer() == null) {
        load();
    }

    if (getServer() == null) {
        return;
    }
    getServer().start();//拿到的是StandardServer
}

而在StandardServerstart方法中,实质是调用StandServicestart方法,最终调用enginestart方法。

//StandardServer.java
protected void startInternal() throws LifecycleException {
    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);
    globalNamingResources.start();
    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
            services[i].start();//这里调用StandService的start方法。
        }
    }
}
//StandardService.java
@Override
protected void startInternal() throws LifecycleException {

    setState(LifecycleState.STARTING);
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }
    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }
    mapperListener.start();
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

关于这里为啥不是start方法,而是startInternal,这是因为他们共同继承于LifecycleBase类,该类中,会调用startInternal方法,调用start方法,实质最终都会调用startInternal

//LifecycleBase.java
public final synchronized void start() throws LifecycleException {
    if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
            LifecycleState.STARTED.equals(state)) {
        return;
    }
    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)) {
        stop();
    } else if (!state.equals(LifecycleState.INITIALIZED) &&
            !state.equals(LifecycleState.STOPPED)) {
        invalidTransition(Lifecycle.BEFORE_START_EVENT);
    }

    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);
    }
   
}

而在StandardEnginestart方法中,实质是通过多线程,创建StartChild方法,启动StandardHost,

//ContainerBase.java
 protected synchronized void startInternal() throws LifecycleException {

    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    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])));
        //这里提交任务,就是启动StandardHost
    }
    MultiThrowable multiThrowable = null;
    for (Future<Void> result : results) {
            result.get();
    }
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }
    setState(LifecycleState.STARTING);
    threadStart();
}

而在StartChild方法中,只是调用子容器的start方法。此处为StandardHost

StartChild.java
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;
    }
}

而在StandardHost调用start方法的时候,会广播一个start事件,最终会被HostConfig接收,并处理,调用HostConfigstart方法,最终会将webapp文件夹下的每一个war,或者文件夹,单独作为一个任务DeployWar,提交到线程池,去启动这个Context,而DeployWar这个任务,实质还是调用HostConfigdeployWAR方法,这里本菜鸟就以一个war包为例,来介绍一下。

  public void start() {
    ObjectName hostON = host.getObjectName();
    oname = new ObjectName
        (hostON.getDomain() + ":type=Deployer,host=" + host.getName());
    Registry.getRegistry(null, null).registerComponent
        (this, oname, this.getClass().getName());
    if (!host.getAppBaseFile().isDirectory()) {
        host.setDeployOnStartup(false);
        host.setAutoDeploy(false);
    }
    if (host.getDeployOnStartup())
        deployApps();//发布app
}

 protected void deployApps() {
    File appBase = host.getAppBaseFile();
    File configBase = host.getConfigBaseFile();
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // Deploy XML descriptors from configBase
    deployDescriptors(configBase, configBase.list());//发布描述配置信息中的服务
    // Deploy WARs
    deployWARs(appBase, filteredAppPaths);//发布war包服务
    // Deploy expanded folders
    deployDirectories(appBase, filteredAppPaths);//发布文件夹的包服务
}

protected void deployWARs(File appBase, String[] files) {
    ExecutorService es = host.getStartStopExecutor();
    List<Future<?>> results = new ArrayList<>();
    for (int i = 0; i < files.length; i++) {
        if (files[i].equalsIgnoreCase("META-INF"))
            continue;
        if (files[i].equalsIgnoreCase("WEB-INF"))
            continue;
        File war = new File(appBase, files[i]);
        if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") &&
                war.isFile() && !invalidWars.contains(files[i]) ) {
            ContextName cn = new ContextName(files[i], true);
            if (isServiced(cn.getName())) {
                continue;
            }
            if (deploymentExists(cn.getName())) {
                DeployedApplication app = deployed.get(cn.getName());
                boolean unpackWAR = unpackWARs;
                if (unpackWAR && host.findChild(cn.getName()) instanceof StandardContext) {
                    unpackWAR = ((StandardContext) host.findChild(cn.getName())).getUnpackWAR();
                }
                if (!unpackWAR && app != null) {
                    File dir = new File(appBase, cn.getBaseName());
                    if (dir.exists()) {
                            app.loggedDirWarning = true;
                    } else {
                        app.loggedDirWarning = false;
                    }
                }
                continue;
            }

            if (!validateContextPath(appBase, cn.getBaseName())) {
                invalidWars.add(files[i]);
                continue;
            }
            results.add(es.submit(new DeployWar(this, cn, war)));
            //重点在这里,提交一个DeployWar任务。
        }
    }
    for (Future<?> result : results) {
            result.get();
    }
}

private static class DeployWar implements Runnable {
    private HostConfig config;
    private ContextName cn;
    private File war;
    public DeployWar(HostConfig config, ContextName cn, File war) {
        this.config = config;
        this.cn = cn;
        this.war = war;
    }
    @Override
    public void run() {
        config.deployWAR(cn, war);
        //HostConfig的deployWAR方法。
    }
}

而在HostConfigdeployWAR方法中,会创建Context,然后将该Context添加到Host过程中(addChild方法),会启动Context


protected void deployWAR(ContextName cn, File war) {

    File xml = new File(host.getAppBaseFile(),
            cn.getBaseName() + "/" + Constants.ApplicationContextXml);

    File warTracker = new File(host.getAppBaseFile(), cn.getBaseName() + Constants.WarTracker);

    boolean xmlInWar = false;
    try (JarFile jar = new JarFile(war)) {
        JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml);
        if (entry != null) {
            xmlInWar = true;
        }
    } catch (IOException e) {
        /* Ignore */
    }

   
    boolean useXml = false;
    if (xml.exists() && unpackWARs &&
            (!warTracker.exists() || warTracker.lastModified() == war.lastModified())) {
        useXml = true;
    }

    Context context = null;
    //初始化Context
    boolean deployThisXML = isDeployThisXML(war, cn);
    if (deployThisXML && useXml && !copyXML) {
        synchronized (digesterLock) {
            context = (Context) digester.parse(xml);
           
        }
        context.setConfigFile(xml.toURI().toURL());
    } else if (deployThisXML && xmlInWar) {
        synchronized (digesterLock) {
            try (JarFile jar = new JarFile(war)) {
                JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml);
                context = (Context) digester.parse(istream);
        }
    } else if (!deployThisXML && xmlInWar) {
    } else {
        context = (Context) Class.forName(contextClass).getConstructor().newInstance();
    }
    
    boolean copyThisXml = false;
    if (deployThisXML) {
        if (host instanceof StandardHost) {
            copyThisXml = ((StandardHost) host).isCopyXML();
        }
        if (!copyThisXml && context instanceof StandardContext) {
            copyThisXml = ((StandardContext) context).getCopyXML();
        }

        if (xmlInWar && copyThisXml) {
            xml = new File(host.getConfigBaseFile(),
                    cn.getBaseName() + ".xml");
            try (JarFile jar = new JarFile(war)) {
                JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml);
                try (InputStream istream = jar.getInputStream(entry);
                        FileOutputStream fos = new FileOutputStream(xml);
                        BufferedOutputStream ostream = new BufferedOutputStream(fos, 1024)) {
                    byte buffer[] = new byte[1024];
                    while (true) {
                        int n = istream.read(buffer);
                        if (n < 0) {
                            break;
                        }
                        ostream.write(buffer, 0, n);
                    }
                    ostream.flush();
                }
            } catch (IOException e) {
            }
        }
    }

    DeployedApplication deployedApp = new DeployedApplication(cn.getName(),
            xml.exists() && deployThisXML && copyThisXml);

    long startTime = 0;
    try{
        deployedApp.redeployResources.put
            (war.getAbsolutePath(), Long.valueOf(war.lastModified()));
        if (deployThisXML && xml.exists() && copyThisXml) {
            deployedApp.redeployResources.put(xml.getAbsolutePath(),
                    Long.valueOf(xml.lastModified()));
        } else {
            deployedApp.redeployResources.put(
                    (new File(host.getConfigBaseFile(),
                            cn.getBaseName() + ".xml")).getAbsolutePath(),
                    Long.valueOf(0));
        }
  
        Class<?> clazz = Class.forName(host.getConfigClass());
        LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
        context.addLifecycleListener(listener);
        context.setName(cn.getName());
        context.setPath(cn.getPath());
        context.setWebappVersion(cn.getVersion());
        context.setDocBase(cn.getBaseName() + ".war");
        host.addChild(context);//添加子Context
    } catch (Throwable t) {
       
    } finally {
        boolean unpackWAR = unpackWARs;
        if (unpackWAR && context instanceof StandardContext) {
            unpackWAR = ((StandardContext) context).getUnpackWAR();
        }
        if (unpackWAR && context.getDocBase() != null) {
            File docBase = new File(host.getAppBaseFile(), cn.getBaseName());
          deployedApp.redeployResources.put(docBase.getAbsolutePath(),
                    Long.valueOf(docBase.lastModified()));
            addWatchedResources(deployedApp, docBase.getAbsolutePath(),
                    context);
            if (deployThisXML && !copyThisXml && (xmlInWar || xml.exists())) {
                deployedApp.redeployResources.put(xml.getAbsolutePath(),
                        Long.valueOf(xml.lastModified()));
            }
        } else {
            addWatchedResources(deployedApp, null, context);
        }
        addGlobalRedeployResources(deployedApp);
    }
    deployed.put(cn.getName(), deployedApp);
}

   private void addChildInternal(Container child) {

        if( log.isDebugEnabled() )
            log.debug("Add child " + child + " " + this);
        synchronized(children) {
            if (children.get(child.getName()) != null)
                throw new IllegalArgumentException("addChild:  Child name '" +
                                                   child.getName() +
                                                   "' is not unique");
            child.setParent(this);  // May throw IAE
            children.put(child.getName(), child);
        }

        // Start child
        // Don't do this inside sync block - start can be a slow process and
        // locking the children object can cause problems elsewhere
        try {
            if ((getState().isAvailable() ||
                    LifecycleState.STARTING_PREP.equals(getState())) &&
                    startChildren) {
                child.start();//启动Context
            }
        } catch (LifecycleException e) {
            log.error("ContainerBase.addChild: start: ", e);
            throw new IllegalStateException("ContainerBase.addChild: start: " + e);
        } finally {
            fireContainerEvent(ADD_CHILD_EVENT, child);
        }
    }
//ContainerBase.java 此处为StandardHost.addChild方法
//addChild
private void addChildInternal(Container child) {
    synchronized(children) {
        child.setParent(this);  
        children.put(child.getName(), child);
    }
    try {
        if ((getState().isAvailable() ||
                LifecycleState.STARTING_PREP.equals(getState())) &&
                startChildren) {
            child.start();//启动context
        }
    } finally {
        fireContainerEvent(ADD_CHILD_EVENT, child);
    }
}

关于Context初始化的内容,后面继续更新。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容