深入剖析tomcat之servlet容器

其实我们开发中经常用tomcat应用服务器,tomcat就一个servlet容器,能够运行基于serlvlet的应用程序并响应相应的http请求,开发时间长了,还是想想具体知道它是怎么运行的,尤其是servlet容器的机理,所以有幸拜读了外国人的《深入剖析tomcat》,感觉挺不错的,可以在此点击免费下载电子书,建议大家有时间读读,在读的过程中边读边翻阅着tomcat的源码,更有助于你理解它的各个机制,此处有tomcat
7的源码,点击可免费下载

本人目前时间和组织语言能力及功力有限,记录纸质笔记本上的同时想好好组织语言总结记录成电子分享并记录,但心有余而力不足,网上看到有好心人(randyjiawenjie)做过记录,大概就是把书中重要的东西摘录的了一下,本人就就特意摘录以下了。再就是为了更有助理解,大家可以参考Tomcat
容器与servlet的交互原理
,简单介绍了serlvet的原理和生命周期,还有学习tomcat源码(2)
实现servlet容器功能
,这篇文章主要还是电子书的代码及解释,升华的文章Tomcat
系统架构与设计模式,第 2 部分: 设计模式分析

主要是《深入剖析tomcat》的第五章和第十一章。个人觉得如下3点是关键:

1. pipeline相关概念及其执行valve的顺序;

2. standardwrapper的接受http请求时候的调用序列;

3. standardwrapper基础阀加载servlet的过程(涉及到STM);

顺便问一句,应该每一个servlet程序员都知道filter。但是你知道Filter在tomcat的哪一个地方出现的吗?答案是standardwrapper基础阀会创建Filter链,并调用doFilter()方法

servlet容器的作用

管理servlet资源,并为web客户端填充response对象。

不同级别的容器

Engine:表示整个Catalina的servlet引擎、

Host:表示一个拥有数个上下文的虚拟主机

Context:表示一个Web应用,一个context包含一个或多个wrapper

Wrapper:表示一个独立的servlet

容器都实现了Container接口,继承了ContainerBase抽象类。

管道任务

3个概念:pipeline、valve、valveContext

pipeline包含servlet容器将要调用的任务集合,定义如下:

[java] view
plain
copy

  1. public interface Pipeline {
  2. public Valve getBasic();
  3. public void setBasic(Valve valve);
  4. public void addValve(Valve valve);
  5. public Valve[] getValves();
  6. public void invoke(Request request, Response response) throws IOException, ServletException;
  7. public void removeValve(Valve valve);
  8. }

valve表示一个具体的执行任务,定义如下:

[java] view
plain
copy

  1. public interface Valve {
  2. public String getInfo();
  3. public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException;
  4. }

valveContext按照字面意思就是阀门的上下文,用于遍历valve

[java] view
plain
copy

  1. public interface ValveContex{
  2. public String getInfo();
  3. public void invokeNext(Request request, Response response) throws IOException, ServletException;
  4. }

valveContext的invokeNext()方法的实现:

[java] view
plain
copy

  1. public final void invokeNext(Request request, Response response) throws IOException, ServletException {
  2. int subscript = stage; stage = stage + 1;
  3. if (subscript < valves.length)
  4. { valves[subscript].invoke(request, response, this); }
  5. else if ((subscript == valves.length) && (basic != null))
  6. {
  7. basic.invoke(request, response, this);
  8. }
  9. else
  10. {
  11. throw new ServletException (sm.getString("standardPipeline.noValve"));
  12. }
  13. }

一个阀门的invoke方法可以如下实现:

[java] view
plain
copy

  1. public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
  2. //Pass the request and response on to the next valve in our pipeline
  3. valveContext.invokeNext(request, response);
  4. // now perform what this valve is supposed to do ...
  5. }

如果pipeline由valve1、valve2、valve3组成,调用valve1. Invoke()会发生什么?执行顺序是什么样的?假设valveN(N=1,2,3)的invoke()方法实现如下:

[java] view
plain
copy

  1. valveContext.invokeNext(request, response);
  2. System.out.println(“valve1 invoke!”);

如果注意到了invokeNext()的实现,这层调用类似与下面的图:

类似与递归调用,输出为

[java] view
plain
copy

  1. “valve3 invoke!”
  2. “valve2 invoke!”
  3. “valve1 invoke!”

顺序恰好于添加的顺序相反。所以这个也可以说明,为什么基础阀看起来是放在最后的,但确实要做装载servlet这样的操作。其实,放在最后的阀门总是第一个被调用。书中的依次添加HeaderLoggerValve、ClientIPLoggerValve依次添加,那么调用顺序为ClientIPLoggerValve、HeaderLoggerValve。注意到书中示例程序的运行结果和添加阀门的顺序。不过话说回来,如果valve的invoke()方法实现为:

[java] view
plain
copy

  1. public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
  2. // now perform what this valve is supposed to do ...
  3. valveContext.invokeNext(request, response); //Pass the request and response on to the next valve in our pipeline
  4. }

那么阀门调用的顺序就会和阀门添加的顺序一致(个人感觉这样好理解)

Wrapper接口

Wrapper表示一个独立servlet定义的容器,wrapper继承了Container接口,并且添加了几个方法。包装器的实现类负责管理其下层servlet的生命周期。

包装器接口中重要方法有allocate和load方法。allocate方法负责定位该包装器表示的servlet的实例。Allocate方法必须考虑一个servlet,是否实现了avax.servlet.SingleThreadModel接口。

Wrapper调用序列:

(1)      连接器调用Wrapper的invoke()方法;

(2)      Wrapper调用其pipeline的invoke()方法;

(3)      Pipeline调用valveContext.invokeNext()方法;

基础阀的invoke实现示例如下:

[java] view
plain
copy

  1. public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
  2. SimpleWrapper wrapper = (SimpleWrapper) getContainer();
  3. ServletRequest sreq = request.getRequest();
  4. ServletResponse sres = response.getResponse();
  5. Servlet servlet = null;
  6. HttpServletRequest hreq = null;
  7. if (sreq instanceof HttpServletRequest)
  8. hreq = (HttpServletRequest) sreq;
  9. HttpServletResponse hres = null;
  10. if (sres instanceof HttpServletResponse) hres = (HttpServletResponse) sres;
  11. // Allocate a servlet instance to process this request
  12. try {
  13. servlet = wrapper.allocate();
  14. if (hres!=null && hreq!=null)
  15. {
  16. servlet.service(hreq, hres);
  17. }
  18. else
  19. {
  20. servlet.service(sreq, sres);
  21. }
  22. }
  23. catch (ServletException e) {
  24. }
  25. }

Context应用程序

Context包含多个wrapper,至于用哪一个wrapper,是由映射器mapper决定的。Mapper定义如下:

[java] view
plain
copy

  1. public interface Mapper {
  2. public Container getContainer();
  3. public void setContainer(Container container);
  4. public String getProtocol();
  5. public void setProtocol(String protocol);
  6. public Container map(Request request, boolean update);
  7. }

map方法返回一个子容器(wrapper)负责来处理请求。map方法有两个参数,一个request对象和一个boolean值。实现将忽略第二个参数。这个方法是从请求对象中获取context路径和使用context的findServletMapping方法获取相关的路径名。如果一个路径名被找到,它使用context的findChild方法获取一个Wrapper的实例。一个mapper的实现:

[java] view
plain
copy

  1. public Container map(Request request, boolean update) {
  2. // Identify the context-relative URI to be mapped
  3. String contextPath = ((HttpServletRequest) request.getRequest()).getContextPath();
  4. String requestURI = ((HttpRequest) request).getDecodedRequestURI();
  5. String relativeURI = requestURI.substring(contextPath.length());
  6. // Apply the standard request URI mapping rules from
  7. // the specification
  8. Wrapper wrapper = null;
  9. String servletPath = relativeURI;
  10. String pathInfo = null;
  11. String name = context.findServletMapping(relativeURI);
  12. if (name != null)
  13. wrapper = (Wrapper) context.findChild(name);
  14. return (wrapper);

[java] view
plain
copy

  1. }

容器包含一条管道,容器的invoke方法会调用pipeline的invoke方法。

1. pipeline的invoke方法会调用添加到容器中的阀门的invoke方法,然后调用基本阀门的invoke方法。

2.在一个wrapper中,基础阀负责加载相关的servlet类并对请求作出相应。

3. 在一个包含子容器的Context中,基础阀使用mapper来查找负责处理请求的子容器。如果一个子容器被找到,子容器的invoke方法会被调用,然后返回步骤1。

Context的一个基础阀示例如下:注意最后一句话:wrapper.invoke(request, response);

[java] view
plain
copy

  1. public void invoke(Request request, Response response,ValveContext valveContext)
  2. throws IOException, ServletException {
  3. // Validate the request and response object types
  4. if (!(request.getRequest() instanceof HttpServletRequest) ||
  5. !(response.getResponse() instanceof HttpServletResponse)) {
  6. return;
  7. }
  8. // Disallow any direct access to resources under WEB-INF or META-INF
  9. HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
  10. String contextPath = hreq.getContextPath();
  11. String requestURI = ((HttpRequest) request).getDecodedRequestURI();
  12. String relativeURI = requestURI.substring(contextPath.length()).toUpperCase();
  13. Context context = (Context) getContainer();
  14. // Select the Wrapper to be used for this Request
  15. Wrapper wrapper = null;
  16. try {
  17. wrapper = (Wrapper) context.map(request, true);
  18. }catch (IllegalArgumentException e) {
  19. badRequest(requestURI, (HttpServletResponse)
  20. response.getResponse());
  21. return;
  22. }
  23. if (wrapper == null) {
  24. notFound(requestURI, (HttpServletResponse) response.getResponse());
  25. return;
  26. }
  27. // Ask this Wrapper to process this Request
  28. response.setContext(context);
  29. wrapper.invoke(request, response);
  30. }

standardwrapper

方法调用序列Sequence of Methods Invocation

对于每一个连接,连接器都会调用关联容器的invoke方法。接下来容器调用它的所有子容器的invoke方法。如果一个连接器跟一个StadardContext实例相关联,那么连接器会调用StandardContext实例的invoke方法,该方法会调用所有它的子容器的invoke方法。

具体过程

1. 连接器创建request和response对象;

2.连接器调用StandardContext的invoke方法;

3.StandardContext的invoke方法必须调用该管道对象的invoke方法。StandardContext管道对象的基础阀是StandardContextValve类的实例。因此,StandardContext管道对象会调用StandardContextValve的invoke方法。

4.StandardContextValve的invoke方法获取相应的wrapper,处理http请求,调用wrapper的invoke方法

5.StandardWrapper是wrapper的标准实现,StandardWrapper对象的invoke方法调用pipeline的invoke方法。

6,StandardWrapper流水线的基本阀门时StandardWrapperValve。因此StandardWrapperValve的invoke方法会被调用。StandardWrapperValve的invoke方法会调用包装器的allocate方法获得一个servlet的实例。

7,当一个servlet需要被加载的时候,方法allocate调用方法load来加载一个servlet

8,方法load会调用servlet的init方法

我们主要关注的是一个servlet被调用的时候发生的细节。因此我们需要看StandardWrapper和StandarWrapperValve类

javax.servlet.SingleThreadModel

一个servlet可以实现javax.servlet.SingleThreadModel接口,实现此接口的一个servlet通俗称为SingleThreadModel(STM)的程序组件。

根据Servlet规范,实现此接口的目的是保证servlet一次只能有一个请求。

StandardWrapper实现

一个StandardWrapper对象的主要职责是:加载它表示的servlet,并实例化。该StandardWrapper不会调用servlet的service方法这个任务留给StandardWrapperValve对象,在StandardWrapper实例的基本阀门管道。StandardWrapperValve对象通过调用StandardWrapper的allocate方法获得Servlet实例。在获得Servlet实例之后的StandardWrapperValve调用servlet的service方法。

在servlet第一次被请求的时候,StandardWrapper加载servlet类。它是动态的加载servlet,所以需要知道servlet类的完全限定名称。通过StandardWrapper类的setServletClass方法将servlet的类名传递给StandardWrapper。必须考虑一个servlet是否实现了SingleThreadModel接口。
如果一个servlet没有实现SingleThreadModel接口,StandardWrapper加载该servlet一次,对于以后的请求返回相同的实例即可。

对于一个STM servlet,情况就有所不同了。StandardWrapper必须保证不能同时有两个线程提交STM servlet的service方法。

[java] view
plain
copy

  1. Servlet instance = <get an instance of the servlet>;
  2. if ((servlet implementing SingleThreadModel>) {
  3. synchronized (instance) {
  4. instance.service(request, response);
  5. }else{
  6. instance.service(request, response);
  7. }

Allocating the Servlet

StandardWrapperValve的invoke方法调用了包装器的allocate方法来获得一个请求servlet的实例,因此StandardWrapper类必须实现该接口

public javax.servlet.Servlet allocate() throws ServletException;

由于要支持STM servlet,这使得该方法更复杂了一点。实际上,该方法有两部分组成,一部分负责非STM servlet的工作,另一部分负责STM servlet。

第一部分的结构如下

if (!singleThreadModel) {

// returns a non-STM servlet instance

}

布尔变量singleThreadModel负责标志一个servlet是否是STM servlet。

它的初始值是false,loadServlet方法会检测加载的servlet是否是STM的,如果是则将它的值该为true

第二部分处理singleThreadModel为true的情况

synchronized (instancepool) {

// returns an instance of the servlet from the pool

}

对于非STM servlet,StandardWrapper定义一个java.servlet.Servlet类型的实例

private Servlet instance = null;

方法allocate检查该实例是否为null,如果是调用loadServlet方法来加载servlet。然后增加contAllocated整型并返回该实例。

[java] view
plain
copy

  1. if (!singleThreadModel) {
  2. if (instance == null) {
  3. synchronized (this) {
  4. if (instance == null) {
  5. try {
  6. instance = loadServlet();
  7. }catch (ServletException e) {
  8. throw e;
  9. }
  10. }
  11. }
  12. }
  13. if (!singleThreadModel) {
  14. countAllocated++;
  15. return (instance);
  16. }

StandardWrapperValve

StandardWrapperValve是StandardWrapper实例上的基本阀,主要完成2个操作:

1,执行与该servlet的所有过滤器;(看到了吗,filter是在StandardWrapper的基础阀出现的)

2,调用servlet实例的的service()方法

要实现这些内容,下面是StandardWrapperValve在他的invoke方法要实现的

. 调用StandardWrapper的allocate的方法来获得一个servlet实例

·调用它的private createFilterChain方法获得过滤链

· 调用过滤器链的doFilter方法。包括调用servlet的service方法

· 释放过滤器链

· 调用包装器的deallocate方法

· 如果Servlet无法使用了,调用Wrapper的unload方法

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-05 13:44:27

深入剖析tomcat之servlet容器的相关文章

tomcat(5)servlet容器

[0]README 0.0)本文部分文字描述转自:“深入剖析tomcat”,旨在学习 tomcat(5)servlet容器 的基础知识: 0.1)intro to servlet容器:servlet容器是用来处理请求servlet资源,并为web客户端填充response 对象的模块: 0.1.1)servlet容器:是 org.apache.catalina.Container接口的实例: 0.1.2)在Tomcat中,共有四种容器(types): t1)Engine: t2)Host: t3

JSP学习 —— 开篇:JSP,servlet容器,Tomcat,servlet容器之间的关系

JSP(JAVA SERVER PAGE)的缩写,其本身就是servlet的简化,是一种动态网页标准,其特点是在HTML代码中嵌入JAVA代码,JSP标签或用户标签来生成网页.至于它为什么会出现,主要原因在于早期的servlet技术在编写代码时经常通篇性的写一大堆HTML标签,静态文本及文本格式等表现逻辑,其开发效率非常之低下:为了解决这种情况,便随之出现了JSP,其静态部分(包括表现逻辑,如图片,文字等等)全用HTML语言来编写,只有需要动态生成的逻辑才由嵌入的JAVA代码来实现. 说到最后,

Web服务器(Apache)与Servlet容器(Tomcat)

之前一直比较迷惑Apache与Tomcat的关系,通过查询资料,有所了解,现记录于此. Apache与Tomcat 两者定位:Apache是HTTP Web服务器,Tomcat是Web容器. 有一个非常形象的比喻:Apache是一辆车,可以装载静态的物件(HTML静态网页等):但不能装动态的水(JSP.CGI等),要装水就需要桶(容器),当然桶也可以不放在车上而单独存放,则该容器即为Tomcat. 两者的主要区别: Apache是世界上最流行的Web服务器(其次是微软的IIS),可以处理浏览器的

tomcat和servlet的关系

一.什么是servlet? 处理请求和发送响应的过程是由一种叫做Servlet的程序来完成的,并且Servlet是为了解决实现动态页面而衍生的东西.理解这个的前提是了解一些http协议的东西,并且知道B/S模式(浏览器/服务器). 二.tomcat和servlet的关系 Tomcat 是Web应用服务器,是一个Servlet/JSP容器. Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户. 而Servlet是一种运行在支持J

SpringBoot-配置嵌入式Servlet容器(十三)

SpringBoot默认使用Tomcat作为嵌入式的Servlet容器: 如何定制和修改Servlet容器的相关配置 1.修改和server有关的配置(ServerProperties[也是EmbeddedServletContainerCustomizer]): server.port=8081 server.context-path=/crud server.tomcat.uri-encoding=UTF-8 //通用的Servlet容器设置 server.xxx //Tomcat的设置 s

SpringBoot(七) -- 嵌入式Servlet容器

一.嵌入式Servlet容器 在传统的开发中,我们在完成开发后需要将项目打成war包,在外部配置好TomCat容器,而这个TomCat就是Servlet容器.在使用SpringBoot开发时,我们无需再外部配置Servlet容器,使用的是嵌入式的Servlet容器(TomCat).如果我们使用嵌入式的Servlet容器,存在以下问题: 1.如果我们是在外部安装了TomCat,如果我们想要进行自定义的配置优化,可以在其conf文件夹下修改配置文件来实现.在使用内置Servlet容器时,我们可以使用

how tomcat works 读书笔记(二)----------一个简单的servlet容器

app1 (建议读者在看本章之前,先看how tomcat works 读书笔记(一)----------一个简单的web服务器 http://blog.csdn.net/dlf123321/article/details/39378157) 回顾我们上一章,我们开发了一个最最简单的web服务器,它可以使用户访问服务器内的静态资源.当然这是远远不够的,在这一节里,我们就试着让服务器在能相应静态资源的基础上继续支持servlet. servlet接口 javax.servlet.Servlet接口

Spring整合quartz关闭,关闭Tomcat Servlet容器时内存泄漏

出错信息 22-Sep-2017 06:19:51.064 WARNING [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [license] appears to have started a thread named [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Wo

how tomcat works 五 servlet容器 上

servlet容器是用来处理请求servlet资源,并为Web客户端填充response对象的模块.在上一篇文章(也就是书的第四章)我们设计了SimpleContainer类,让他实现Container接口,也基本完成了容器的作用.但是我们得知道在实际的tomcat中有4类容器: Engine: 表示整个Catalina servlet引擎; Host: 包含一个或多个Context容器的虚拟主机; Context:表示一个Web应用程序,一个Context可以包含多个Wrapper Wrapp