How tomcat works (5)servlet容器阅读笔记

第四章阅读了tomcat默认连接器的实现,当时connector中的使用的容器是自定义的容器,也是非常之简单奥,一个人就干完了所有的活,完成了初始化类加载器,加载servlet,调用servlet的service方法等的活儿,秉承了专事专干的也就是模块化设计的理念,这样的设计肯定是不够灵活的,这一章就来看看tomcat中的容器是如何设计的

总体介绍

总的来说呢,tomcat将容器分为了四类:

  • Engine:表示整个Catalina servlet引擎
  • Host: 表示包含有一个或者多个Context容器的虚拟主机,一台tomcat还能用来部署多个web应用?非常的阿妹增啊!!
  • Context:表示一个web应用程序,一个Context可以包含多个Wrapper
  • Wrapper:(包装纸)表示一个独立的servlet
  • 它们的具体实现在org.apache.catalina.core包下面,对应到具体的类就是standardxxx

容器的分层有点像流水线,一个web应用可能有很多的servlet,那么context就负责随便干点活(调一下valve),然后把活交给打工人(basicvalve 把活交给wrapper),而wrapper也负责随便干点活(调一下valve),然后把活交给真正的劳动人民(basicvalve 调用servlet的service方法),这里提到的valve、basicvalve都是

所以container接口及其上述容器的UML如下:

高级容器可以包含0-多个低级子容器,但是wrapper就没法再向下了,有如下的接口用于子容器的管理

public void addChild(Container child);
public void removeChild(Container child);
public Container findChild(String name);
public Container[] findChildren();
//如果wrapper调用这些接口就会抛出异常,以下为standardWrapper的addChild实现
public void addChild(Container child) { throw new IllegalStateException
(sm.getString("standardWrapper.notChild")); }

Pipeline和valve

这两者可以说是目前所接触到的tomcat容器和普通自定义容器的最大区别了,其实说简单点也就是加了点过滤器,来看看tomcat中这个功能如何实现吧

管道:包含了该servlet容器将要执行的任务,一个容器对应了一条管道

//standardPipeline
protected Container container = null;
//constructor
public StandardPipeline(Container container) { super();
setContainer(container); }
public void setContainer(Container container) { this.container = container; }

:也就是该servlet容器具体执行的任务,一条管道上面有很多很多阀门,但是有一个basicvalve,也就是基础阀,它永远是最后执行的,pipeline和valve两者的关系就如下图所示

一种简单的方式就是,用一个for循环遍历valves数组,对于每个valve都执行invoke方法,但是tomcat采取了另一种执行方式,引入了一个新的接口org.apache.catalina.valveContext来实现阀的遍历执行,具体的过程代码如下

//pipeline的invoke方法
public void invoke(Request request, Response response)
throws IOException, ServletException { // Invoke the first Valve in this pipeline for this request
(new StandardPipelineValveContext()).invokeNext(request, response); } //StandardPipelineValveContext的invokeNext
//StandardPipelineValveContext是StandardPipeline的一个内部类
//也就是通过内部类的方式一个一个访问valve然后调用
public void invokeNext(Request request, Response response)
throws IOException, ServletException {
//成员变量stage初始值为0,protected int stage = 0;
int subscript = stage;
stage = stage + 1; // Invoke the requested Valve for the current request thread
//valves数组
//protected Valve valves[] = new Valve[0];
if (subscript < valves.length) {
valves[subscript].invoke(request, response, this);
} else if ((subscript == valves.length) && (basic != null)) {
//最后调用basic阀门
basic.invoke(request, response, this);
} else {
throw new ServletException
(sm.getString("standardPipeline.noValve"));
}
} //这一章提供了两个简单的阀,就是打印一些信息
//clientIPLoggervalve
//这里还需要传递valveContext实现进来,在invoke中回调invokeNext方法
public void invoke(Request request, Response response, ValveContext valveContext)
throws IOException, ServletException { // Pass this request on to the next valve in our pipeline
valveContext.invokeNext(request, response);
//所以反而这个是等所有的调用结束后才会执行
System.out.println("Client IP Logger Valve");
ServletRequest sreq = request.getRequest();
System.out.println(sreq.getRemoteAddr());
System.out.println("------------------------------------");
}
//HeaderLoggervalve
public void invoke(Request request, Response response, ValveContext valveContext)
throws IOException, ServletException { // Pass this request on to the next valve in our pipeline
valveContext.invokeNext(request, response); System.out.println("Header Logger Valve");
ServletRequest sreq = request.getRequest();
if (sreq instanceof HttpServletRequest) {
HttpServletRequest hreq = (HttpServletRequest) sreq;
Enumeration headerNames = hreq.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement().toString();
String headerValue = hreq.getHeader(headerName);
System.out.println(headerName + ":" + headerValue);
} }
else
System.out.println("Not an HTTP Request"); System.out.println("------------------------------------");
}

Wrapper最低级的容器

wrapper接口中比较重要的方法

  • allocate方法:分配一个已经初始化的实例
  • load方法:加载并初始化实例

以Wrapper作为容器的应用程序的UML如下

主要类以及流程分析

SimpleLoader的构造方法

构造方法中初始化了classLoader,还是熟悉的味道,但是这里只初始化了ClassLoader

public SimpleLoader() {
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
//public static final String WEB_ROOT ="D:\\tomcat\\HowTomcatWorks\\webroot";
File classPath = new File(WEB_ROOT);
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
urls[0] = new URL(null, repository, streamHandler);
classLoader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
}
SimpleWrapper

void load()

public void load() throws ServletException {
instance = loadServlet();
}

Servlet loadServlet()

将servlet加载并实例化赋值给成员变量servlet

private Servlet loadServlet() throws ServletException {
if (instance!=null)
return instance; Servlet servlet = null;
String actualClass = servletClass;
if (actualClass == null) {
throw new ServletException("servlet class has not been specified");
}
//过去上面的simpleLoader
Loader loader = getLoader();
// Acquire an instance of the class loader to be used
if (loader==null) {
throw new ServletException("No loader.");
}
ClassLoader classLoader = loader.getClassLoader();
//删除了try catch
// Load the specified servlet class from the appropriate class loader
Class classClass = null;
if (classLoader!=null) {
classClass = classLoader.loadClass(actualClass);
}
servlet = (Servlet) classClass.newInstance();
servlet.init(null);
return servlet;
}

至此位置,已经理清楚了classloader的加载过程,那么具体一个请求的调用过程呢?也就是basicvalve是如何来的

basicvalve的调用过程分析

在pipeline和valve中分析过了,最后一个调用basicvalve,那么这个basicvalve是谁呢?

StandardWrapper构造方法

//在构造时就指定了standardWrapper了奥,那么这个StandardWrapperValue又是何方神圣呢?
public StandardWrapper() {
super();
pipeline.setBasic(new StandardWrapperValve());
}

StandardWrapperValve的invoke方法干了啥?对basic的invoke干了啥

public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
// Initialize local variables we may need
boolean unavailable = false;
Throwable throwable = null;
StandardWrapper wrapper = (StandardWrapper) getContainer();
ServletRequest sreq = request.getRequest();
ServletResponse sres = response.getResponse();
Servlet servlet = null;
HttpServletRequest hreq = null;
if (sreq instanceof HttpServletRequest)
hreq = (HttpServletRequest) sreq;
HttpServletResponse hres = null;
if (sres instanceof HttpServletResponse)
hres = (HttpServletResponse) sres; // Check for the application being marked unavailable
if (!((Context) wrapper.getParent()).getAvailable()) {
hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardContext.isUnavailable"));
unavailable = true;
} // Check for the servlet being marked unavailable
if (!unavailable && wrapper.isUnavailable()) {
log(sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
if (hres == null) {
; // NOTE - Not much we can do generically
} else {
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE))
hres.setDateHeader("Retry-After", available);
hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
}
unavailable = true;
} // Allocate a servlet instance to process this request
//调用了allocate方法,有点像connector的分配processor方法
if (!unavailable) {
servlet = wrapper.allocate();
} // Acknowlege the request
response.sendAcknowledgement(); // Create the filter chain for this request
//非常之容易迷失奥,我们的servlet已经到这里了
ApplicationFilterChain filterChain =
createFilterChain(request, servlet); // Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
try {
String jspFile = wrapper.getJspFile();
if (jspFile != null)
sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
else
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
if ((servlet != null) && (filterChain != null)) {
//在doFilter里面,如果没有filter要执行,才会终于轮到我们的service服务
//往下的代码就不贴了
filterChain.doFilter(sreq, sres);
}
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
} catch (IOException e) {
...
} // Release the filter chain (if any) for this request
try {
if (filterChain != null)
filterChain.release();
} catch (Throwable e) {
log(sm.getString("standardWrapper.releaseFilters",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
} // Deallocate the allocated servlet instance
try {
if (servlet != null) {
wrapper.deallocate(servlet);
}
} catch (Throwable e) {
log(sm.getString("standardWrapper.deallocateException",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
} // If this servlet has been marked permanently unavailable,
// unload it and release this instance
try {
if ((servlet != null) &&
(wrapper.getAvailable() == Long.MAX_VALUE)) {
wrapper.unload();
}
} catch (Throwable e) {
log(sm.getString("standardWrapper.unloadException",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
} }

至此,connector的一个简简单单的container.invoke()的具体流程就已经清晰了,先经过pipeline和valve的摧残,等到最后basicValve调用的时候,又要先经过filter的摧残,然后才调用servlet的service方法,非常之幸苦啊,期间还需要做一些准备工作,设置classLoader,加载并且实例化servlet

启动Bootstrap1,打印输出如下:

Client IP Logger Valve
127.0.0.1
------------------------------------
Header Logger Valve
host:localhost:8080
user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
accept-language:en-US,en;q=0.5
accept-encoding:gzip, deflate
connection:keep-alive
upgrade-insecure-requests:1
cache-control:max-age=0
------------------------------------

这两个valve确实是干了活的,并且由于这两个valve的特点,先调用的valve后打印信息

Context

通过上面的学习,已经学会了部署一个仅包含一个servlet的wrapper应用,但是通常来说,应用怎么可能只包含一个servlet呢?肯定是要多个servlet协作的呀

其实context的大部分和wrapper基本一致,只不过这里的context的BasicVavle就不负责真正调用servlet的service方法了,它负责分发请求,根据servletName或者其他的东西将请求分发给context容器内包含的那些wrappers,然后在wrapper里面再执行上面的流程

这个时候就来了个新的组件Mapper映射器,顾名思义,就是负责将请求映射到对应的wrapper上面

mapper接口的定义如下:

package org.apache.catalina;
public interface Mapper{
public Container getContainer();
public void setContainer(Container container);
public String getProtocol();
public void setProtocol(String procotol);
public Container map(Request request,boolean update);
}
  • map方法返回指定的container,也是它的核心方法,找到对应的wrapper

本应用程序的UML如下图所示:

主要类以及流程分析

这里主要分析以下Context是如何找到对应的mapper这条线

StandardContextValve的invoke
public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException { // Validate the request and response object types
if (!(request.getRequest() instanceof HttpServletRequest) ||
!(response.getResponse() instanceof HttpServletResponse)) {
return; // NOTE - Not much else we can do generically
} // Disallow any direct access to resources under WEB-INF or META-INF
HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
String contextPath = hreq.getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI =
requestURI.substring(contextPath.length()).toUpperCase();
if (relativeURI.equals("/META-INF") ||
relativeURI.equals("/WEB-INF") ||
relativeURI.startsWith("/META-INF/") ||
relativeURI.startsWith("/WEB-INF/")) {
notFound(requestURI, (HttpServletResponse) response.getResponse());
return;
} Context context = (Context) getContainer(); // Select the Wrapper to be used for this Request
Wrapper wrapper = null;
try {
//调用map方法找到对应的wrapper
wrapper = (Wrapper) context.map(request, true);
} catch (IllegalArgumentException e) {
badRequest(requestURI,
(HttpServletResponse) response.getResponse());
return;
}
if (wrapper == null) {
notFound(requestURI, (HttpServletResponse) response.getResponse());
return;
} // Ask this Wrapper to process this Request
response.setContext(context);
//调用wrapper的invoke方法
wrapper.invoke(request, response); }
SimpleContext的map方法
public Container map(Request request, boolean update) {
//this method is taken from the map method in org.apache.cataline.core.ContainerBase
//the findMapper method always returns the default mapper, if any, regardless the
//request's protocol
Mapper mapper = findMapper(request.getRequest().getProtocol());
if (mapper == null)
return (null); // Use this Mapper to perform this mapping
//调用SimpleContextMapper的map方法
return (mapper.map(request, update));
}
SimpleContextMapper的map方法
public Container map(Request request, boolean update) {
// Identify the context-relative URI to be mapped
String contextPath =
((HttpServletRequest) request.getRequest()).getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
//从request中切割出来relativeURI
String relativeURI = requestURI.substring(contextPath.length());
// Apply the standard request URI mapping rules from the specification
Wrapper wrapper = null;
String servletPath = relativeURI;
String pathInfo = null;
//根据这个URI去寻找对应的wrapper name
String name = context.findServletMapping(relativeURI);
if (name != null)
//根据name找到对应的wrapper
wrapper = (Wrapper) context.findChild(name);
return (wrapper);
}

SimpleContext的findServletMapping方法

public String findServletMapping(String pattern) {
synchronized (servletMappings) {
//servletMappings就是一个HashMap
return ((String) servletMappings.get(pattern));
}
}

SimpleContext的findChild方法

public Container findChild(String name) {
if (name == null)
return (null);
synchronized (children) { // Required by post-start changes
return ((Container) children.get(name));
}
}

至此context是如何根据relativeURI找到对应的wrapper的流程分析结束

容器启动

可以看到,此时需要启动一个服务器需要事先做多少准备,准备classloader,准备wrapper,准备context,添加valve,添加mapper,然后就可以启动了

public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet"); Context context = new SimpleContext();
context.addChild(wrapper1);
context.addChild(wrapper2); Valve valve1 = new HeaderLoggerValve();
Valve valve2 = new ClientIPLoggerValve(); ((Pipeline) context).addValve(valve1);
((Pipeline) context).addValve(valve2); Mapper mapper = new SimpleContextMapper();
mapper.setProtocol("http");
context.addMapper(mapper);
Loader loader = new SimpleLoader();
context.setLoader(loader);
// context.addServletMapping(pattern, name);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");
connector.setContainer(context);
try {
connector.initialize();
connector.start(); // make the application wait until we press a key.
System.in.read();
}
catch (Exception e) {
e.printStackTrace();
}
}

访问localhost:8080/Modern或者localhost:8080/Primitive

How tomcat works(深入剖析tomcat)servlet容器的更多相关文章

  1. How Tomcat works — 四、tomcat启动(3)

    上一节说到StandardService负责启动其子组件:container和connector,不过注意,是有先后顺序的,先启动container,再启动connector,这一节先来看看conta ...

  2. 深入剖析tomcat之一个简单的servlet容器

    上一篇,我们讲解了如果开发一个简单的Http服务器,这一篇,我们扩展一下,让我们的服务器具备servlet的解析功能. 简单介绍下Servlet接口 如果我们想要自定义一个Servlet,那么我们必须 ...

  3. How Tomcat works — 二、tomcat启动(1)

    主要介绍tomcat启动涉及到的一些接口和类. 目录 概述 tomcat包含的组件 server和service Lifecycle Container Connector 总结 概述 tomcat作 ...

  4. How Tomcat works — 七、tomcat发布webapp

    目录 什么叫发布 webapp发布方式 reload 总结 什么叫发布 发布就是让tomcat知道我们的程序在哪里,并根据我们的配置创建Context,进行初始化.启动,如下: 程序所在的位置 创建C ...

  5. How Tomcat works — 六、tomcat处理请求

    tomcat已经启动完成了,那么是怎么处理请求的呢?怎么到了我们所写的servlet的呢? 目录 Http11ConnectionHandler Http11Processor CoyoteAdapt ...

  6. How Tomcat works — 五、tomcat启动(4)

    前面摆了三节的姿势,现在终于要看到最终tomcat监听端口,接收请求了. 目录 Connector Http11Protocol JIoEndpoint 总结 在前面的初始化都完成之后,进行Conne ...

  7. How Tomcat works — 八、tomcat中的session管理

    在使用shiro的session的时候感觉对于tomcat中session的管理还不是特别清楚,而且session管理作为tomcat中比较重要的一部分还是很有必要学习的. 目录 概述 session ...

  8. How Tomcat works — 三、tomcat启动(2)

    在了解了tomcat 的一些基本组件之后,学习启动过程就更容易理解了,因为启动过程就是启动各个组件. 目录 启动顺序 Bootstrap类 Catalina类 StandardServer类和Stan ...

  9. servlet之servlet容器(一)

    1.servlet容器 ·servlet容器为javaweb应用提供运行时环境,负责管理servlet和jsp的生命周期以及管理它们的共享数据 ·servlet容器中的文件目录结构 ·tomcat是一 ...

随机推荐

  1. P1098 字符串的展开

    P1098 字符串的展开 刷新三观的模拟题 题意描述 太长了自己去看吧. 算法分析 模拟题分析你*呀! 写这篇题解的唯一原因是:三目运算符用的好的话,可以让百行大模拟变成30行水题. 代码实现 #in ...

  2. Hadoop安装 与 HDFS体系结构

  3. 834. Sum of Distances in Tree —— weekly contest 84

    Sum of Distances in Tree An undirected, connected tree with N nodes labelled 0...N-1 and N-1 edges a ...

  4. 使用 c++ 模板显示实例化解决模板函数声明与实现分离的问题

    问题背景 开始正文之前,做一些背景铺垫,方便读者了解我的工程需求.我的项目是一个客户端消息分发中心,在连接上消息后台后,后台会不定时的给我推送一些消息,我再将它们转发给本机的其它桌面产品去做显示.后台 ...

  5. php连接神通数据库 ci框架

    神通数据库连接手册 1.扩展安装 目前连接神通数据库有两种方式 ODBC PDO_ACI 具体请看手册,目前使用PDO_ODBC方法PS:请看操作2 目前只有64位有pdo_aci.so文件,需要在神 ...

  6. 13、form组件

    Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否 ...

  7. 谈谈对不同I/O模型的理解 (阻塞/非阻塞IO,同步/异步IO)

    一.关于I/O模型的问题 最近通过对ucore操作系统的学习,让我打开了操作系统内核这一黑盒子,与之前所学知识结合起来,解答了长久以来困扰我的关于I/O的一些问题. 1. 为什么redis能以单工作线 ...

  8. 三分钟快速解析GraphQL基本工作思路!

    欢迎阅读 本文会通过实际场景介绍一下 GraphQL,目的是让你快速了解 GraphQL 是什么,以及基本工作思路,不包含实际用法,所以阅读很轻松. 一.GraphQL 是什么? GraphQL 是后 ...

  9. 在java9+版本中,接口的内容和注意

    1.成员变量其实就是常量,格式: [public] [static] [final] 数据类型 常量名称 = 数据值: 注意: 常量必须进行赋值,而且一旦赋值不能改变. 常量名称完全大写,用下划线进行 ...

  10. git引入_版本控制介绍

    八个字形容git技术: 公司必备,一定要会 一.git概念: git是一个免费的,开源的分布式版本控制系统,可以快速高效的处理从小型到大型的项目 二.什么是版本控制: 版本控制是一种一个记录一个或若个 ...