(五) Tomcat 源码系列之 URI 映射

在地址栏输入的 URI , Tomcat 是如何找到对应的 Servlet 或 JSP (Jsp 就是一个 Servlet) 呢 ?

其实在启动 Tomcat 组件 MapperListener 的时候, 就将对应的映射关系放入了一个 Mapper 中, 它有这几个作用

  1. 映射关系存储:存储所有的 Host,Context 及 Wrapper 的对应关系;
  2. 映射关系初始化及变更:当新增一个组件或者移除一个组件时,Mapper 如何维护 URI 到 Wrapper 的映射关系;
  3. 映射关系使用 :根据 URI,映射到具体的 Host,Context 和 wrapper

Mapper

public final class Mapper {
    
    // 存储所有的 Host 
    volatile MappedHost[] hosts = new MappedHost[0];
    ...
    protected abstract static class MapElement<T> {

        public final String name;
        public final T object;

        public MapElement(String name, T object) {
            this.name = name;
            this.object = object;
        }
    }
    // 这个里面存储的是 MappedContext 数组
    protected static final class ContextList {
        public final MappedContext[] contexts;
        ...
    }
    // 带版本号的 Context , 在 URI 请求参数中可以带版本号
    // 一个 MappedContext 对应一个 ContextVersion (通过 path 对应)
    protected static final class ContextVersion extends MapElement<Context> {
        // context 的匹配路径
        public final String path;
        public final int slashCount;
        public final WebResourceRoot resources;
        // context 的欢迎页面
        public String[] welcomeResources;
        // 默认的 wrapper
        public MappedWrapper defaultWrapper = null;
        // 精确匹配路径的 wrapper
        public MappedWrapper[] exactWrappers = new MappedWrapper[0];
        // 通配符结尾的 wrapper
        public MappedWrapper[] wildcardWrappers = new MappedWrapper[0];
        // 扩展名匹配的 wrapper
        public MappedWrapper[] extensionWrappers = new MappedWrapper[0];
        public int nesting = 0;
        // 这个context是否还可用,是否被暂停
        private volatile boolean paused;
    }
    // URI 映射的主机
    // 该 Host 下部署的所有 Web 应用, 存放的是 MappedContext 数组
    protected static final class MappedHost extends MapElement<Host> {
       public volatile ContextList contextList;
        ...
    }
    // URI 映射的 Context, 也就是一个 Web 应用
    protected static final class MappedContext extends MapElement<Void> {
        ...
    }
    // URI 映射的 Servlet  
    protected static class MappedWrapper extends MapElement<Wrapper> {
        ...
    }
  
}

MapperListener

实现了 ContainerListener 与 LifecycleListener 接口,监听 tomcat 组件的变化,当有 Host,Context 及 Wrapper 变更时,调用 Mapper 相关方法,增加或者删除 Host,Context,Wrappe r等

在启动 MapperListener 的时候添加了 URI 映射, 放入 Mapper 对象中, 直接看 MapperListener 的 startInternal 方法

public void startInternal() throws LifecycleException {

    setState(LifecycleState.STARTING);

    Engine engine = service.getContainer();
    if (engine == null) {
        return;
    }
    // 默认主机 localhost
    findDefaultHost();

    // 为这个 Engine 添加 生命周期 MapperListener, 容器监听器 MapperListener, 子容器同样处理
    addListeners(engine);

    // 找到 Engine 的子容器 Host
    Container[] conHosts = engine.findChildren();
    for (Container conHost : conHosts) {
        Host host = (Host) conHost;
        if (!LifecycleState.NEW.equals(host.getState())) {
            // 同时会注册 Engine 的子组件 Context 和 Wrapper
            // 将映射关系放入 mapper 对象中
            // 以后直接用 URI 匹配 mapper 找到对应的 Servlet
            registerHost(host);
        }
    }
}
------------------
private void registerHost(Host host) {

    // Host 的别名
    String[] aliases = host.findAliases();
    // 添加到 Mapper 映射中, 比如 127.0.0.1 -> localhost
    // 当 8080 端口监听到请求后, 会根据域名找到对应的 Host
    mapper.addHost(host.getName(), aliases, host);

    // 处理子容器 : Context
    for (Container container : host.findChildren()) {
        if (container.getState().isAvailable()) {
            registerContext((Context) container);
        }
    }
    ...
}

addHost

public synchronized void addHost(String name, String[] aliases,
                                 Host host) {
    // 通配符检查, 是否以  .*  URI 开头
    name = renameWildcardHost(name);
    // 一份新的 MappedHost 数组, 总是被老 MappedHost 长度大 1 位
    MappedHost[] newHosts = new MappedHost[hosts.length + 1];
    // 将 Host 转换为 MappedHost 对象
    MappedHost newHost = new MappedHost(name, host);
    // MappedHost 插入 MapperHost 数组中,
    // 如果不存在 name 命名的 MapperHost,则插入成功,返回 true
    if (insertMap(hosts, newHosts, newHost)) {
        // 插入成功, 覆盖老的 MappedHost 数组
        hosts = newHosts;
        //  如果是默认主机
        if (newHost.name.equals(defaultHostName)) {
            defaultHost = newHost;
        }
        ...
    }
    else { // 说明存在 name 相同的 MappedHost
        MappedHost duplicate = hosts[find(hosts, name)];
        // 判断存储的 Host 对象是否相同, 如果是, 直接覆盖
        if (duplicate.object == host) {
            ...
                newHost = duplicate;
        }
    }
    // 如果 Host 有别名, 处理这个别名
    List<MappedHost> newAliases = new ArrayList<>(aliases.length);
    for (String alias : aliases) {
        alias = renameWildcardHost(alias);
        MappedHost newAlias = new MappedHost(alias, newHost);
        if (addHostAliasImpl(newAlias)) {
            newAliases.add(newAlias);
        }
    }
    newHost.addAliases(newAliases);
}

addContextVersion

在执行 registryContext 方法的时候, 先处理默认的 Servlet (DefaultServlet, JspServlet) 以及自定义 Servlet 映射信息, 并保存到一个集合中, 并传给 addContextVersion , 处理 Context 的映射信息

public void addContextVersion(String hostName, Host host, String path,
                              String version, Context context, String[] welcomeResources,
                              WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {

    // 同样是通配符处理, 去除开头的 *.
    hostName = renameWildcardHost(hostName);

    // 根据 host name 从 MappedHost 数组中找到此 Context 的父容器 Host
    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;
        }
    }
    // 如果是别名
    if (mappedHost.isAlias()) {
        log.error("No host found: " + hostName);
        return;
    }
    int slashCount = slashCount(path);
    synchronized (mappedHost) {
        // 创建 ContextVersion 对象, 包含 Context 对象, 对应的 URI 映射, 版本信息, 欢迎页面...
        ContextVersion newContextVersion = new ContextVersion(version,
                                                              path, slashCount, context, resources, welcomeResources);
        if (wrappers != null) {
            // 处理 Context 子容器 Wrapper 的映射信息
            addWrappers(newContextVersion, wrappers);
        }

        // 对 MappedContext 处理
        ContextList contextList = mappedHost.contextList;
        // contextList中不同的 MapperContext 是通过 path 区分的
        // 而这个 path 就是 context 的 path
        MappedContext mappedContext = exactFind(contextList.contexts, path);
        if (mappedContext == null) {
            // 没有就创建一个
            mappedContext = new MappedContext(path, newContextVersion);
            ContextList newContextList = contextList.addContext(
                mappedContext, slashCount);
            if (newContextList != null) {
                updateContextList(mappedHost, newContextList);
                contextObjectToContextVersionMap.put(context, newContextVersion);
            }
        }
        else {
            // 和 Host 做相似处理
            ContextVersion[] contextVersions = mappedContext.versions;
            ContextVersion[] newContextVersions = new ContextVersion[contextVersions.length + 1];
            // 同样将 ContextVersion 插入到 ContextVersion 数组中
            if (insertMap(contextVersions, newContextVersions,
                          newContextVersion)) {
                mappedContext.versions = newContextVersions;
                contextObjectToContextVersionMap.put(context, newContextVersion);
            }
            ...
        }
    }

}

addWrapper

addWrapper 有多个重载方法, 但最终都是调用下面的实现

synchronized (context) {
    // 通配符匹配,  先处理通配符的 URI 映射
    if (path.endsWith("/*")) {
        ...
    }
    else if (path.startsWith("*.")) {
        ...
   }
    else if (path.equals("/")) { 
        MappedWrapper newWrapper = new MappedWrapper("", wrapper, jspWildCard, resourceOnly);
        context.defaultWrapper = newWrapper;
    }
    else {  
        final String name;
        if (path.length() == 0) {
            name = "/";
        }
        else {
            name = path;
        }
       
        MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
                                                     jspWildCard, resourceOnly);
        MappedWrapper[] oldWrappers = context.exactWrappers;
        MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
        // 插入到 MappedWrapper 数组中
        if (insertMap(oldWrappers, newWrappers, newWrapper)) {
            context.exactWrappers = newWrappers;
        }
    }
  
}

可以发现, 映射关系以数组的形式放入到放入 Mapper 对象中, Host 对应 Mapper 对象中的 MappedHost 数组, 每个 MappedHost 对象有一个属性 ContextList, 存放的就是 MappedContext 数组, 一个 MappedContext 根据 path 对应一个 ContextVersion, 而每个 ContextVersion 有三个数组, 存放的就是 MappedWrapper, 所有, URI 映射关系就这样与 Mapper 映射起来

猜你喜欢

转载自blog.csdn.net/Gp_2512212842/article/details/107483457