上一篇《Tomcat中的链接器是如何设计的》介绍了Tomcat中链接器的设计,咱们知道链接器是负责监听网络端口,获取链接请求,而后转换符合Servlet标准的请求,交给容器去处理,那么咱们这篇文章将顺着上一篇文章的思路,看看一个请求到了容器,容器是如何请求的。web
说明:本文tomcat版本是9.0.21,不建议零基础读者阅读。apache
咱们继续跟着上篇文章Adapter
的源码,继续分析,上篇文章结尾的源码以下:tomcat
//源码1.类: CoyoteAdapter implements Adapter public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); postParseSuccess = postParseRequest(req, request, res, response); if (postParseSuccess) { //check valves if we support async request.setAsyncSupported( connector.getService().getContainer().getPipeline().isAsyncSupported()); // Calling the container connector.getService().getContainer().getPipeline().getFirst().invoke( request, response); } }
上面的源码的主要做用就是获取到容器,而后调用getPipeline()
获取Pipeline
,最后去invoke
调用,咱们来看看这个Pipeline
是作什么的。网络
//源码2.Pipeline接口 public interface Pipeline extends Contained { public Valve getBasic(); public void setBasic(Valve valve); public void addValve(Valve valve); public Valve[] getValves(); public void removeValve(Valve valve); public Valve getFirst(); public boolean isAsyncSupported(); public void findNonAsyncValves(Set<String> result); } //源码3. Valve接口 public interface Valve { public Valve getNext(); public void setNext(Valve valve); public void backgroundProcess(); public void invoke(Request request, Response response) throws IOException, ServletException; public boolean isAsyncSupported();
咱们从字面上能够理解Pipeline
就是管道,而Valve
就是阀门,实际上在Tomcat中的做用也是和字面意思差很少。每一个容器都有一个管道,而管道中又有多个阀门。咱们经过后面的分析来证实这一点。app
咱们看到上面的源码是Pipeline
和Valve
的接口,Pipeline
主要是设置Valve
,而Valve
是一个链表,而后能够进行invoke
方法的调用。咱们回顾下这段源码:框架
//源码4 connector.getService().getContainer().getPipeline().getFirst().invoke( request, response);
这里是直接获取容器的管道,而后获取第一个Valve
进行调用。咱们在以前提到过Valve
是一个链表,这里只调用第一个,也就是能够经过Next去调用到最后一个。咱们再回顾下咱们第一篇文章《Tomcat在SpringBoot中是如何启动的》中提到过,容器是分为4个子容器,分别为Engine
、Host
、Context
、Wrapper
,他们同时也是父级和子级的关系,Engine
>Host
>Context
>Wrapper
。async
我以前提到过,每一个容器都一个Pipeline
,那么这个是怎么体现出来的呢?咱们看容器的接口源码就能够发现,Pipeline
是容器接口定义的一个基本属性:ide
//源码5. public interface Container extends Lifecycle { //省略其余代码 /** * Return the Pipeline object that manages the Valves associated with * this Container. * * @return The Pipeline */ public Pipeline getPipeline(); }
咱们知道了每一个容器都有一个管道(Pipeline
),管道中有许多阀门(Valve
),Valve
能够进行链式调用,那么问题来了,父容器管道中的Valve
怎么调用到子容器中的Valve
呢?在Pipeline
的实现类StandardPipeline
中,咱们发现了以下源码:post
/** // 源码6. * The basic Valve (if any) associated with this Pipeline. */ protected Valve basic = null; /** * The first valve associated with this Pipeline. */ protected Valve first = null; public void addValve(Valve valve) { //省略部分代码 // Add this Valve to the set associated with this Pipeline if (first == null) { first = valve; valve.setNext(basic); } else { Valve current = first; while (current != null) { //这里循环设置Valve,保证最后一个是basic if (current.getNext() == basic) { current.setNext(valve); valve.setNext(basic); break; } current = current.getNext(); } } container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve); }
根据如上代码,咱们知道了basic
是一个管道(Pipeline
)中的最后一个阀门,按道理只要最后一个阀门是下一个容器的第一个阀门就能够完成所有的链式调用了。咱们用一个请求debug下看看是否是和咱们的猜想同样,咱们在CoyoteAdapter
中的service
方法中打个断点,效果以下: ui
这里咱们能够知道,在适配器调用容器的时候,也就是调用Engine
的管道,只有一个阀门,也就是basic,值为StandardEngineValve
。咱们发现这个阀门的invoke方法以下:
//源码7. public final void invoke(Request request, Response response) throws IOException, ServletException { // Select the Host to be used for this Request Host host = request.getHost(); if (host == null) { // HTTP 0.9 or HTTP 1.0 request without a host when no default host // is defined. This is handled by the CoyoteAdapter. return; } if (request.isAsyncSupported()) { request.setAsyncSupported(host.getPipeline().isAsyncSupported()); } // Ask this Host to process this request host.getPipeline().getFirst().invoke(request, response); }
咱们继续debug查看结果以下:
因此这里的basic
实际上将会调用到Host
容器的管道(Pipeline
)和阀门(Valve
),也就是说,每一个容器管道中的basic
是负责调用下一个子容器的阀门。我用一张图来表示:
这张图清晰的描述了,Tomcat内部的容器是如何流转请求的,从链接器(Connector
)过来的请求会进入Engine
容器,Engine
经过管道(Pieline
)中的阀门(Valve
)来进行链式调用,最后的basic
阀门是负责调用下一个容器的第一个阀门的,一直调用到Wrapper
,而后Wrapper
再执行Servlet
。
咱们看看Wrapper
源码,是否真的如咱们所说:
//源码8. public final void invoke(Request request, Response response) throws IOException, ServletException { //省略部分源码 Servlet servlet = null; if (!unavailable) { servlet = wrapper.allocate(); } // Create the filter chain for this request ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); filterChain.doFilter(request.getRequest(), response.getResponse()); }
看到这里,你可能会说这里明明只是建立了过滤器(Filter
)而且去调用而已,并无去调用Servlet
,没错,这里确实没有去调用Servlet
,可是咱们知道,过滤器(Filter
)是在Servlet
以前执行的,也就是说,filterChain.doFilter
执行完以后变会执行Servlet
。咱们看看ApplicationFilterChain
的源码是否如咱们所说:
//源码9. public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { //省略部分代码 internalDoFilter(request,response); } //源码10. private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { //省略部分代码 // Call the next filter if there is one if (pos < n) { //省略部分代码 ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); filter.doFilter(request, response, this); return; } //调用servlet // We fell off the end of the chain -- call the servlet instance servlet.service(request, response);
经过源码咱们发现,在调用完全部的过滤器(Filter
)以后,servlet
就开始调用service
。咱们看看servlet
的实现类
这里咱们熟悉的HttpServlet
和GenericServlet
是Tomcat
包的类,实际上只有HttpServlet
,由于GenericServlet
是HttpServlet
的父类。后面就是移交给了框架去处理了,Tomcat内部的请求已经到此是完成了。
咱们知道,Tomcat是支持部署多个应用的,那么Tomcat是如何支持多应用的部署呢?是怎么保证多个应用之间不会混淆的呢?要想弄懂这个问题,咱们仍是要回到适配器去提及,回到service
方法
//源码11.类:CoyoteAdapter public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception { //省略部分代码 // Parse and set Catalina and configuration specific // request parameters //处理URL映射 postParseSuccess = postParseRequest(req, request, res, response); if (postParseSuccess) { //check valves if we support async request.setAsyncSupported( connector.getService().getContainer().getPipeline().isAsyncSupported()); // Calling the container connector.getService().getContainer().getPipeline().getFirst().invoke( request, response); } }
咱们在以前的源码中只谈到了connector.getService().getContainer().getPipeline().getFirst().invoke( request, response)
这段代码,这部分代码是调用容器,可是在调用容器以前有个postParseRequest
方法是用来处理映射请求的,咱们跟进看看源码:
//源码12.类:CoyoteAdapter protected boolean postParseRequest(org.apache.coyote.Request req, Request request, org.apache.coyote.Response res, Response response) throws IOException, ServletException { 省略部分代码 boolean mapRequired = true; while (mapRequired) { // This will map the the latest version by default connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData()); //没有找到上下文就报404错误 if (request.getContext() == null) { // Don't overwrite an existing error if (!response.isError()) { response.sendError(404, "Not found"); } // Allow processing to continue. // If present, the error reporting valve will provide a response // body. return true; } }
这里就是循环去处理Url映射,若是Context
没有找到,就返回404错误,咱们继续看源码:
//源码13.类:Mapper public void map(MessageBytes host, MessageBytes uri, String version, MappingData mappingData) throws IOException { if (host.isNull()) { String defaultHostName = this.defaultHostName; if (defaultHostName == null) { return; } host.getCharChunk().append(defaultHostName); } host.toChars(); uri.toChars(); internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData); } //源码14.类:Mapper private final void internalMap(CharChunk host, CharChunk uri, String version, MappingData mappingData) throws IOException { //省略部分代码 // Virtual host mapping 处理Host映射 MappedHost[] hosts = this.hosts; MappedHost mappedHost = exactFindIgnoreCase(hosts, host); //省略部分代码 if (mappedHost == null) { mappedHost = defaultHost; if (mappedHost == null) { return; } } mappingData.host = mappedHost.object; // Context mapping 处理上下文映射 ContextList contextList = mappedHost.contextList; MappedContext[] contexts = contextList.contexts; //省略部分代码 if (context == null) { return; } mappingData.context = contextVersion.object; mappingData.contextSlashCount = contextVersion.slashCount; // Wrapper mapping 处理Servlet映射 if (!contextVersion.isPaused()) { internalMapWrapper(contextVersion, uri, mappingData); } }
因为上面的源码比较多,我省略了不少代码,保留了能理解主要逻辑的代码,总的来讲就是处理Url包括三部分,映射Host
,映射Context
和映射Servlet
(为了节省篇幅,具体细节源码请感兴趣的同窗自行研究)。
这里咱们能够发现一个细节,就是三个处理逻辑都是紧密关联的,只有Host
不为空才会处理Context
,对于Servlet
也是同理。因此这里咱们只要Host
配置不一样,那么后面全部的子容器都是不一样的,也就完成了应用隔离的效果。可是对于SpringBoot内嵌Tomcat方式(使用jar包启动)来讲,并不具有实现多应用的模式,自己一个应用就是一个Tomcat。
为了便于理解,我也画了一张多应用隔离的图,这里咱们假设有两个域名admin.luozhou.com
和web.luozhou.com
而后我每一个域名下部署2个应用,分别是User
,log
,blog
,shop
。那么当我去想去添加用户的时候,我就会请求admin.luozhou.com
域名下的User
的Context
下面的add
的Servlet(说明:这里例子设计不符合实际开发原则,add这种粒度应该是框架中的controller完成,而不是Servlet)。
这篇文章咱们研究了Tomcat中容器是如何处理请求的,咱们来回顾下内容:
Engine
)Pieline
)-阀门(Valve
)模式完成容器的调用的,父容器调用子容器主要经过一个basic
的阀门来完成的。wrapper
完成调用后就会构建过滤器来进行过滤器调用,调用完成后就到了Tomcat内部的最后一步,调用servlet。也能够理解咱们经常使用的HttpServlet
,全部基于Servlet
规范的框架在这里就进入了框架流程(包括SpringBoot)。