tomcat 是web容器和servlet容器,比如代码实现,tomcat会监听某端口是否有发送请求过来,在此之前tomcat通过某解析器,去解析了web.xml或者某配置中所配置或者扫描注解比如 @GetMapping 的方式去初始化容器,将所有的servlet实现处理函数类加载到某个集合或者存储空间中,通过tomcat的某适配器去获取请求之后匹配容器中某存储的servlet,交给servlet处理进行客户响应。因此tomcat称为 web容器,也称为servlet容器。

QQ图片20201201123401.png

可以看到,tomcat源码中,createSocket 按照我们普通的思路也是

ServerSocket socket = new ServerSocket(8080);

1、按照这个思路去创建了一个监听8080端口的连接,而ServerSocket是java.net包下的方法。 这是验证了tomcat确实会去监听某端口,通过serverScoket。

2、tomcat中曾经配置过 <Context/> 这么一个标签,而这个标签在官网中的解释是,

一个Context标签就是一个web应用。

private WebXml buildMergedWebXml(boolean validate, boolean blockExternal)
        throws JasperException {
    WebXml webXml = new WebXml();
    WebXmlParser webXmlParser = new WebXmlParser(validate, validate, blockExternal);
    // Use this class's classloader as Ant will have set the TCCL to its own
    webXmlParser.setClassLoader(getClass().getClassLoader());

    try {
        URL url = getResource(
                org.apache.tomcat.util.descriptor.web.Constants.WEB_XML_LOCATION);
        if (!webXmlParser.parseWebXml(url, webXml, false)) {
            throw new JasperException(Localizer.getMessage("jspc.error.invalidWebXml"));
        }
    } catch (IOException e) {
        throw new JasperException(e);
    }

    // if the application is metadata-complete then we can skip fragment processing
    if (webXml.isMetadataComplete()) {
        return webXml;
    }

    // If an empty absolute ordering element is present, fragment processing
    // may be skipped.
    Set<String> absoluteOrdering = webXml.getAbsoluteOrdering();
    if (absoluteOrdering != null && absoluteOrdering.isEmpty()) {
        return webXml;
    }

    Map<String, WebXml> fragments = scanForFragments(webXmlParser);
    Set<WebXml> orderedFragments = WebXml.orderWebFragments(webXml, fragments, this);

    // JspC is not affected by annotations so skip that processing, proceed to merge
    webXml.merge(orderedFragments);
    return webXml;
}

可以看到,tomcat中有一个读取web.xml 文件的那么一个操作,

getResource(org.apache.tomcat.util.descriptor.web.Constants.WEB_XML_LOCATION);
,而这个常量所对应的就是WEB-INF下面的web.xml相对路径,

public static final String WEB_XML_LOCATION = "/WEB-INF/web.xml";

一个Context就是一个web应用,那么对应的就会有ContextConfi.class 那么一个配置,在源码中看到

// Step 9. Apply merged web.xml to Context
        if (ok) {
            configureContext(webXml);
        }

有那么一段初始化 应用上下文的,传入了webXml,继续进入方法,可以看到有一段获取servlet的代码

for (ServletDef servlet : webxml.getServlets().values()) {
        Wrapper wrapper = context.createWrapper();

一个web项目有一个web.xml 在web.xml中配置了一个个servlet标签,这就解释了一个项目全部的servlet都写在了配置中。
接下来,一个Context标签代表一个web应用,而对应的有配置类,配置类初始化了应用,获取配置文件,并且通过wrapper对servlet进行包装(包装器模式),得到在tomcat源码中,wrapper === servlet
而在代码中,一个Context并不是一个实现类,而是一个接口,对应的实现类多个,找到一个StandardContext.java 中有那么一段代码

public boolean loadOnStartup(Container children[]) {

    // Collect "load on startup" servlets that need to be initialized
    TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
    for (int i = 0; i < children.length; i++) {
        Wrapper wrapper = (Wrapper) children[i];
        int loadOnStartup = wrapper.getLoadOnStartup();
        if (loadOnStartup < 0)
            continue;
        Integer key = Integer.valueOf(loadOnStartup);
        ArrayList<Wrapper> list = map.get(key);
        if (list == null) {
            list = new ArrayList<>();
            map.put(key, list);
        }
        list.add(wrapper);
    }

    // Load the collected "load on startup" servlets
    for (ArrayList<Wrapper> list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
            } catch (ServletException e) {
                getLogger().error(sm.getString("standardWrapper.loadException",
                                  getName()), StandardWrapper.getRootCause(e));
                // NOTE: load errors (including a servlet that throws
                // UnavailableException from tht init() method) are NOT
                // fatal to application startup, excepted if failDeploymentIfServletLoadedOnStartupFails is specified
                if(getComputedFailCtxIfServletStartFails()) {
                    return false;
                }
            }
        }
    }
    return true;

}

第一个for,是收集全部标签为servlet并且为加载时启动的servlet,list.add(wrapper) 所以在源码中wrapper 就等于 servlet。
第二个for,是进行通过load方法进行实例化未实例的servlet并且进行加载。

第二点发现:在server.xml 配置文件中,每一个标签在tomcat源码中都可以找到相对应的类。
通过tomcat中的server.xml 可以看出 是有层级的,反推出来tomcat的架构图。。虽然我也没看懂这个图。。
QQ图片20201201131919.png

其实有点懵逼的。。还好开始逐个解释了,打开官网= =
tomcat中server.xml中的解释.png

Server: 在Tomcat的世界中, 服务器代表整个容器。
Service: 是连接server与service中的Connector的中间介质,最终转移给Engine
Engine: 用来管理host虚拟主机,类似于cloud中的网关作为一个中间介质类似于mvc去匹配host
Host: 是tomcat的一个域名,这个域名可以有多个想起什么都行的虚拟域名,而一个host里面管理多个context,一个context就是一个web应用。类似于springboot中yml的配置的项目前缀,域名/项目名称,去访问这个模块。
Connector: 用来监听获取客户端的请求之后转发给Engine
Context: 一个Context代表一个web应用。

整体解释: 比如

url:http://wangyifei.dashuaibi.com:8080/shopping/servlet

从这个url与上面的tomcat架构图进行解释: http作为一个客户端请求的通信协议,发送到service中的connector,通过connector监听的端口,比如8080端口,之后将剩下的信息转发给Engine,Engine再去匹配到指定的虚拟机主机,如:wangyifei.dashuaibi.com这个虚拟域名,之后再通过context去匹配是哪一个项目 类似于springboot中yml的配置的项目前缀,域名/项目名称,去访问这个模块。 再通过指定的servlet通过filter过滤器去匹配到交给哪一个函数类或者方法去处理这个请求进行一个响应。

从tomcat架构图还可以解决了一个多年的疑惑- -注意看connector下面的thread pool,官网解释,这个线程池是给tomcat中的connector进行调配使用的,可以猜想到,连接一个客户端创建一个线程,所以得到请求是多线程的。

还有一个神奇的。。以为session是Java带来的,没想到是tomcat中的。。tomcat中有一个Manager.java 这么一个interface,而在接口中,出现了createSession,而前端的请求中的。。。卧槽!! 果然经过百度和视频+源码结构图理解,猜想HttpRequestServlet是tomcat的一个东西,果然是!!!Java提供了一个接口,而tomcat对这个接口进行了实现。 所以request.getSession() 其实并不是Java创建的,而是进入了tomcat源码中的Manager.java 中进行生成的一个Session!!。

最后修改:2020 年 12 月 01 日 09 : 14 PM
如果觉得我的文章对你有用,请随意赞赏