请求在Tomcat中的运行流程
本来想写完Servlet在将笔记中的Tomcat整理出来,但是发现不说清除Tomcat,Servlet根本没法说清楚,所以,这一篇主要梳理请求在Tomcat中到底发生了什么。就像我们知道的Tomcat被作为WEB容器使用,当tomcat接收到请求后会发生下列事情:之前有一篇文章说了大概的Tomcat的运行,但是,那篇文章将Tomcat和Servlet作为一个Web容器来讲了,并不是很详细 ...
本来想写完Servlet在将笔记中的Tomcat整理出来,但是发现不说清除Tomcat,Servlet根本没法说清楚,所以,这一篇主要梳理请求在Tomcat中到底发生了什么。
就像我们知道的Tomcat被作为WEB容器使用,当tomcat接收到请求后会发生下列事情:
之前有一篇文章说了大概的Tomcat的运行,但是,那篇文章将Tomcat和Servlet作为一个Web容器来讲了,并不是很详细
Tomcat简述
1.Tomcat流程
下面我试图将Tomcat更加详细的说明出来:
Tomcat中的六个容器:
容器 | 作用 |
---|---|
Server容器 | 一个StandardServer类实例就表示一个Server容器,server是tomcat的顶级构成容器 |
Service容器 | 一个StandardService类实例就表示一个Service容器,Tomcat的次顶级容器,Service是这样一个集合:它由一个或者多个Connector组成,以及一个Engine,负责处理所有Connector所获得的客户请求。 |
Engine容器 | 一个StandardEngine类实例就表示一个Engine容器。Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名。当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理,Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理 |
Host容器 | 一个StandardHost类实例就表示一个Host容器,代表一个VirtualHost,虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配。每个虚拟主机下都可以部署(deploy)一个或者多个WebApp,每个Web App对应于一个Context,有一个Context path。当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。匹配的方法是“最长匹配”,所以一个path==”“的Context将成为该Host的默认Context。所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配 |
Context容器 | 一个StandardContext类实例就表示一个Context容器。一个Context对应于一个Web Application,一个WebApplication由一个或者多个Servlet组成。Context在创建的时候将根据配置文件CATALINA_HOME/conf/web.xml和WEBAPP_HOME/WEB-INF/web.xml载入Servlet类。当Context获得请求时,将在自己的映射表(mappingtable)中寻找相匹配的Servlet类。如果找到,则执行该类,获得请求的回应,并返回 |
Wrapper容器 | 一个StandardWrapper类实例就表示一个Wrapper容器,Wrapper容器负责管理一个Servlet,包括Servlet的装载、初始化、资源回收。Wrapper是最底层的容器,其不能在添加子容器了。Wrapper是一个接口,其标准实现类是StandardWrapper |
-Connector
一个Connector将在某个指定端口上侦听客户请求,并将获得的请求交给Engine来处理,从Engine处获得回应并返回客户。
Tomcat有两个典型的Connector:
- 一个直接侦听来自browser的http请求
- 一个侦听来自其它WebServer的请求
Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求。
Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。
下面是流程图:
这是一个宏观的路线
2.Tomcat的启动与类的具体实现
一个WEB应用对应一个context容器,也就是servlet运行时的servlet容器。
添加一个web应用时将会创建一个StandardContext容器,并且给这个context容器设置必要的参数,url和path分别代表这个应用在tomcat中的访问路径和这个应用实际的物理路径,这两个参数与tomcat配置中的两个参数是一致的。
其中一个最重要的一个配置是ContextConfig,这个类会负责整个web应用配置的解析工作。
最后将这个context容器加入到父容器host中。
接下来会调用tomcat的start方法启动tomcat。
Tomcat的启动逻辑是基于观察者模式的,所有的容器都会继承Lifecycle接口,它管理着容器的整个生命周期,所有容器的修改和状态改变都会由它通知已经注册的观察者。
Tomcat启动的时序如下:
仔细看看基本上就清楚了,这里只记录一下细节:
ContextConfig的init方法
当context容器初始状态设置Init时,添加到context容器的listener将会被调用。ContextConfig继承了LifecycleListener接口,它是在调用Tomcat.addWebapp时被加入到StandardContext容器中的。ContextConfig类会负责整个WEB应用的配置文件的解析工作。
- ContextConfig的init方法将会主要完成一下工作:
- 创建用于解析XML配置文件的contextDigester对象
- 读取默认的context.xml文件,如果存在则解析它
- 读取默认的Host配置文件,如果存在则解析它
- 读取默认的Context自身的配置文件,如果存在则解析它
- 设置Context的DocBase
startInternal方法
ContextConfig的init方法完成后,Context容器会执行startInternal方法,这个方法包括如下几个部分:
- 创建读取资源文件的对象
- 创建ClassLoader对象
- 设置应用的工作目录
- 启动相关的辅助类,如logger,realm,resources等
- 修改启动状态,通知感兴趣的观察者
- 子容器的初始化
- 获取ServletContext并设置必要的参数
- 初始化“load on startuo”的Servlet
Web应用的初始化
web应用的初始化在14步,下面是该初始化的详细内容
WEB应用的初始化工作是在ContextConfig的configureStart方法中实现的,应用的初始化工作主要是解析web.xml文件,这个文件是一个WEB应用的入口。
Tomcat首先会找globalWebXml,这个文件的搜索路径是engine的工作目录下的org/apache/catalina/startup/NO-DEFAULT_XML或conf/web.xml。
接着会找hostWebXml,这个文件可能会在System.getProperty(“catalina.base”)/conf/$ {EngineName}/${HostName}/web.xml.default中。
接着寻找应用的配置文件examples/WEB-INF/web.xml,web.xml文件中的各个配置项将会被解析成相应的属性保存在WebXml对象中。
接下来会讲WebXml对象中的属性设置到context容器中,这里包括创建servlet对象,filter,listerner等,这些在WebXml的configureContext方法中。
以及下面是解析servlet的代码对象:
for (ServletDef servlet : servlets.values()) {
Wrapper wrapper = context.createWrapper();
String jspFile = servlet.getJspFile();
if (jspFile != null) {
wrapper.setJspFile(jspFile);
}
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(roleRef.getName(), roleRef.getLink());
}
wrapper.setServletClass(servlet.getServletClass());
MultipartDef multipartdef = servlet.getMultipartDef();
if (multipartdef != null) {
if (multipartdef.getMaxFileSize() != null &&
multipartdef.getMaxRequestSize()!= null &&
multipartdef.getFileSizeThreshold() != null) {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation(),
Long.parseLong(multipartdef.getMaxFileSize()),
Long.parseLong(multipartdef.getMaxRequestSize()),
Integer.parseInt(
multipartdef.getFileSizeThreshold())));
} else {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation()));
}
}
if (servlet.getAsyncSupported() != null) {
wrapper.setAsyncSupported(
servlet.getAsyncSupported().booleanValue());
}
context.addChild(wrapper);
}
3.创建Servlet实例
前面完成了servlet的解析工作,并且被包装成了StandardWrapper添加到Context容器中,但是它仍然不能为我们工作,它还没有被实例化。
3.1.创建
如果Servlet的load-on-startup配置项大于0,那么在Context容器启动时就会被实例化。
(这个是在web.xml中进行了配置)
前面提到的在解析配置文件时会读取默认的globalWebXml,在conf下的web.xml文件中定义了一些默认的配置项,其中定义了两个Servlet,分别是org.apache.catalina.servlets.DefaultServlet和org.apache.jsper.servlet.JspServelt,它们的load-on-startup分别是1和3,也就是当tomcat启动时这两个servlet就会被启动。
创建Servlet实例的方式是从Wrapper.loadServlet开始的,loadServlet方法要完成的就是获取servletClass,然后把它交给InstanceManager去创建一个基于servletClass.class的对象。如果这个Servlet配置了jsp-file,那么这个servletClass就是在conf/web.xml中定义的org.apache.jasper.servlet.JspServlet。
3.2.初始化
初始化Servlet在StandardWrapper的initServlet方法中,这个方法很简单,就是调用Servlet的init()方法,同时把包装了StandardWrapper对象的StandardWrapperFacade作为ServletConfig传给Servlet。
如果该Servlet关联的是一个JSP文件,那么前面初始化的就是JspServlet,接下来会模拟一次简单请求,请求调用这个JSP文件,以便编译这个JSP文件为类,并初始化这个类。
这样Servlet对象的初始化就完成了。
3.3.容器默认Servlet
每个servlet容器都有一个默认的servlet,一般都叫做default。
例如:tomcat中的 DefaultServlet 和 JspServlet (上面的部分)
4.创建HttpServlet
这一篇细细说说请求是怎么交到Servlet手上的。此时,包括Servlet都已经初始化化完毕,一切已经准备就绪。
HttpServletRequest的产生大概在第四步
步骤 | 过程 | 详情 |
---|---|---|
1 | AbstractEndpoint类及其子类来处理。 | AbstractEndpoint这个抽象类中有一个抽象内部类Acceptor,这个Acceptor的实现类是AbstractEndpoint的三个子类的内部类Acceptor来实现的。 |
1 | 我们的请求就是被这Acceptor监听到并且接收的。这个类其实是一个线程类,因为AbstractEndpoint.Acceptor实现了Runnable接口。 | |
1 | AprEndpoint接收请求的过程。就是用一个接收器接收请求,过程中会使用套接字。但是好像并不是有的请求都会用这个Acceptor来接收。 | |
1 | 当接收请求完毕,经过一系列的处理后就会由AprEndpoint的内部类SocketProcessor来将请求传给ProtocolHandler来处理。这个SocketProcessor也是一个线程类。它有一行代码将套接字传给了第二步来处理。 | |
2 | 在/AbstractConnectionHandler接收到第一步传来的套接字以后,对套接字进行处理 | wapper就是套接字包装类的对象,这里还是理解为套接字,套接字在这里传给了第③步的Processor接口的实例。state = processor.process(wrapper); |
3 | 第二步完成后,就会交给Processor接口的实现类来处理。 | 在这里将会创建请求和响应,但不是我们熟悉的HttpServletRequest或HttpServletResponse类型或其子类型。而是org.apache.coyote.Request 和 org.apache.coyote.Response类型的 |
3 | 它将会在Connector中来进行处理,我说的是处理而不是类型转换是因为org.apache.coyote.Request 和HttpServletRequest并不是父子关系的类,总之,HttpServletRequest的请求是由Connector来创建,在CoyoteAdapter中处理成HttpServletRequest. | |
3 | 这个方法就是请求对象、响应对象和套接字进行信息交互的地方,也就是真真正正将套接字中的信息转化为请求信息,还要把响应信息写到套接字中。 | |
4 | 第三步完成之后交给CoyoteAdapter来处理 | CoyotoAdapter是将请求传入Server容器的切入点。Adapter. This represents the entry point in a coyote-based servlet container. |
4 | (具体怎么处理成HttpServletRequest看代码) | CoyoteAdapter中有一个service()方法。这个方法持有一个Connector的引用。这个Connector又持有一个Service容器的引用,而Service容器有持有一个Container(Container的实现类有StandardEngine、StandardHost等等)的引用。所以CoyoteAdapter就可以根据这些引用将请求传递到Server容器中了。 |
5 | 如果上面的请求传递到的Container是StandaradEngine,那么就会Engine就会调用它持有的StandardPipeline对象来处理请求。 | StandardPipeline就相当于一条管道,这条管道中的有许多阀门,这些阀门会对请求进行处理,并且控制它下一步往哪里传递。StandardEngine的管道使用的阀门是StandardEngineValve。 |
6 | 容器继续逐层传递 | 和StandardEngine一样,StandardHost、StandardContext、StandardWrapper这几个容器都拥有自己的一条管道StandardPipeline来处理的请求。但是需要注意的是他们使用的阀门是不一样的。StandardHost则会使用StandardHostValve,其他的同理。 |
7 | 当最后一个StandardWrapperVale处理完请求后,此时请求已经到达了最底层的容器了。 | StandardWrapper就是最底层的容器,它不允许再有子容器。其实每一个StandardWrapper代表一个Servlet,因为每一个StandardWrapper都会持有一个Servlet实例的引用。 |
//CoyoteAdapter的service(...)方法
/**
* Service method.
*/
@Override
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res)
throws Exception {
//下面这两行代码就是将请求处理后转化为HttpServletRequest的
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
//如果请求为null就让Connector来创建。
if (request == null) {
// Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);
//省略部分代码
}
if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
}
boolean comet = false;
boolean async = false;
boolean postParseSuccess = false;
try {
// Parse and set Catalina and configuration specific
// request parameters
req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container 在这里将请求传到Server容器中。
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
//省略部分代码
}
} /**
5.Tomcat核心类
Tomcat的体系结构:
一个Connector+一个Container构成一个Service,Service就是对外提供服务的组件,有了Service组件Tomcat就可以对外提供服务了。
那么这些个组件到底是干嘛用的呢?
Connector是一个连接器,主要负责接收请求并把请求交给Container
Container就是一个容器,主要装的是具体处理请求的组件。
Service主要是为了关联Container与Connector,一个单独的Container或者一个单独的Connector都不能完整处理一个请求,只有两个结合在一起才能完成一个请求的处理。
5.1 Tomcat的两大组件
1. Connecter组件
一个Connecter将在某个指定的端口上侦听客户请求,接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理Engine(Container中的一部分),从Engine出获得响应并返回客户。
Tomcat中有两个经典的Connector,一个直接侦听来自Browser的HTTP请求,另外一个来自其他的WebServer请求。Cotote HTTP/1.1 Connector在端口8080处侦听来自客户Browser的HTTP请求,Coyote JK2 Connector在端口8009处侦听其他Web Server的Servlet/JSP请求。
Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。
2. Container组件
主容器,包含Tomcat的各个容器。
Container是容器的父接口,由四个容器组成,分别是Engine,Host,Context,Wrapper。其中Engine包含Host,Host包含Context,Content包含Wrapper,一个Servlet class对应一个Wrapper
6.总结
这篇文章是整理多篇文章所得,并不能保证完全正确,如有错误,感谢指正。
附件:
server.xml的配置:server.xml
参考:
文章大部分是网络资料,以及少量的自我梳理,以下是参考的链接:
http://www.cnblogs.com/GooPolaris/p/8111837.html
http://blog.csdn.net/res_cue/article/details/21756357
https://www.cnblogs.com/gy19920604/p/5377766.html
http://blog.csdn.net/simba1949/article/details/79242746
更多推荐
所有评论(0)