回到StandardService的start方法,我们已经分析了engine及其子容器的启动,现在我们继续往下看看其他组件的启动

protected void org.apache.catalina.core.StandardService.startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        setState(LifecycleState.STARTING);

        // Start our defined Container first
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }
        //线程池的启动
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }
        //映射监听的启动
        mapperListener.start();

        // Start our defined Connectors second
        //连接的启动
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                try {
                    // If it has already failed, don't try and start it
                    if (connector.getState() != LifecycleState.FAILED) {
                        connector.start();
                    }
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
        }
    }

在研究其他组件的启动时,我们别漏了Engine还有一个后台的线程操作(也是每个容器都有的操作),这个操作主要是为了进行资源的监控,如果发生变化就进行
reload,也就是context的重启

protected void org.apache.catalina.core.ContainerBase.threadStart() {

        if (thread != null)
            return;
        if (backgroundProcessorDelay <= 0)
            return;

        threadDone = false;
        String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
        //容器后台操作进程
        thread = new Thread(new ContainerBackgroundProcessor(), threadName);
        thread.setDaemon(true);
        thread.start();

    }

在ContainerBase的backgroundProcess方法中会触发一个periodic事件,这个事件被HostConfig捕获

 protected void check() {
        //判断是否是自动部署
        if (host.getAutoDeploy()) {
            // Check for resources modification to trigger redeployment
            //获取所有部署的应用
            DeployedApplication[] apps =
                deployed.values().toArray(new DeployedApplication[0]);
            //检查资源是否被更改
            for (int i = 0; i < apps.length; i++) {
                if (!isServiced(apps[i].name))
                    checkResources(apps[i], false);
            }

            // Check for old versions of applications that can now be undeployed
            //检查不被重新部署的老版本
            if (host.getUndeployOldVersions()) {
                checkUndeploy();
            }

            // Hotdeploy applications
            //热部署
            deployApps();
        }
    }

检查资源是否发生变更

//app:维护这某个context需要进行监控的资源,skipFileModificationResolutionCheck:是否跳过资源修改时间分辨率的检查
protected synchronized void org.apache.catalina.startup.HostConfig.checkResources(DeployedApplication app,
            boolean skipFileModificationResolutionCheck) {
        //获取需要需要重新部署的资源
        String[] resources =
            app.redeployResources.keySet().toArray(new String[0]);
        // Offset the current time by the resolution of File.lastModified()
        //计算当前时间与FILE_MODIFICATION_RESOLUTION_MS(1000ms)的差值,也就是提前1000ms
        //这主要是因为File的lastModified()方法的分辨率是1s,所以在1s内发生的更改,其时间会直接加一秒
        //所以这里会进行偏移
        long currentTimeWithResolutionOffset =
                System.currentTimeMillis() - FILE_MODIFICATION_RESOLUTION_MS;
        for (int i = 0; i < resources.length; i++) {
            File resource = new File(resources[i]);
            if (log.isDebugEnabled())
                log.debug("Checking context[" + app.name +
                        "] redeploy resource " + resource);
            //获取发布应用时文件的修改时间
            long lastModified =
                    app.redeployResources.get(resources[i]).longValue();
            //如果资源存在或者这个资源不存在,但是修改时间是零(比如那些可能一开始不存在,但是后面可能会动态加进来的资源)
            if (resource.exists() || lastModified == 0) {
                // 如果上次修改时间不相等,假设某个资源在部署的时间为12:00:00,然后在12:00:01之内做了更改
                //那么这个资源的修改时间变更为12:00:01,但是此时真实时间还未到12:00:01,那么这里不就丢失了更新吗???
                //直到当前时间超过12:00:01时会发生更新。那么存储到重部署资源map的时间是12:00:01,表示一个过去时间(要不然直接部署就成了未来时间了)
                //其实这里没有看懂???
                if (resource.lastModified() != lastModified && (!host.getAutoDeploy() ||
                        resource.lastModified() < currentTimeWithResolutionOffset ||
                        skipFileModificationResolutionCheck)) {
                    //如果资源是一个目录
                    if (resource.isDirectory()) {
                        // No action required for modified directory
                        //重新设置修改时间
                        app.redeployResources.put(resources[i],
                                Long.valueOf(resource.lastModified()));
                    //当前资源是否有描述文件,比如context.xml,并且是否是war包
                    } else if (app.hasDescriptor &&
                            resource.getName().toLowerCase(
                                    Locale.ENGLISH).endsWith(".war")) {
                        // 找到对应的context对象
                        Context context = (Context) host.findChild(app.name);
                        String docBase = context.getDocBase();
                        if (!docBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) {
                            // This is an expanded directory
                            File docBaseFile = new File(docBase);
                            if (!docBaseFile.isAbsolute()) {
                                docBaseFile = new File(host.getAppBaseFile(),
                                        docBase);
                            }
                            //重新启动
                            reload(app, docBaseFile, resource.getAbsolutePath());
                        } else {
                            //重新启动
                            reload(app, null, null);
                        }
                        // Update times
                        //更新资源修改时间
                        app.redeployResources.put(resources[i],
                                Long.valueOf(resource.lastModified()));
                        app.timestamp = System.currentTimeMillis();
                        boolean unpackWAR = unpackWARs;
                        if (unpackWAR && context instanceof StandardContext) {
                            unpackWAR = ((StandardContext) context).getUnpackWAR();
                        }
                        //如果需要解压,那么当然需要监控解压后的资源,以docBase作为非绝对路径的前缀
                        if (unpackWAR) {
                            addWatchedResources(app, context.getDocBase(), context);
                        } else {
                            //如果不需要解压,那么没有docBase,直接监控context.xml配置的资源,因为没有docBase
                            //只能都当前绝对路径使用,可能找不到再下次检查部署时找不到文件(会被undeploy)
                            addWatchedResources(app, null, context);
                        }
                        return;
                    } else {
                        // Everything else triggers a redeploy
                        // (just need to undeploy here, deploy will follow)
                        //取消部署,将从host中移除context,并且移除部署的App,资源监控对象
                        undeploy(app);
                        //删除资源
                        deleteRedeployResources(app, resources, i, false);
                        return;
                    }
                }
            } else {
                // There is a chance the the resource was only missing
                // temporarily eg renamed during a text editor save
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e1) {
                    // Ignore
                }
                // Recheck the resource to see if it was really deleted
                if (resource.exists()) {
                    continue;
                }
                // Undeploy application
                //取消部署,并且删除对应context的资源
                undeploy(app);
                deleteRedeployResources(app, resources, i, true);
                return;
            }
        }
        resources = app.reloadResources.keySet().toArray(new String[0]);
        boolean update = false;
        for (int i = 0; i < resources.length; i++) {
            File resource = new File(resources[i]);
            if (log.isDebugEnabled()) {
                log.debug("Checking context[" + app.name + "] reload resource " + resource);
            }
            long lastModified = app.reloadResources.get(resources[i]).longValue();
            // File.lastModified() has a resolution of 1s (1000ms). The last
            // modified time has to be more than 1000ms ago to ensure that
            // modifications that take place in the same second are not
            // missed. See Bug 57765.
            if ((resource.lastModified() != lastModified &&
                    (!host.getAutoDeploy() ||
                            resource.lastModified() < currentTimeWithResolutionOffset ||
                            skipFileModificationResolutionCheck)) ||
                    update) {
                if (!update) {
                    // Reload application
                    //重新部署
                    reload(app, null, null);
                    update = true;
                }
                // Update times. More than one file may have been updated. We
                // don't want to trigger a series of reloads.
                //更新监控
                app.reloadResources.put(resources[i],
                        Long.valueOf(resource.lastModified()));
            }
            app.timestamp = System.currentTimeMillis();
        }
    }

从上面我们看到,tomcat使用一个单独的后台线程去监控资源,如果发生变更,那么就进行重新部署,资源不完整的context会被下掉。

//app:发布的应用,用于维护会导致重新部署的资源修改时间,fileToRemove:docBase,用于删除,newDocBase就是那个被修改了的资源绝对路径
 private void org.apache.catalina.startup.HostConfig.reload(DeployedApplication app, File fileToRemove, String newDocBase) {
        if(log.isInfoEnabled())
            log.info(sm.getString("hostConfig.reload", app.name));
        //查找对应的context
        Context context = (Context) host.findChild(app.name);
        if (context.getState().isAvailable()) {
            if (fileToRemove != null && newDocBase != null) {
                //设置监听器,用于监听after_stop事件,然后删除资源
                context.addLifecycleListener(
                        new ExpandedDirectoryRemovalListener(fileToRemove, newDocBase));
            }
            // Reload catches and logs exceptions
            context.reload();
        } else {
            // If the context was not started (for example an error
            // in web.xml) we'll still get to try to start
            if (fileToRemove != null && newDocBase != null) {
                ExpandWar.delete(fileToRemove);
                context.setDocBase(newDocBase);
            }
            try {
                //启动
                context.start();
            } catch (Exception e) {
                log.warn(sm.getString
                         ("hostConfig.context.restart", app.name), e);
            }
        }
    }

context的reload,reload首先需要将自己标记为暂停中,然后停止,然后删除资源,启动,标记为可用。

public synchronized void org.apache.catalina.core.StandardContext.reload() {

        // Validate our current component state
        if (!getState().isAvailable())
            throw new IllegalStateException
                (sm.getString("standardContext.notStarted", getName()));

        if(log.isInfoEnabled())
            log.info(sm.getString("standardContext.reloadingStarted",
                    getName()));

        // Stop accepting requests temporarily.
        //标记为暂停
        setPaused(true);

        try {
            //停止,修改状态,触发before_stop,stoping等事件,对应ContextConfig生命周期监听器来说,主要是将context中维护的filter,servlet
            //listener,监控资源,JNDI,session等等进行关闭,session的话还会进行钝化。
            stop();
        } catch (LifecycleException e) {
            log.error(
                sm.getString("standardContext.stoppingContext", getName()), e);
        }

        try {
            //启动,这部分逻辑就是StandardContext的启动,不再赘述
            start();
        } catch (LifecycleException e) {
            log.error(
                sm.getString("standardContext.startingContext", getName()), e);
        }
        //修改暂停标记为false,表示已经启动
        setPaused(false);

        if(log.isInfoEnabled())
            log.info(sm.getString("standardContext.reloadingCompleted",
                    getName()));

    }

线程池的启动,tomcat的线程池实现是StandardThreadExecutor,我们直接看到它的startInternal方法,其父类的start方法就不再看了,和其他组件基本一样的操作,因为它们继承的基类都是一样

protected void org.apache.catalina.core.StandardThreadExecutor.startInternal() throws LifecycleException {
        //创建队列,这个队列继承自JDK的LinkedBlockingQueue并发队列
        taskqueue = new TaskQueue(maxQueueSize);
        //线程工厂,namePrefix固定前缀tomcat-exec-,daemon默认为true,getThreadPriority()默认为5
        TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
        //创建线程池,getMinSpareThreads()默认为25,getMaxThreads()默认200,maxIdleTime默认60000毫秒,
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
        //threadRenewalDelay默认1000毫秒,表示两个线程之间隔着1秒的时间进行启动(为了避免所有线程同时启动)
        executor.setThreadRenewalDelay(threadRenewalDelay);
        //是否重新启动所有的核心线程
        if (prestartminSpareThreads) {
            executor.prestartAllCoreThreads();
        }
        //给任务队列设置线程池
        taskqueue.setParent(executor);
        //给这个线程池设置状态并触发生命周期事件
        setState(LifecycleState.STARTING);
    }

mapperListener.start()

MapperListener属于ContainerListener和LifecycleListener,关联Mapper

 public void org.apache.catalina.mapper.MapperListener.startInternal() throws LifecycleException {
        //设置状态starting
        setState(LifecycleState.STARTING);
        //获取Engine容器
        Engine engine = service.getContainer();
        if (engine == null) {
            return;
        }
        //查找默认的Host,一般就是localhost,如果找到就设置到Mapper中
        findDefaultHost();

        //给子容器添加MapperListener
        //(*1*)
        addListeners(engine);
        
        //获取子容器
        Container[] conHosts = engine.findChildren();
        for (Container conHost : conHosts) {
            Host host = (Host) conHost;
            if (!LifecycleState.NEW.equals(host.getState())) {
                // Registering the host will register the context and wrappers
                //注册host,host又注册context,context又注册wrapper
                registerHost(host);
            }
        }
    }
    
    //(*1*)
    private void addListeners(Container container) {
        container.addContainerListener(this);
        container.addLifecycleListener(this);
        for (Container child : container.findChildren()) {
            addListeners(child);
        }
    }
    

注册Host

private void org.apache.catalina.mapper.MapperListener.registerHost(Host host) {
        //获取host的别名
        String[] aliases = host.findAliases();
        //注册host
        mapper.addHost(host.getName(), aliases, host);
        //注册context
        for (Container container : host.findChildren()) {
            if (container.getState().isAvailable()) {
                registerContext((Context) container);
            }
        }
        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerHost",
                    host.getName(), domain, service));
        }
    }

mapper.addHost

public synchronized void org.apache.catalina.mapper.Mapper.addHost(String name, String[] aliases,
                                     Host host) {
        //重命名通配符的host名字,如果是*.开头,那么会从1开始截取
        name = renameWildcardHost(name);
        //MappedHost对象数组,用于包装host,别名Host,context集合
        MappedHost[] newHosts = new MappedHost[hosts.length + 1];
        //创建一个MappedHost
        MappedHost newHost = new MappedHost(name, host);
        //插入host到数组中
        //(*1*)
        if (insertMap(hosts, newHosts, newHost)) {
            //插入成功,赋值新数组
            hosts = newHosts;
            //如果是默认的host,那么额外进行赋值
            if (newHost.name.equals(defaultHostName)) {
                defaultHost = newHost;
            }
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("mapper.addHost.success", name));
            }
        } else {
            //如果插入失败,那就说明存在冲突,找出重复的MappedHost
            MappedHost duplicate = hosts[find(hosts, name)];
            //如果连host对象都相同,那么直接打印debug信息
            if (duplicate.object == host) {
                // The host is already registered in the mapper.
                // E.g. it might have been added by addContextVersion()
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("mapper.addHost.sameHost", name));
                }
                //用旧的MappedHost覆盖newHost引用,以便后面直接设置别名MappedHost集合
                newHost = duplicate;
            } else {
                如果是不同的Host对象,那么出现了冲突,打印错误日志,并直接返回
                log.error(sm.getString("mapper.duplicateHost", name,
                        duplicate.getRealHostName()));
                // Do not add aliases, as removeHost(hostName) won't be able to
                // remove them
                return;
            }
        }
        //别名MappedHost集合
        List<MappedHost> newAliases = new ArrayList<>(aliases.length);
        for (String alias : aliases) {
            alias = renameWildcardHost(alias);
            //创建别名MappedHost,alias别名,newHost是前面插入的MappedHost对象
            MappedHost newAlias = new MappedHost(alias, newHost);
            //添加别名MappedHost
            //(*3*)
            if (addHostAliasImpl(newAlias)) {
                //如果成功,添加到别名集合中
                newAliases.add(newAlias);
            }
        }
        //添加与原始MappedHost的关联集合中
        newHost.addAliases(newAliases);
    }
    
    
    //(*1*)
     private static final <T> boolean insertMap
        (MapElement<T>[] oldMap, MapElement<T>[] newMap, MapElement<T> newElement) {
        //二分法查找
        int pos = find(oldMap, newElement.name);
        //如果找到了相同的MapHost,那么直接返回,不允许插入相同的MapHost,否则就出现了歧义
        if ((pos != -1) && (newElement.name.equals(oldMap[pos].name))) {
            return false;
        }
        //拷贝0到pos位置的MapHost
        System.arraycopy(oldMap, 0, newMap, 0, pos + 1);
        //将新的MapHost插入到pos的后面一个,这就保持了从小到大的顺序
        newMap[pos + 1] = newElement;
        //拷贝剩下的MapHost
        System.arraycopy
            (oldMap, pos + 1, newMap, pos + 2, oldMap.length - pos - 1);
        //表示插入成功
        return true;
    }
    
    //(*2*)
    private static final <T> int find(MapElement<T>[] map, String name) {

        int a = 0;
        int b = map.length - 1;

        // Special cases: -1 and 0
        //如果旧的mapHost数组就没有值,那么直接返回-1
        if (b == -1) {
            return -1;
        }
        //如果这个名字比第一个的host都还小,那么肯定不存在旧的maphost
        if (name.compareTo(map[0].name) < 0) {
            return -1;
        }
        //如果b为零,说明只存在一个MapHost,直接返回下标零。因为只有一个MapHost的时候
        //将要插入的MapHost肯定是大于或者等于旧的这个MapHost,肯定是要插在零下标的后面
        if (b == 0) {
            return 0;
        }

        int i = 0;
        //二分法查找
        while (true) {
            i = (b + a) / 2;
            int result = name.compareTo(map[i].name);
            if (result > 0) {
                a = i;
            } else if (result == 0) {
                return i;
            } else {
                b = i;
            }
            //找到最后一个比将要插入MapHost要小的MapHost,返回其下标
            if ((b - a) == 1) {
                int result2 = name.compareTo(map[b].name);
                if (result2 < 0) {
                    return a;
                } else {
                    return b;
                }
            }
        }

    }
    
    //(*3*)
     private synchronized boolean org.apache.catalina.mapper.Mapper.addHostAliasImpl(MappedHost newAlias) {
        MappedHost[] newHosts = new MappedHost[hosts.length + 1];
        //插入别名MappedHost对象
        if (insertMap(hosts, newHosts, newAlias)) {
            hosts = newHosts;
            //是否为默认MappedHost
            if (newAlias.name.equals(defaultHostName)) {
                defaultHost = newAlias;
            }
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("mapper.addHostAlias.success",
                        newAlias.name, newAlias.getRealHostName()));
            }
            return true;
        } else {
            //查找重复名称的MappedHost
            MappedHost duplicate = hosts[find(hosts, newAlias.name)];
            //获取原始的MappedHost对象,如果他们原始MappedHost对象相同,那么表示别名冲突
            if (duplicate.getRealHost() == newAlias.getRealHost()) {
                // A duplicate Alias for the same Host.
                // A harmless redundancy. E.g.
                // <Host name="localhost"><Alias>localhost</Alias></Host>
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("mapper.addHostAlias.sameHost",
                            newAlias.name, newAlias.getRealHostName()));
                }
                return false;
            }
            //打印错误,发生别名与其他Host的名字冲突
            log.error(sm.getString("mapper.duplicateHostAlias", newAlias.name,
                    newAlias.getRealHostName(), duplicate.getRealHostName()));
            return false;
        }
    }

registerContext

注册Host的子容器Context

private void org.apache.catalina.mapper.MapperListener.registerContext(Context context) {
        //获取context容器的请求路径
        String contextPath = context.getPath();
        //如果是根路径,contextPath设置为空字符串
        if ("/".equals(contextPath)) {
            contextPath = "";
        }
        //获取父容器Host
        Host host = (Host)context.getParent();
        //获取资源根节点
        WebResourceRoot resources = context.getResources();
        //获取欢迎页面名
        String[] welcomeFiles = context.findWelcomeFiles();
        //WrapperMappingInfo集合,用于维护Mapping,Wrapper等一些信息
        List<WrapperMappingInfo> wrappers = new ArrayList<>();
        //获取Wrapper
        for (Container container : context.findChildren()) {
            
            //创建WrapperMappingInfo集合
            //(*1*)
            prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);

            if(log.isDebugEnabled()) {
                log.debug(sm.getString("mapperListener.registerWrapper",
                        container.getName(), contextPath, service));
            }
        }
        //注册context
        mapper.addContextVersion(host.getName(), host, contextPath,
                context.getWebappVersion(), context, welcomeFiles, resources,
                wrappers);

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerContext",
                    contextPath, service));
        }
    }
    
     //(*1*)
     private void org.apache.catalina.mapper.MapperListener.prepareWrapperMappingInfo(Context context, Wrapper wrapper,
            List<WrapperMappingInfo> wrappers) {
        //获取servletName
        String wrapperName = wrapper.getName();
        boolean resourceOnly = context.isResourceOnlyServlet(wrapperName);
        //获取该Servlet的映射数组
        String[] mappings = wrapper.findMappings();
        //每个mapping创建一个WrapperMappingInfo对象
        for (String mapping : mappings) {
            //判断是否为jsp通配符模式
            boolean jspWildCard = (wrapperName.equals("jsp")
                                   && mapping.endsWith("/*"));
            wrappers.add(new WrapperMappingInfo(mapping, wrapper, jspWildCard,
                    resourceOnly));
        }
    }
    

注册context

//hostName, host, contextPath路径,version版本,welcomeResources欢迎页数组,context的根resources, wrappers刚才的Servlet容器集合
public void org.apache.catalina.mapper.Mapper.addContextVersion(String hostName, Host host, String path,
            String version, Context context, String[] welcomeResources,
            WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {
        //和前面设置Host是一样的
        hostName = renameWildcardHost(hostName);
        //精确查找MappedHost
        MappedHost mappedHost  = exactFind(hosts, hostName);
        
        //按道理应该已经存在的,如果真的没有,那么重新注册
        if (mappedHost == null) {
            addHost(hostName, new String[0], host);
            //精确查找
            mappedHost = exactFind(hosts, hostName);
            if (mappedHost == null) {
                log.error("No host found: " + hostName);
                return;
            }
        }
        //如果找出来的是一个别名Host,那么表示没有找到原始的Host
        if (mappedHost.isAlias()) {
            log.error("No host found: " + hostName);
            return;
        }
        //"/"的个数
        int slashCount = slashCount(path);
        synchronized (mappedHost) {
            //维护context,wrapper,版本的关系
            ContextVersion newContextVersion = new ContextVersion(version,
                    path, slashCount, context, resources, welcomeResources);
            if (wrappers != null) {
                //注册wrapper,分为三种类型的wrapper,通配符wrapper,扩展名wrapper,精确匹配的wrapper
                addWrappers(newContextVersion, wrappers);
            }
            //获取维护host与context关系的集合对象
            ContextList contextList = mappedHost.contextList;
            //精确匹配对应的MappedContext,与查找host是一样的,不过多的说明了
            MappedContext mappedContext = exactFind(contextList.contexts, path);
            if (mappedContext == null) {
                //构建MappedContext
                mappedContext = new MappedContext(path, newContextVersion);
                //注册MappedContext,与注册Host一样的套路,从小到大的顺序
                ContextList newContextList = contextList.addContext(
                        mappedContext, slashCount);
                if (newContextList != null) {
                    //给mappedHost与其别名MappedHost设置ContextList
                    updateContextList(mappedHost, newContextList);
                    //Mapper的一个成员变量context-》ContextVersion
                    contextObjectToContextVersionMap.put(context, newContextVersion);
                }
            } else {
                //如果已经存在旧得MappedContext了,那么获取它所维护的不同版本的contextversion
                ContextVersion[] contextVersions = mappedContext.versions;
                ContextVersion[] newContextVersions = new ContextVersion[contextVersions.length + 1];
                //像host那样从小到大插入
                if (insertMap(contextVersions, newContextVersions,
                        newContextVersion)) {
                    mappedContext.versions = newContextVersions;
                    //Mapper的一个成员变量context-》ContextVersion
                    contextObjectToContextVersionMap.put(context, newContextVersion);
                } else {
                    // Re-registration after Context.reload()
                    // Replace ContextVersion with the new one
                    int pos = find(contextVersions, version);
                    //如果已经存在相同版本的contextVersion,那么覆盖
                    if (pos >= 0 && contextVersions[pos].name.equals(version)) {
                        contextVersions[pos] = newContextVersion;
                        contextObjectToContextVersionMap.put(context, newContextVersion);
                    }
                }
            }
        }

    }

注册Wrapper

 private void org.apache.catalina.mapper.Mapper.addWrappers(ContextVersion contextVersion,
            Collection<WrapperMappingInfo> wrappers) {
        for (WrapperMappingInfo wrapper : wrappers) {
            addWrapper(contextVersion, wrapper.getMapping(),
                    wrapper.getWrapper(), wrapper.isJspWildCard(),
                    wrapper.isResourceOnly());
        }
    }
    
    protected void addWrapper(ContextVersion context, String path,
            Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) {

        synchronized (context) {
            //如果是以/*结尾
            if (path.endsWith("/*")) {
                // Wildcard wrapper
                //如/user/*,截取之后就是/user
                String name = path.substring(0, path.length() - 2);
                //包装成MappedWrapper
                MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
                        jspWildCard, resourceOnly);
                MappedWrapper[] oldWrappers = context.wildcardWrappers;
                MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
                //像注册host一样从小到大注册(以name进行排序)
                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
                    context.wildcardWrappers = newWrappers;
                    //计算wrapper的/个数
                    int slashCount = slashCount(newWrapper.name);
                    //给context保存最长路径的/个数(嵌套层数最多的那个)
                    if (slashCount > context.nesting) {
                        context.nesting = slashCount;
                    }
                }
                //后缀
            } else if (path.startsWith("*.")) {
                // Extension wrapper
                //获取后缀
                String name = path.substring(2);
                MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
                        jspWildCard, resourceOnly);
                MappedWrapper[] oldWrappers = context.extensionWrappers;
                MappedWrapper[] newWrappers =
                    new MappedWrapper[oldWrappers.length + 1];
                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
                    context.extensionWrappers = newWrappers;
                }
                //默认wrapper
            } else if (path.equals("/")) {
                // Default wrapper
                MappedWrapper newWrapper = new MappedWrapper("", wrapper,
                        jspWildCard, resourceOnly);
                context.defaultWrapper = newWrapper;
            } else {
                // Exact wrapper
                //精确匹配wrapper
                final String name;
                if (path.length() == 0) {
                    // Special case for the Context Root mapping which is
                    // treated as an exact match
                    name = "/";
                } else {
                    name = path;
                }
                MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
                        jspWildCard, resourceOnly);
                MappedWrapper[] oldWrappers = context.exactWrappers;
                MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
                //操作方式与host,context一样,以name进行从小到大排序
                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
                    context.exactWrappers = newWrappers;
                }
            }
        }
    }

    

以下为Mapper的类图

在这里插入图片描述